Skip to content

Latest commit

 

History

History
449 lines (311 loc) · 18.5 KB

设计模式.md

File metadata and controls

449 lines (311 loc) · 18.5 KB

设计模式六大原则

  1. 单一职责原则:(Single responsibility principle SRP)每个类都应该有一个单一的功能,并且功能应该由这个类封装起来。并且它的所有服务都应该严密地与该功能平行。—《敏捷软件开发,原则,模式和实践》Robert Cecil Martin

  2. 迪米特法则:最少知识原则,一个类对于其他类知道的越少越好(LOD)

  3. 接口隔离原则:(interface-segregation principles ISP)客户不应被迫使用对其而言无用的方法或功能。ISP 拆分非常庞大臃肿的接口成为更小和更具体的接口,这样客户只需要知道他们感兴趣的方法。这个缩小的接口被成为角色接口。ISP 的目的是使系统解开耦合,从而更容易重构,更改和重新部署。

  4. 里式替换原则:(Liskov Substitution principle,LSP)派生类(子类)对象可以在程序中替换其基类(超类)对象。—Barbara Liskov

  5. 依赖倒置原则:(Dependency inversion principle,DIP)高层次的模块不依赖于低层次的模块,两者都依赖于抽象接口;抽象接口不依赖于具体实现,具体实现依赖于抽象接口。

    Dependency_inversion.png

  6. 开闭原则:(Open close principle OCP)软件中的对象(类、模块、函数等)应该对于扩展是开放的,但对于修改是封闭的。这意味着一个实体允许在不改变它的源代码的前提下变更它的行为。

工厂模式

  1. 在工厂模式中,创建对象时不会对客户端暴露创建逻辑,而是通过一个共同的接口来指向新创建的对象,实现了调用者和创建者的分离。工厂模式分为简单工厂、工厂方法、抽象工厂。
  2. 使用工厂模式的好处:
    • 降低系统的耦合性,为后期修改提供便利。
    • 将选择实现类、创建对象统一管理和控制。将调用者和实现类解耦。
  3. Spring 中的工厂模式
    1. Spring IOC
      • Spring IOC 容器在创建 bean 的过程中使用了工厂模式
      • Spring 中通过 XML 配置和通过注解配置创建 Bean,大部分是通过简单工厂模式创建的
      • 当容器拿到 BeanName 和 Class 类型后,动态地通过反射创建具体的某个对象,最后将创建的对象放入 Map 中
    2. Spring IOC 使用 工厂模式创建 Bean 的原因
      • 减少系统之间的耦合性
      • 减少系统代码的重复量
      • Spring IOC 容器中的 Map 集合,使得工厂模式创建的对象符合单例模式,提高系统的运行效率。
  4. 工厂模式的分类
    • 简单工厂模式:用来生产同一等级结构的任意产品(不支持扩展增加产品)
    • 工厂方法模式:用来生产同一等级结构的固定产品(支持扩展增加产品)
    • 抽象工厂模式:用于生产不同产品族的全部产品(不支持扩展增加产品;支持增加产品族)

策略模式

  1. 定义

    定义一组算法,将每一个算法封装起来,从而使得它们可以相互切换

  2. 特点

    • 每一组算法就是不同的一种策略

    • 这些算法都实现了相同的接口或继承了相同的抽象类,因此这些策略之间可以相互转换

      image-20210621203402757.png

    • 涉及到的角色

      • 封装角色(Context):上层访问策略的入口,持有抽象策略角色的引用
      • 抽象策略角色(Strategy):提供相应算法的实现接口,定义策略必须的方法和属性
      • 具体策略角色(Concrete Strategy):具体的算法策略实现逻辑
  3. 优点与缺点

    • 优点:1.算法可以自由切换。2. 避免多重条件判断。3.扩展性良好
    • 缺点:1. 策略类的数量会随着策略的增多而变多。2.所有策略类都需要对外暴露

单例模式

  1. 定义

    单例对象的类必须保证只有一个实例存在

  2. 适用场景

    • 需要创建频繁实例化然后销毁的对象
    • 创建对象时耗时或者消耗资源过多,但是需要经常使用的对象
    • 有状态的工具类对象
    • 频繁访问数据库或文件的对象
  3. 实现的分类

    • 饿汉式

      public class Singleton{
          /**
          * 优点:没有线程安全问题,实现简单
          * 缺点:提前初始化会延长类加载器加载类的时间;如果不使用会浪费内存空间;不能传递参数
          **/
          private static final Singleton instance = new Singleton();
          private Singleton(){}
          
          public static Singleton getInstance(){
              return instance;
          }
      }
    • 懒汉式

      // 静态内部类方式
      public class Singleton{
          private Singleton(){}
          
          /**
          * 使用延迟类加载,在访问该方法时才初始化实例对象
          * 优点:解决线程安全,延迟初始化
          **/
          public static Singleton getInstance() {
              return Holder.INSTANCE;
          }
       	
          /**
          * Java 中的静态变量和静态代码是在类加载的时候就执行的
          * 成员变量随着对象的创建而存在,随着对象的回收而被释放
          * 静态变量随着类的加载而存在,随着类的消失而消失
          **/
          private static class Holder {
              private static final Singleton INSTANCE = new Singleton();
          }
      }
    • 双重检查锁创建懒汉式

      public class Singleton{
          // volatile 避免指令重排序,重排序可能导致初始化的实例是 null。
          private volatile static Singleton instance;
          
          private Singleton(){}
          
          public Singleton getInstance() {
              if (null == instance) { // 第一次判断,提高性能,减少多个线程对于锁的竞争
                  synchronized(Singleton.class) {
                      if (null == instance) { // 第二次判断避免之前等待的线程持有锁之后再次进行初始化
                          instance = new Singleton();
                      }
                  }
              }
              
              return instance;
          }
      }
  4. Spring 中 Bean 单例

    • 当多个用户访问同一服务时,容器会给每一个请求分配一个线程,这时多个线程会并发执行该请求对应的业务逻辑(成员方法)。如果此时处理逻辑中存在对单例状态的修改(体现为修改单例的成员属性),必须考虑线程同步的问题。

    • 实现

    • Controller 默认是单例的,因此不要使用非静态的成员变量。

    • 单例对象生命周期(单例对象与容器共存亡)

      • 出生:容器创建时对象出生。(立即创建)
      • 活着:只要容器在,对象一直活着。
      • 死亡:容器销毁,对象消亡
    • 多例对象生命周期

      • 出生:使用对象时,Spring 为我们创建(延时创建)
      • 活着:对象在使用过程中就一直活着
      • 死亡:当对象长时间未使用且没有别的引用时,由 Java 的垃圾回收器回收。
  5. 工具类的考虑

    • 如果没有配置信息的工具类,使用静态类更好,随处调用,不需要引用
    • 如果有配置信息的工具类,最好使用单例模式。

命令模式

  1. 定义

    将请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。

  2. 模式结构

    image.png

    • Command:抽象命令类
    • ConcreteCommand:具体命令类
    • Invoker:调用者
    • Receiver:接收者
    • Client:客户类
  3. 优点

    • 降低系统的耦合度
    • 新的命令可以很容易地加入到系统中
    • 可以比较容易地设计一个命令队列和宏命令(组合命令)
    • 可以方便地实现对请求的 Undo 和 Redo
  4. 缺点

    可能导致系统存在过多的具体命令类,影响命令模式的使用。

  5. 样例代码

    public interface Command { // 每个命令对象的实现接口
        void execute();
    }
    
    public class Receiver { // 接受命令,作出相应的对象
        public Receiver() {}
        
        public void actionOne() {
            System.out.println("Command One.......");
        }
        
        public void actionTwo() {
            System.out.println("Command Two.......");
        }
    }
    
    public class CommandOne implements Command {
        private Receiver receiver;
        
        public CommandOne(Receiver receiver) {this.receiver = receiver;}
        
        @Overrided
        public void execute() {
            receiver.actionOne();
    	}
    }
    
    public class CommandTwo implements Command {
        private Receiver receiver;
        
        public CommandTwo(Receiver receiver) {this.receiver = receiver;}
        
        @Overrided
        public void execute() {
            receiver.actionTwo();
    	}
    }
    
    public class Invoker { // 调用者对象
        private Command commandOne;
        private Command commandTwo;
        
        public Invoker(Command commandOne, Command commandTwo) {
            this.commandOne = commandOne;
            this.commandTwo = commandTwo;
        }
        
        public void actionOne() {this.commandOne.excute();}
        
        public void actionTwo() {this.commandTwo.execute();}
    }
    
    public class Client {
        public static void main(String[] args) {
            Receiver receiver = new Receiver();
            CommandOne commmandOne = new CommandOne(receiver);
            CommandTwo commmandTwo = new CommandTwo(receiver);
            Invoker invoker = new Invoker(commandOne, commandTwo);
            invoker.actionOne();
            invoker.actionTwo();
        }
    }

代理模式

  1. 定义

    • 通过代理控制对象的访问,在这个对象调用方法之前或之后去处理/添加新的功能(AOP 的微实现)
    • 代理在原有代码乃至原业务流程都不修改的情况下,直接在业务流程中切入新代码,增加新功能,与 Spring 的 AOP 相似
    img
  2. 应用场景

    • Spring AOP、日志打印、异常处理、事务控制、权限控制等。
  3. 代理的分类

    • 静态代理

      • 涉及到的角色:

        • 抽象角色:声明真实对象和代理对象的共同接口
        • 代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时附加其他的操作,相当于对真实的对象进行封装。
        • 真实角色:代理角色所代表的真实对象,最终引用的对象。
      • 优缺点

        • 优点:在不修改目标对象的功能前提下,对目标进行功能扩展
        • 缺点:每个代理类都需要实现委托类的接口,如果接口修改方法,那么代理类也必须修改方法。其次,代理类每一个接口对象对应一个委托兑现,如果委托对象非常多,则静态代理类将会十分臃肿。
    • 动态代理

      • JDK 动态代理

        • 动态代理解决静态代理中存在的问题,通过反射来实现,使用 java.lang.reflect.Proxy,通过固定的规则来生成

        • JDK 动态代理的生成步骤

          1. 编写一个委托类的接口,即静态代理的 Subject 接口
          2. 实现一个真正的委托类,即静态代理的 RealSubject 类
          3. 创建一个动态代理类,实现 InvocationHandler 接口,并重写 incoke 方法
          4. 在测试类中,生成动态代理的对象
          public class DynamicProxy implements InvocationHandler {
              private Object object;
              
              public DynamicProxy(Object object){
                  this.object = object;
          	}
              
              @Override
              public Object invoke(Object proxy, Method method, Object[] args) {
                  Object result = method.invoke(proxy, args);
                  return result;
              }
          }
          // 创建动态代理对象
          Subject realSubject = new RealSubject();
          DynamicProxy proxy = new DyanmicProxy(realSubject);
          ClassLoader loader = realSubject.getClass().getClassLoader();
          Subject subject = (Subject) Proxy.newInstance(loader, new Class[]{Subject.class}, proxy);
          subject.visit();
          
          /**
          *
          * @param loader: 指定对象的类加载器
          * @param interfaces: 代理对象需要实现的接口,可以同时指定多个接口
          * @param h: 方法调用的实际处理者,代理对象的方法调用都会转发到这里
          **/
          Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h)
      • cglib 动态代理

        // 一个没有任何实现接口的类 HelloConcrete
        public class HelloConcrete{
            public String sayHello(String str) {
            	return "HelloConcrete: " + str;
        	}
        }
        
        // CGLIB
        // 1. 首先实现一个 MyMethodInterceptor,方法调用会转发到该类的 intercept() // 方法 
        public class MyMethodInterceptor implements MethodInterceptor {
            @Override
            public Object intercept(Object obj, Method method, Object[] args,
                MethodProxy proxy) throws Throwable {
                logger.info("You said: " + Arrays.toString(args));
                return proxy.invokeSuper(obj, args);
        	}
        }
        
        // 在需要使用 HelloConcrete时,通过 CGLIB 动态代理获取代理对象
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(HelloConcrete.class);
        enhancer.setCallback(new MyMethodInterceptor());
        
        HelloConcrete hello = (HelloConcrete)enhancer.create();
        System.out.println(hello.sayHello("I love you!"));
        • 通过 CGLIB 的 Enhancer 来指定要代理的目标对象、实际处理代理逻辑的对象,最终通过调用 create() 方法得到代理对象,对于这个对象的所有非 final 方法都会转发给 MethodInterceptor.intercept() 方法,在 intercept() 方法里可以加入任何逻辑,如:修改方法参数、加入 日志功能、安全检查功能等;通过调用 MethodProxy.invokeSuper() 方法,将调用转发给原始对象,具体示例中就是 HelloConcrete 的具体方法。
        • 对于从 Object 中继承的方法,CGLIB 也会进行代理,如:hashCode()、equals()、toString()等,但是 getClass()、wait()不会,这是因为他们是 final 方法。
        • 原理
          • CGLIB 是一个高性能的代码生成包,底层是通过使用字节码处理框架 ASM,可以在运行期间扩展 Java 类与实现 Java 接口,Enhancer 是 CGLIB 的字节码增强器,可以很方便地对类进行扩展。
          • 创建步骤:
            1. 生成代理类的二进制字节码文件
            2. 加载二进制字节码,生成 Class 对象(使用 Class.forName() 加载)
            3. 通过反射机制获得实例构造,并创建代理对象。
      • JDK 动态代理与 CGLIB 代理的区别

        1. JDK 动态代理利用拦截器(拦截器必须实现 InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用 InvokerHandler 来处理。只能对实现了接口的类生成代理。
        2. CGLIB 利用 ASM 字节码处理框架,将代理对象类的 Class 文件加载进来,通过修改字节码生成子类的方式来进行处理。主要是对指定的类生成一个子类,覆盖其中的方法并且实现增强,但是由于采用的方法是继承的方式,因此对于 final 修饰的类和方法,无法被继承并增强。
        3. 选择
          • 如果目标对象实现了接口,默认情况下会采用 JDK 动态代理实现 AOP
          • 如果目标对象实现了接口,可以强制采用 CGLIB 实现 AOP
          • 如果目标对象没有实现接口,必须采用 CGLIB,Spring 会自动在 JDK 动态代理和 CGLIB 之间进行转换。

模板方法模式

  1. 定义

    封装一个操作中的算法骨架,将一些步骤延迟到子类中

  2. 优缺点

    • 优点:封装不可变部分,扩展可变部分;提取公共代码,易于维护;行为由父类控制,子类实现
    • 缺点:不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

适配器模式

  1. 定义

    将一个类的接口转换成客户希望的另一个接口

  2. 使用场景

    • 系统需要使用现有的类,而此类的接口不满足系统的需要
    • 想要建立一个可重复使用的类,用于一些彼此之间没有太大关联的类,包括一些将来可能引进的类一起工作,这些类不一定有一致的接口
    • 通过接口转换,将一个类插入另一个类中
  3. 优缺点

    • 优点:可以让两个互不关联的类一起运行;提高了系统的复用;增加了类的透明度;灵活性好
    • 缺点:过多的适配器的使用,会让系统非常凌乱,不易对系统进行整体的把控;

装饰器模式

  1. 定义

    允许向一个现有的对象添加新的功能,同时又不改变其结构

  2. 优缺点

    • 优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能
    • 多层装饰较为复杂
  3. 使用场景

    • 扩展一个类的功能
    • 动态增加功能,动态撤销

观察者模式

  1. 定义

    一个对象状态改变给其他对象通知的问题,而且要考虑到易用和耦合,保证高效的协作。

  2. 优缺点

    • 优点:观察者和被观察着是抽象耦合的;建立起一套触发机制
    • 缺点:如果一个被观察者同时拥有多个直接或间接的观察者时,通知所有的观察者将会消耗大量的时间;如果观察者和被观察者之间存在循环依赖的话,循环调用可能会导致系统崩溃;观察者模式没有相应的机制让观察者知道所观察的对象是怎么变化的。