面试题 设计模式

请列举出在JDK中几个常用的设计模式?

单例模式(Singleton pattern)用于Runtime,Calendar和其他的一些类中。工厂模式(Factory pattern)被用于各种不可变的类如 Boolean,像Boolean.valueOf,观察者模式(Observer pattern)被用于 Swing 和很多的事件监听中。装饰器设计模式(Decorator design pattern)被用于多个 Java IO 类中。

什么是设计模式?是否在代码里面使用过任何设计模式?

设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。设计模式是代码可用性的延伸
设计模式分类:创建型模式,结构型模式,行为型模式

静态代理、JDK动态代理以及CGLIB动态代理

代理模式是java中最常用的设计模式之一,尤其是在spring框架中广泛应用。对于java的代理模式,一般可分为:静态代理、动态代理、以及CGLIB实现动态代理。
对于上述三种代理模式,分别进行说明。

静态代理

静态代理其实就是在程序运行之前,提前写好被代理方法的代理类,编译后运行。在程序运行之前,class已经存在。下面实现一个静态代理demo:
2021-05-30-21-20-43-857298.png
定义一个接口Target

  1. package com.test.proxy;
  2. public interface Target {
  3. public String execute();
  4. }

TargetImpl 实现接口Target

  1. package com.test.proxy;
  2. public class TargetImpl implements Target {
  3. @Override
  4. public String execute() {
  5. System.out.println("TargetImpl execute!");
  6. return "execute";
  7. }
  8. }

代理类

  1. package com.test.proxy;
  2. public class Proxy implements Target{
  3. private Target target;
  4. public Proxy(Target target) {
  5. this.target = target;
  6. }
  7. @Override
  8. public String execute() {
  9. System.out.println("perProcess");
  10. String result = this.target.execute();
  11. System.out.println("postProcess");
  12. return result;
  13. }
  14. }

测试类:

  1. package com.test.proxy;
  2. public class ProxyTest {
  3. public static void main(String[] args) {
  4. Target target = new TargetImpl();
  5. Proxy p = new Proxy(target);
  6. String result = p.execute();
  7. System.out.println(result);
  8. }
  9. }

运行结果:

  1. perProcess
  2. TargetImpl execute
  3. postProcess
  4. execute

静态代理需要针对被代理的方法提前写好代理类,如果被代理的方法非常多则需要编写很多代码,因此,对于上述缺点,通过动态代理的方式进行了弥补。

动态代理

动态代理主要是通过反射机制,在运行时动态生成所需代理的class。
2021-05-30-21-20-43-980882.png
接口

  1. package com.test.dynamic;
  2. public interface Target {
  3. public String execute();
  4. }

实现类

  1. package com.test.dynamic;
  2. public class TargetImpl implements Target {
  3. @Override
  4. public String execute() {
  5. System.out.println("TargetImpl execute!");
  6. return "execute";
  7. }
  8. }

代理类

  1. package com.test.dynamic;
  2. import java.lang.reflect.InvocationHandler;
  3. import java.lang.reflect.Method;
  4. public class DynamicProxyHandler implements InvocationHandler{
  5. private Target target;
  6. public DynamicProxyHandler(Target target) {
  7. this.target = target;
  8. }
  9. @Override
  10. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  11. System.out.println("========before==========");
  12. Object result = method.invoke(target,args);
  13. System.out.println("========after===========");
  14. return result;
  15. }
  16. }

测试类

  1. package com.test.dynamic;
  2. import java.lang.reflect.Proxy;
  3. public class DynamicProxyTest {
  4. public static void main(String[] args) {
  5. Target target = new TargetImpl();
  6. DynamicProxyHandler handler = new DynamicProxyHandler(target);
  7. Target proxySubject = (Target) Proxy.newProxyInstance(TargetImpl.class.getClassLoader(),TargetImpl.class.getInterfaces(),handler);
  8. String result = proxySubject.execute();
  9. System.out.println(result);
  10. }
  11. }

运行结果:

  1. ========before==========
  2. TargetImpl execute
  3. ========after===========
  4. execute

无论是动态代理还是静态代理,都需要定义接口,然后才能实现代理功能。这同样存在局限性,因此,为了解决这个问题,出现了第三种代理方式:cglib代理。

cglib代理

CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。
2021-05-30-21-20-44-082943.png
目标类

  1. package com.test.cglib;
  2. public class Target {
  3. public String execute() {
  4. String message = "-----------test------------";
  5. System.out.println(message);
  6. return message;
  7. }
  8. }

通用代理类

  1. package com.test.cglib;
  2. import net.sf.cglib.proxy.MethodInterceptor;
  3. import net.sf.cglib.proxy.MethodProxy;
  4. import java.lang.reflect.Method;
  5. public class MyMethodInterceptor implements MethodInterceptor{
  6. @Override
  7. public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
  8. System.out.println(">>>>MethodInterceptor start...");
  9. Object result = proxy.invokeSuper(obj,args);
  10. System.out.println(">>>>MethodInterceptor ending...");
  11. return "result";
  12. }
  13. }

测试类

  1. package com.test.cglib;
  2. import net.sf.cglib.proxy.Enhancer;
  3. public class CglibTest {
  4. public static void main(String[] args) {
  5. System.out.println("***************");
  6. Target target = new Target();
  7. CglibTest test = new CglibTest();
  8. Target proxyTarget = (Target) test.createProxy(Target.class);
  9. String res = proxyTarget.execute();
  10. System.out.println(res);
  11. }
  12. public Object createProxy(Class targetClass) {
  13. Enhancer enhancer = new Enhancer();
  14. enhancer.setSuperclass(targetClass);
  15. enhancer.setCallback(new MyMethodInterceptor());
  16. return enhancer.create();
  17. }
  18. }

执行结果:

  1. ***************
  2. >>>>MethodInterceptor start...
  3. -----------test------------
  4. >>>>MethodInterceptor ending...
  5. result

代理对象的生成过程由Enhancer类实现,大概步骤如下:

  1. 生成代理类Class的二进制字节码;
  2. 通过Class.forName加载二进制字节码,生成Class对象;
  3. 通过反射机制获取实例构造,并初始化代理类对象。

    单例模式

    单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
    意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
    主要解决:一个全局使用的类频繁地创建与销毁。
    懒汉式,线程安全
    代码实例: ```java public class Singleton2 {

    private static Singleton2 instance;

    private Singleton2() {}

    public static synchronized Singleton2 getInstance() {

    1. if (instance == null) {
    2. instance = new Singleton2();
    3. }
    4. return instance;

    }

}

  1. <a name="Chh5F"></a>
  2. ### 饿汉式,线程安全
  3. 代码实例:
  4. ```java
  5. public class Singleton3 {
  6. private static Singleton3 instance = new Singleton3();
  7. private Singleton3() {}
  8. public static Singleton3 getInstance() {
  9. return instance;
  10. }
  11. }

双检锁/双重校验锁 + volatile关键字

代码实例:

  1. public class Singleton7 {
  2. private static volatile Singleton7 instance = null;
  3. private Singleton7() {}
  4. public static Singleton7 getInstance() {
  5. if (instance == null) {
  6. synchronized (Singleton7.class) {
  7. if (instance == null) {
  8. instance = new Singleton7();
  9. }
  10. }
  11. }
  12. return instance;
  13. }
  14. }

工厂模式

工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
主要解决:主要解决接口选择的问题。
创建一个 Shape 接口和实现 Shape 接口的实体类。下一步是定义工厂类 ShapeFactory。
FactoryPatternDemo,演示类使用 ShapeFactory 来获取 Shape 对象。它将向 ShapeFactory 传递信息(CIRCLE / RECTANGLE / SQUARE),以便获取它所需对象的类型。
2021-05-30-21-20-44-380701.jpeg

步骤 1:创建一个接口

Shape.java

  1. public interface Shape {
  2. void draw();
  3. }

步骤 2:创建实现接口的实体类

Rectangle.java

  1. public class Rectangle implements Shape {
  2. @Override
  3. public void draw() {
  4. System.out.println("Inside Rectangle::draw() method.");
  5. }
  6. }

Square.java

  1. public class Square implements Shape {
  2. @Override
  3. public void draw() {
  4. System.out.println("Inside Square::draw() method.");
  5. }
  6. }

Circle.java

  1. public class Circle implements Shape {
  2. @Override
  3. public void draw() {
  4. System.out.println("Inside Circle::draw() method.");
  5. }
  6. }

步骤 3:创建一个工厂,生成基于给定信息的实体类的对象

ShapeFactory.java

  1. public class ShapeFactory {
  2. //使用 getShape 方法获取形状类型的对象
  3. public Shape getShape(String shapeType) {
  4. if (shapeType == null) {
  5. return null;
  6. }
  7. shapeType = shapeType.toLowerCase();
  8. switch (shapeType) {
  9. case "circle":
  10. return new Circle();
  11. case "rectangle":
  12. return new Rectangle();
  13. case "square":
  14. return new Square();
  15. default:
  16. return null;
  17. }
  18. }
  19. }

步骤 4:使用该工厂,通过传递类型信息来获取实体类的对象

FactoryPatternDemo.java

  1. public class FactoryPatternDemo {
  2. public static void main(String[] args) {
  3. ShapeFactory shapeFactory = new ShapeFactory();
  4. //获取 Circle 的对象,并调用它的 draw 方法
  5. Shape shape1 = shapeFactory.getShape("CIRCLE");
  6. //调用 Circle 的 draw 方法
  7. shape1.draw();
  8. //获取 Rectangle 的对象,并调用它的 draw 方法
  9. Shape shape2 = shapeFactory.getShape("RECTANGLE");
  10. //调用 Rectangle 的 draw 方法
  11. shape2.draw();
  12. //获取 Square 的对象,并调用它的 draw 方法
  13. Shape shape3 = shapeFactory.getShape("SQUARE");
  14. //调用 Square 的 draw 方法
  15. shape3.draw();
  16. }
  17. }

步骤 5:验证输出

  1. Inside Circle::draw() method.
  2. Inside Rectangle::draw() method.
  3. Inside Square::draw() method.

观察者模式

当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。
意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

实现

观察者模式使用三个类 Subject、Observer 和 Client。Subject 对象带有绑定观察者到 Client 对象和从 Client 对象解绑观察者的方法。创建 Subject 类、Observer 抽象类和扩展了抽象类 Observer 的实体类。
ObserverPatternDemo,演示类使用 Subject 和实体类对象来演示观察者模式。
2021-05-30-21-20-44-492079.jpeg

步骤 1:创建 Subject 类

Subject.java

  1. public class Subject {
  2. private List<Observer> observers = new ArrayList<>();
  3. private int state;
  4. public int getState() {
  5. return state;
  6. }
  7. public void setState(int state) {
  8. this.state = state;
  9. notifyAllObservers();
  10. }
  11. public void attach(Observer observer) {
  12. observers.add(observer);
  13. }
  14. public void notifyAllObservers() {
  15. for (Observer observer : observers) {
  16. observer.update();
  17. }
  18. }
  19. }

步骤 2:创建 Observer 类

Observer.java

  1. public abstract class Observer {
  2. protected Subject subject;
  3. public abstract void update();
  4. }

步骤 3:创建实体观察者类

BinaryObserver.java

  1. public class BinaryObserver extends Observer {
  2. public BinaryObserver(Subject subject) {
  3. this.subject = subject;
  4. this.subject.attach(this);
  5. }
  6. @Override
  7. public void update() {
  8. System.out.println("Binary String: "
  9. + Integer.toBinaryString(subject.getState()));
  10. }
  11. }

OctalObserver.java

  1. public class OctalObserver extends Observer {
  2. public OctalObserver(Subject subject){
  3. this.subject = subject;
  4. this.subject.attach(this);
  5. }
  6. @Override
  7. public void update() {
  8. System.out.println( "Octal String: "
  9. + Integer.toOctalString( subject.getState() ) );
  10. }
  11. }

HexaObserver.java

  1. public class HexaObserver extends Observer {
  2. public HexaObserver(Subject subject){
  3. this.subject = subject;
  4. this.subject.attach(this);
  5. }
  6. @Override
  7. public void update() {
  8. System.out.println( "Hex String: "
  9. + Integer.toHexString( subject.getState() ).toUpperCase() );
  10. }
  11. }

步骤 4:使用 Subject 和实体观察者对象

ObserverPatternDemo.java

  1. public class ObserverPatternDemo {
  2. public static void main(String[] args) {
  3. Subject subject = new Subject();
  4. new BinaryObserver(subject);
  5. new HexaObserver(subject);
  6. new OctalObserver(subject);
  7. System.out.println("First state change: 15");
  8. subject.setState(15);
  9. System.out.println();
  10. System.out.println("Second state change: 10");
  11. subject.setState(10);
  12. }
  13. }

步骤 5:验证输出

  1. First state change: 15
  2. Binary String: 1111
  3. Hex String: F
  4. Octal String: 17
  5. Second state change: 10
  6. Binary String: 1010
  7. Hex String: A
  8. Octal String: 12

装饰器模式

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
主要解决:一般的,为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。

实现

创建一个 Shape 接口和实现了 Shape 接口的实体类。然后创建一个实现了 Shape 接口的抽象装饰类 ShapeDecorator,并把 Shape 对象作为它的实例变量。
RedShapeDecorator 是实现了 ShapeDecorator 的实体类。
DecoratorPatternDemo,演示类使用 RedShapeDecorator 来装饰 Shape 对象。
2021-05-30-21-20-44-605602.jpeg

步骤 1:创建一个接口

Shape.java

  1. public interface Shape {
  2. void draw();
  3. }

步骤 2:创建实现接口的实体类

Rectangle.java

  1. public class Rectangle implements Shape {
  2. @Override
  3. public void draw() {
  4. System.out.println("Shape: Rectangle");
  5. }
  6. }

Circle.java

  1. public class Circle implements Shape {
  2. @Override
  3. public void draw() {
  4. System.out.println("Shape: Circle");
  5. }
  6. }

步骤 3:创建实现了 Shape 接口的抽象装饰类

ShapeDecorator.java

  1. public abstract class ShapeDecorator implements Shape {
  2. protected Shape decoratorShape;
  3. public ShapeDecorator(Shape decoratorShape) {
  4. this.decoratorShape = decoratorShape;
  5. }
  6. @Override
  7. public void draw() {
  8. decoratorShape.draw();
  9. }
  10. }

步骤 4:创建扩展了 ShapeDecorator 类的实体装饰类

RedShapeDecorator.java

  1. public class RedShapeDecorator extends ShapeDecorator {
  2. public RedShapeDecorator(Shape decoratorShape) {
  3. super(decoratorShape);
  4. }
  5. @Override
  6. public void draw() {
  7. decoratorShape.draw();
  8. setRedBorder(decoratorShape);
  9. }
  10. private void setRedBorder(Shape decoratorShape) {
  11. System.out.println("Border Color: Red");
  12. }
  13. }

步骤 5:使用 RedShapeDecorator 来装饰 Shape 对象

DecoratorPatternDemo.java

  1. public class DecoratorPatternDemo {
  2. public static void main(String[] args) {
  3. Shape circle = new Circle();
  4. Shape redCircle = new RedShapeDecorator(new Circle());
  5. Shape redRectangle = new RedShapeDecorator(new Rectangle());
  6. System.out.println("Circle with normal border");
  7. circle.draw();
  8. System.out.println("\nCircle of red border");
  9. redCircle.draw();
  10. System.out.println("\nRectangle of red border");
  11. redRectangle.draw();
  12. }
  13. }

步骤 6:验证输出

  1. Circle with normal border
  2. Shape: Circle
  3. Circle of red border
  4. Shape: Circle
  5. Border Color: Red
  6. Rectangle of red border
  7. Shape: Rectangle
  8. Border Color: Red

秒杀系统设计

什么是秒杀

通俗一点讲就是网络商家为促销等目的组织的网上限时抢购活动

业务特点

  • 高并发:秒杀的特点就是这样时间极短、 瞬间用户量大。
  • 库存量少:一般秒杀活动商品量很少,这就导致了只有极少量用户能成功购买到。
  • 业务简单:流程比较简单,一般都是下订单、扣库存、支付订单
  • 恶意请求,数据库压力大

    解决方案

    前端:页面资源静态化,按钮控制,使用答题校验码可以防止秒杀器的干扰,让更多用户有机会抢到
    nginx:校验恶意请求,转发请求,负载均衡;动静分离,不走tomcat获取静态资源;gzip压缩,减少静态文件传输的体积,节省带宽,提高渲染速度
    业务层:集群,多台机器处理,提高并发能力
    redis:集群保证高可用,持久化数据;分布式锁(悲观锁);缓存热点数据(库存)
    mq:削峰限流,MQ堆积订单,保护订单处理层的负载,Consumer根据自己的消费能力来取Task,实际上下游的压力就可控了。重点做好路由层和MQ的安全
    数据库:读写分离,拆分事务提高并发度

    秒杀系统设计小结

  • 秒杀系统就是一个“三高”系统,即高并发、高性能和高可用的分布式系统

  • 秒杀设计原则:前台请求尽量少,后台数据尽量少,调用链路尽量短,尽量不要有单点
  • 秒杀高并发方法:访问拦截、分流、动静分离
  • 秒杀数据方法:减库存策略、热点、异步、限流降级
  • 访问拦截主要思路:通过CDN和缓存技术,尽量把访问拦截在离用户更近的层,尽可能地过滤掉无效请求。
  • 分流主要思路:通过分布式集群技术,多台机器处理,提高并发能力。