设计模式

        设计模式常用的七大原则:

  1. 单一职责原则——一个类应该只负责一项职责。
  2. 接口隔离原则——一个类对应另一个类的依赖应该建立在最小的接口上。
  3. 依赖倒转原则
    1. 高层模块不应依赖于低层模块
    2. 抽象不应该依赖于细节
    3. 面向接口编程(中心思想)
  4. 里氏替换原则——所有引用基类的地方必须能透明的使用子类。
  5. 开闭原则——软件实体对扩展开放,对修改关闭,用抽象构建框架,用实现扩展细节。
  6. 迪米特法则——一个类应该将逻辑封装在内部,不对外泄露。
  7. 合成复用原则——尽量使用合成/聚合方式

        23种设计模式的三种类型:

  1. 创建型模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式;
  2. 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式;
  3. 行为型模式:模板方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式(责任链模式)。

1.单例模式

        单例模式,即采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(通常为静态方法)。单例模式有八种方式:

  1. 饿汉式(静态常量)
  2. 饿汉式(静态代码块)
  3. 懒汉式(线程不安全)
  4. 懒汉式(线程安全,同步方法)
  5. 懒汉式(线程安全,同步代码块)
  6. 双重检查
  7. 静态内部类
  8. 枚举

1.1 饿汉式(静态常量)

  1. 构造器私有化(即不能通过 $new$ 创建实例)
  2. 类内部创建对象
  3. 向外暴露一个静态公共方法
  4. 代码实现
class Singleton {
  private final static Singleton instance = new Singleton();

  private Singleton() {
  }

  public static Singleton getInstance() {
    return instance;
  }
}

优点:写法简单,在类装载时实例化,避免了线程同步问题。
缺点:不是懒加载,可能存在内存资源的浪费。

1.2 饿汉式(静态代码块)

  1. 构造器私有化
  2. 类内部声明私有静态成员
  3. 静态代码块内实例化
  4. $getInstance$ 公共静态方法实现
class Singleton2 {
  private static Singleton2 instance;

  static {
    instance = new Singleton2();
  }

  private Singleton2() {}

  public static Singleton2 getInstance() {
    return instance;
  }
}

优缺点同上。

1.3 懒汉式(线程不安全)

  1. 构造器私有化
  2. 声明静态私有成员
  3. 实现 $getInstance$ 方法,只有使用时才创建对象
class Singleton3 {
  private static Singleton3 instance;

  private Singleton3() {}

  public static Singleton3 getInstance() {
    if (instance == null) instance = new Singleton3();
    return instance;
  }
}

优点:懒加载。
缺点:线程不安全,因为使用 $if$ 判断,如果多个线程同时进入,可能会创建多个实例。
结论:实际开发中不能使用。

1.4 懒汉式(线程安全,同步方法)

在线程不安全的基础上,在 $getInstance$ 方法上添加 $synchronized$ 关键字。

class Singleton4 {
  private static Singleton4 instance;

  private Singleton4() {}

  public static synchronized Singleton4 getInstance() {
    if (instance == null) instance = new Singleton4();
    return instance;
  }
}

优点:懒加载,线程安全
缺点:每个线程都要执行一次 $getInstance$ 方法,但实际上只需执行一次即可。
结论:实际开发中不推荐使用

1.5 懒汉式(线程安全,同步代码块)

将创建实例的过程移入同步代码块中

class Singleton5 {
  private static Singleton5 instance;

  private Singleton5() {}

  public static Singleton5 getInstance() {
    if (instance == null) {
      synchronized (Singleton5.class) {
        instance = new Singleton5();
      }
    }
    return instance;
  }
}

虽然使用了同步代码块,但是仍然处于线程不安全的状态。因为可能存在多个线程进入 $if$ 块中,这时还是会产生多个实例。
结论:实际开发中不能使用

1.6 双重检查

使用 $volatile$ 修饰私有静态成员,在同步代码块内再进行一次空判断。

class Singleton6 {
  private static volatile Singleton6 instance;

  private Singleton6() {}

  public static Singleton6 getInstance() {
    if (instance == null) {
      synchronized (Singleton6.class) {
        if (instance == null) instance = new Singleton6();
      }
    }
    return instance;
  }
}

优点:同时解决了懒加载和线程安全问题
结论:在实际开发中推荐使用

1.7 静态内部类

在外部类进行类装载时,静态内部类不会被装载。同时类装载也是线程安全的。因此可以将创建实例的过程放在静态内部类中实现。

class Singleton7{
  private Singleton7() {}

  public static Singleton7 getInstance() {
    return Singleton7Instance.INSTANCE;
  }

  private static class Singleton7Instance {
    private static final Singleton7 INSTANCE = new Singleton7();
  }
}

优点:利用JVM的特性保证了线程安全,同时实现了懒加载
结论:在实际开发中推荐使用

1.8 枚举

通过枚举实现单例模式。

enum Singleton8 {
  INSTANCE;
  public void method() {}
}

优点:避免了多次创建对象和线程问题
结论:推荐使用

1.9 JDK中的单例模式

$java.lang.Runtime$ 类,使用的是饿汉式

public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    public static Runtime getRuntime() {
        return currentRuntime;
    }

    private Runtime() {}

    // ...
}

1.10 注意事项

  1. 单例模式保证内存中只存在一个对应的对象。对于一些需要频繁创建和销毁的对象,使用单例模式可以提高性能。
  2. 实例化单例类,不能使用 $new$ ,只能使用相应的获取对象的方法。
  3. 使用场景:
    1. 需要频繁创建和销毁的对象
    2. 创建时会消耗过多时间或者资源的对象
    3. 频繁访问或使用的工具类、数据库或文件对象

2. 工厂模式

2.1 简单工厂模式

        简单工厂模式是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪种产品类的实例。即定义一个创建对象的类,由这个类来进行封装实例化对象的行为。

public class SimplePizzaFactory {
  public Pizza createPizza(String orderType) {
    Pizza pizza = null;
    System.out.println("SimplePizzaFactory");
    if (orderType.equals("greek")) {
      pizza = new GreekPizza();
      pizza.setName("GreekPizza");
    } else if (orderType.equals("cheese")) {
      pizza = new CheesePizza();
      pizza.setName("ChessePizza");
    } else if (orderType.equals("pepper")) {
      pizza = new PepperPizza();
      pizza.setName("PepperPizza");
    }
    return pizza;
  }
}

SimplePizzaFactory

        简单工厂模式也叫静态工厂模式,其工厂类内部的 $create$ 方法可以设置为静态方法。

2.2 工厂方法模式

        工厂方法模式定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类进行。

public abstract class OrderPizza {
  abstract Pizza createPizza(String orderType);
}

FactoryMethodPizza

2.3 抽象工厂模式

        抽象工厂模式定义了一个接口用于创建相关或存在依赖的对象集合。从设计层面上看,可以理解为是对简单工厂模式的改进。抽象工厂模式将工厂抽象为两层,抽象工厂和具体实现的工厂子类,即将单个简单工厂扩展为一个工厂集合。

public interface AbsFactory {
  public Pizza createPizza(String orderType);
}

AbstractFactoryPizza

2.4 JDK应用举例

$java.util.Calendar$ 类使用了简单工厂模式

public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {
    // ...
    public static Calendar getInstance()
        {
            return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
        }
    // ...
}

        工厂模式将实例化对象的代码提取出来,放到一个类中进行统一管理,达到依赖关系解耦的目的,从而提高了扩展性和可维护性。在使用工厂模时,创建对象实例不直接使用 $new$ ,而是在工厂方法中使用 $new$ 。相对应的工厂类不应实现具体类,而应实现抽象类或者接口,不要覆盖基类中已经实现的方法。

3. 原型模式

        Java的 $Object$ 类提供了一个 $clone$ 方法,可以复制一个对象。如果一个类需要实现 $clone$ 方法,必须要实现 $Cloneable$ 接口。原型模式是指用原型实例指定创建对象的种类,并且通过拷贝这些原型来创建新对象。新的对象通过调用原型对象的 $clone$ 方法进行创建。

class Sheep implements Cloneable {
    @Override
    protected Object clone() throws CloneNotSupportedException {
      return super.clone();
    }
}

3.1 Spring源码示例

        在Spring框架中配置bean时可以指定 $scope$ 的值为 $prototype$ ,代表通过原型模式创建。

else if (mbd.isPrototype()) {
      var11 = null;

      Object prototypeInstance;
      try {
        this.beforePrototypeCreation(beanName);
        prototypeInstance = this.createBean(beanName, mbd, args);
      } finally {
        this.afterPrototypeCreation(beanName);
      }
      // ...
}

        $AbstractBeanFactory$ 类中的 $doGetBean$ 方法用于获取容器中的 $bean$ 。如果先前设置 $scope = prototype$ ,那么就会进入该 $if$ 块中。块中的 $createBean$ 方法通过原型模式创建对象实例。

3.2 深拷贝

        $clone$ 方法默认是浅拷贝,也就代表如果成员为对象,那么只会复制对象的引用值。在实际开发中,存在着两种实现深拷贝的方式:重写 $clone$ 或者对象序列化。

public class DeepPrototype implements Serializable, Cloneable {
  public String name;
  public DeepCloneableTarget deepCloneableTarget;

  //  1. 重写clone
  @Override
  protected Object clone() throws CloneNotSupportedException {
    Object deep = null;
    deep = super.clone();
    DeepPrototype deepPrototype = (DeepPrototype) deep;
    deepPrototype.deepCloneableTarget = (DeepCloneableTarget) deepCloneableTarget.clone();
    return deepPrototype;
  }

  //  2. 对象序列化(推荐)
  public Object deepClone() {
    DeepPrototype ret = null;
    try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos); ) {
      oos.writeObject(this);
      try (ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
          ObjectInputStream ois = new ObjectInputStream(bais)) {
        ret = (DeepPrototype) ois.readObject();
      } catch (ClassNotFoundException e1) {
        e1.printStackTrace();
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
    return ret;
  }
}

优点:当创建的新对象比较复杂时,可以使用原型模式。不需要重新初始化对象,而且当对象发生变化时,不需要修改。
缺点:需要给每个类都实现一个克隆方法。当对类进行改造时,可能需要修改源码,违反了ocp原则。

4. 建造者模式

        建造者模式,又叫生成器模式。它将复杂对象的建造过程抽象,这个抽象过程的不同实现方法可以构造出不同表现的对象。通过建造者模式,用户不需要知道内部细节,只需要通过指定类型和内容即可构造出复杂对象。
        建造者模式包含四个角色:

  1. $Product$ :即产品角色,指一个具体的产品对象
  2. $Builder$ :即抽象建造者,创建产品对象各个部件的接口(抽象类)
  3. $ConcreteBuilder$ :即具体建造者,是实现接口的对象
  4. $Director$ :即指挥者,使用接口的对象

BuilderPattern

4.1 JDK源码示例

$java.lang.StringBuilder$ 中使用了建造者模式

StringBuilder

  1. $Appendable$ 接口定义了多个 $append$ 方法,均为抽象方法。因此可以将 $Appendable$ 视为 $Builder$ 。
  2. $AbstractStringBuilder$ 实现了 $Appendable$ 中的方法,可以视为 $ConcreteBuilder$ ,即使不能实例化。
  3. $StringBuilder$ 既是 $Director$ ,也是 $ConcreteBuilder$ 。建造方法是在 $AbstractStringBuilder$ 中实现的。

        使用了建造者模式,用户不需要了解内部实现即可根据类型和需求创建出不同的对象。对象创建的过程分为多个方法,使创建过程更加清晰,也能更方便的控制。在建造者模式的基础上,添加新的具体建造者无需修改原有代码,符合开闭原则。
        对于一些差异很大的对象,不适宜使用建造者模式。如果一个对象十分复杂,需要多个建造者。那么如果继续使用建造者模式,会导致系统庞大。因此在这种情况下不应继续使用建造者模式。
        与抽象工厂模式对比,抽象工厂模式不需要关注建造的过程,关注的是由哪个工厂建造;而建造者模式主要关注点就在过程。

5. 适配器模式

        适配器,又叫包装器,用于将某个类的接口转换成客户端期望的另一个接口表示,主要的目的是兼容,让原本不匹配的两个类能够一起工作。适配器模式主要有三类:类适配器模式、对象适配器模式、接口适配器模式。
        用户不能直观地感受到适配器的存在。适配器转换目标对象,用户通过调用经适配器转换后的对象的接口方法,可以间接调用原目标对象的相应方法。被适配的对象称为 $src$ 类,适配后的对象称为 $dst$ 类。

5.1 类适配器模式

        适配器为 $Adapter$ 类,通过继承 $src$ 类,实现 $dst$ 类接口,从而完成适配。

ClassAdapter

        由于Java实行的是单继承机制,因此要求 $dst$ 必须为接口,存在一定的局限性。此外,$src$ 类中的方法都会在 $Adapter$ 类中暴露,增加了一定的成本。但反过来,由于方法暴露,因此也可以重写一部分方法,增加灵活性。

5.2 对象适配器模式

        在类适配器模式的基础上加以修改。$Adapter$ 类不再继承 $src$ 类,而是持有 $src$ 类实例。对象适配器模式是适配器模式中常用的一种。

ObjectAdapter

5.3 接口适配器模式

        接口适配器模式,也称为适配器模式或缺省适配器模式。当不需要全部实现接口提供的方法的时候,可以设计一个抽象类实现该接口,并为方法提供空实现。

InterfaceAdapter

5.4 Spring MVC源码示例

$HandlerAdapter$ 使用了适配器模式

Spring MVC 流程:

  1. 请求发送给 $DispatcherServlet$,
  2. $HandlerMapping$ 处理,找到对应的 $Handler$,
  3. $HandlerMapping$ 返回一个 $ModelAndView$ 对象,
  4. $InternalResourceViewResolve$ 解析对象,找到指定的资源,
  5. Tomcat包装结果,返回给浏览器.

        $Dispatcher$ 的 $doDispatch$ 方法获取对应的 $Adapter$ ,再通过 $Adapter$ 处理相应的 $Controller$ 。

6. 桥接模式

        桥接模式将实现与抽象分到两个层次中,可以独立改变。桥接模式基于类的最小设计原则,主要特点是把抽象和实现分离。

Bridge

        $Phone$ 为抽象类,$Brand$ 为接口,是 $Phone$ 的成员。$Phone$ 和 $Brand$ 分别有对应的实现类。

6.1 JDBC源码示例

        从桥接模式的角度,$Driver$ 就是一个接口,下面的实现类可以是MySQL的,也可以是Oracle的。
        $Driver$ 使用 $DriverManager$ 注册。$DriverManager$ 通过 $getConnection$ 方法获取 $Connection$ 。$Connection$ 有多个实现,由不同的数据库提供商决定。

        使用了桥接模式后,高层部分只需要知道抽象部分和实现部分的接口即可,其余部分由具体业务实现。但同时,桥接模式的引入提高了设计和理解的难度。由于聚合发生在抽象层,因此开发者需要针对抽象层进行设计和编程。桥接模式的使用要求识别出系统变化的两个维度,具有一定局限性。

7. 装饰者模式

        装饰者模式可以将新功能动态地附加到对象上。装饰者模式的主体( $Component$ ) ,也就是被装饰的对象。装饰者模式的包装( $Decorator$ ) ,也就是装饰者。后者继承和聚合前者,中间也可以添加接口作为缓冲层。

Decorator

        使用如上模式之后,购买一份咖啡只需要 $new$ 一个咖啡对象。在咖啡对象的基础上,如果要添加调料,只需要 $new$ 一个调料对象,并传入原有咖啡对象即可。在这个过程中,由于咖啡和调料都继承了 $Drink$ ,因此不需要重新声明。在包装的过程中,可以通过重写方法来返回不同的描述和价格。

public class CoffeeBar {
  public static void main(String[] args) {
    Drink order = new LongBlack();
    System.out.println("order description=" + order.getDescription() + ", cost=" + order.cost());
    order = new Milk(order);
    System.out.println("order description=" + order.getDescription() + ", cost=" + order.cost());
  }
}

7.1 JDK源码示例

$FilterInputStream$ 类就是一个装饰者

  1. $InputStream$ 是被装饰的对象
  2. $FilterInputStream$ 是装饰者,其内部包含一个 $InputStream$ 类型的成员

8. 组合模式

        组合模式又叫部分整体模式,它将对象组合成树状结构来表示整体和部分的层次关系。通过组合模式,用户能够以一致的方式处理单个对象以及组合对象。$Component$ 是组合中对象声明接口,包含所有类共有的默认行为,用于访问和管理子部件。$Leaf$ 是继承了 $Component$ 的叶子节点。$Composite$ 是继承了 $Component$ 的非叶子节点,实现操作子部件的相关方法,用于存储子部件。

Composite

        $Department$ 是 $Leaf$ ,$University$ 和 $College$ 是 $Composite$ 。

8.1 JDK源码示例

$HashMap$ 使用了组合模式

HashMapComposite

  1. $Map$ 是一个接口,可以视为 $Composite$,
  2. $HashMap$ 实现了 $Map$ ,可以视为 $Component$,
  3. $Node$ 是 $HashMap$ 的内部类,可以视为 $Leaf$.

        组合模式能简化操作,用户只需要面对一致的对象而不需要考虑内部结构。组合模式具有较强的扩展性,用户可以随意添加节点。树形结构使得组合模式的遍历十分方便,但反过来如果节点间差异较大,则不适用于组合模式。

9. 外观模式

        外观模式定义了一个高层接口,为子系统中的一组接口提供了一个一致的界面。调用端只需跟该高层接口交互,不需要了解其子系统的内部细节。
        $Facade$ 即外观类,提供统一调用接口,负责将请求代理给适当的子系统对象。$Client$ 即调用者,调用外观接口。子系统集合处理 $Facade$ 指定的请求。

Facade

        影院中的每个设备都有对应的操作方法,在影片放映的不同过程中需要分别调用对应的方法,可以使用 $Facade$ 来进行统一调度。

9.1 MyBatis源码示例

MyBatis中的 $Configuration$ 创建 $MetaObject$ 的过程中使用了外观模式

public MetaObject newMetaObject(Object object) {
    return MetaObject.forObject(object, this.objectFactory, this.objectWrapperFactory, this.reflectorFactory);
}

        外观模式降低了用户对子系统复杂性的感受,使子系统更容易维护。当需要分层设计时,可以使用外观模式来更好的划分层次。但是也不能滥用外观模式,要以利于维护为目的。

10. 享元模式

        享元模式也叫蝇量模式,是运用共享技术有效地支持大量细粒度对象的模式。享元模式常用于底层开发,如数据库连接池等,能够解决重复对象造成的内存浪费问题。享元模式将对象的信息分为内部状态和外部状态两部分。 内部状态是存储在享元对象内且不会因环境而改变的信息,是对象共享的信息。外部状态是对象依赖的信息,会随环境变化而改变。
        $FlyWeight$ 是抽象的享元类,定义对象的内部状态和外部状态。$ConcreteFlyWeight$ 是具体的享元类,实现具体业务。$UnSharedConcreteFlyWeight$ 是不可共享的角色,虽然继承 $FlyWeight$ ,但不会出现在享元工厂。$FlyWeightFactory$ 是享元工厂类,作为池容器,负责创建和返回享元类。

FlyWeight

10.1 JDK源码示例

$java.lang.Integer$ 类中使用了享元模式

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

        $Integer.valueOf$ 方法,在 $-128 \sim 127$ 之间使用享元模式创建。也即代表使用 $valueOf$ 创建的所有处于 $-128 \sim 127$ 的同一大小的对象都是同一引用。

        在系统中的大部分对象状态可以外部化时,可以使用享元模式。如果对象在内存中存在着唯一标识码,可以使用 $HashMap$ 或 $HashTable$ 存储。要注意的是,使用享元模式要分离内部状态和外部状态,还需要一个工厂类加以控制。

11. 代理模式

        代理模式为对象提供一个代理对象,控制对该对象的访问。其他对象可以通过访问代理对象的方式访问目标对象。被代理的对象可以是远程对象、创建开销大的对象或者需要安全控制的对象。代理模式主要有三种形式:静态代理、动态代理和Cglib代理。

11.1 静态代理

        定义一个接口或者父类,然后令被代理对象与代理对象一起实现相同接口或者继承相同父类。

StaticProxy

        通过静态代理,我们能够在不修改源对象的情况下对其进行扩展,但是由于要求实现同一接口,因此一旦接口改变,就需要同时修改两个对象,增加了维护难度。

11.2 动态代理

        动态代理对象不需要实现接口,但是目标对象需要实现接口。动态代理通过JDK提供的API实现,动态地在内存中构建代理对象。动态代理也叫JDK代理、接口代理。

DynamicProxy

        $ProxyFactory.getProxyInstance$ 接受一个对象,利用反射机制返回一个代理对象,再通过该代理对象调用目标对象方法。

public class ProxyFactory {
  private Object target;

  public ProxyFactory(Object target) {
    this.target = target;
  }

  public Object getProxyInstance() {
    return Proxy.newProxyInstance(
        target.getClass().getClassLoader(),
        target.getClass().getInterfaces(),
        new InvocationHandler() {
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return method.invoke(target, args);
          }
        });
  }
}
public static Object new ProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
  1. $loader$ 为目标对象使用的类加载器,有固定获取方法
  2. $interfaces$ 为目标对象实现的接口类型,使用泛型方式确定
  3. $h$ 为事件处理类,当执行目标对象方法时,会触发事件处理器方法,即将当前事件方法作为参数传入

11.3 Cglib代理

        静态代理和动态代理都要求目标对象实现一个接口,而当目标对象没有实现接口时,Cglib代理允许你通过其子类实现代理,因此也被叫做子类代理。Cglib代理可以在运行期扩展以及实现Java接口,被许多AOP框架所使用,其底层通过字节码处理框架ASM来转换字节码并生成新的类。使用Cglib代理时,要注意目标类不能为 $final$ 。

public class ProxyFactory implements MethodInterceptor {
  private Object target;

  public ProxyFactory(Object target) {
    this.target = target;
  }

  public Object getProxyInstance() {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(target.getClass());
    enhancer.setCallback(this);
    return enhancer.create();
  }

  @Override
  public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
      throws Throwable {
    return method.invoke(target, objects);
  }
}

        $getProxyInstance$ 方法为 $target$ 创建一个代理。$Enhancer$ 为一个工具类,设置目标对象为其父类,动态构建子类对象。重写 $intecept$ 方法实现被代理对象的方法调用。

11.4 代理模式的变体

  1. 防火墙代理——内网通过代理穿透防火墙访问公网
  2. 缓存代理——请求资源时先检查代理,如果代理没有再从数据库获取
  3. 远程代理——可以将远程对象当作本地对象使用
  4. 同步代理——在多线程中负责同步工作

12. 模板方法模式

        模板方法模式又叫模板模式,在一个抽象类中公开定义了模板方法,其子类可以重写一些方法,但调用应按照抽象类中定义的方法进行。

TemplateMethod

        在模板方法模式的父类中,我们可以定义一个方法,称为钩子,默认为空,子类可以视情况覆盖。

12.1 Spring源码示例

Spring IOC容器初始化时用到模板方法模式

        $AbstractApplicationContext.refresh$ 方法是一个模板方法,其内部间接调用抽象方法 $refreshBeanFactory$ 和 $getBeanFactory$ ,其子类继承并实现抽象方法。$PostProcessBeanFactory$ 和 $onRefresh$ 是钩子方法,在父类中为空实现。

13. 命令模式

        在软件设计中,我们经常需要发送请求,但是并不知道请求的接收者和被请求的操作,这时只需要在程序运行时指定即可。命令模式允许我们消除请求发送者和接收者间的耦合,实现更灵活的调用。在命令模式中,请求被封装为一个对象,表示不同的请求。命令模式支持撤销操作。
        $Invoker$ 是调用者,$Receiver$ 是被调用者,$ConcreteCommand$ 实现了 $Command$ 接口,持有接受对象。

Command

        $LightReceiver$ 负责控制灯的开关,$Command$ 持有 $LightReceiver$ ,调用其方法,$RemoteController$ 通过 $Command$ 实现对灯的控制。

13.1 Spring源码示例

$JdbcTemplate$ 中使用了命令模式

  1. $JdbcTemplate$ 类中存在 $query$ 方法,
  2. $query$ 方法中定义了一内部类 $QueryStatementCallback$,
  3. $QueryStatementCallback$ 实现了 $StatementCallback$ 接口,
  4. 在 $query$ 方法结束时调用了 $execute$ 方法并传入了 $(StatementCallback)$ $new$ $QueryStatementCallback$.

        $JdbcTemplate$ 通过 $query$ 方法,使其成为了 $Invoker$ 。$StatementCallback$ 和 $QueryStatementCallback$ 是 $Command$ 和 $ConcreteCommand$ 的关系,同时也充当着 $Receiver$。

        命令模式将请求对象与被请求对象解耦,请求者和被请求者不需要知道对方是谁即可调用。通过命令队列,可以轻松的实现多线程调用。但是对于有很多的命令的操作,容易产生过多的命令类,增加复杂度。命令模式的使用还可以方便实现命令的撤销。通过引入空命令,可以省去判空语句。

14. 访问者模式

        访问者模式封装一些作用于某种结构的元素的操作,可以在不改变数据结构的前提下定义新操作。访问者模式将数据结构与数据操作进行分离,解决其耦合性问题。访问者模式通过向被访问类里添加一个供外部访问的接口。当需要对一个结构进行很多种不同的操作,又要避免对结构造成很大影响时,可以使用访问者模式。
        $Visitor$ 是一个抽象类,$ConcreteVisitor$ 是其实现。$ObjectStructure$ 是一数据结构,可以枚举其元素,提供一高层接口,使访问者可以访问其元素。$Element$ 为接口,定义了一 $accept$ 方法,接受一访问者对象,$ConcreteElement$ 是其实现。

Visitor

        双分派的使用使得无论两种类怎么变化都不会影响另一种类,例如我们要添加新的动作时只需要创建新的 $Action$ 的实现即可,要细化 $Person$ 时也只需要修改其实现类即可。

        访问者模式的使用使得程序的灵活性更高,适用于结构稳定,功能统一的系统。但是由于具体元素向访问公开细节,是迪米特法则所不推荐的。同时访问类也违反了依赖倒转原则,抽象类依赖于具体元素。

15. 迭代器模式

        迭代器模式提供一种遍历结合模式的接口,允许通过一个一致的方法,在不需要暴露内部结构的情况下对集合进行遍历。
        $Iterator$ 是一个接口,提供了 $hasNext$, $next$, $remove$ 方法,$ConcreteIterator$ 实现了 $Iterator$ 。$Aggregate$ 是一个聚合接口,将其实现和客户端解耦,$ConcreteAggregate$ 是其具体实现,持有对象的集合。

Iterator

        $College$ 接口中声明了 $iterator$ 方法,通过调用该方法即可获得对应的迭代器。$Iterator$ 的实现中持有对应的集合,通过对不同的类创建不同的实现,可以做到在不暴露内部实现的情况下实现对集合的遍历。

15.1 JDK源码示例

  1. $List$ 接口声明了 $iterator$ 方法
  2. $ArrayList$ 实现了 $List$ 接口,$iterator$ 方法返回一个 $Itr$ 对象
  3. $Itr$ 实现了 $Iterator$ 接口

        迭代器模式的使用使得客户端不需要知道内部细节即可实现对集合的遍历。迭代器的使用将管理对象集合和遍历对象集合的功能分开,满足了单一职责原则。但是如果存在很多聚合类时,每个类都需要声明一个对应的迭代器实现类,增加了复杂度。

16. 观察者模式

        观察者模式定义了一个 $Subject$ 接口,接口中的 $registerObserver$ 方法可以注册新的观察者,$removeObserver$ 方法移除观察者,$notifyObservers$ 方法通知所有观察者,且可以根据需要改变推送策略。$Observer$ 接口内有一个 $update$ 方法,用于更新数据。观察者模式是对象间多对一依赖的一种设计模式,被依赖的对象是 $Subject$ ,依赖对象是 $Observer$ ,$Observer$ 通过 $Subject$ 通知的数据进行更新。

Observer

16.1 JDK源码示例

$Observable$ 类使用了观察者模式

public class Observable {
    private Vector<Observer> obs;

    public Observable() {
        obs = new Vector<>();
    }

    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }

    public void notifyObservers(Object arg) {
        Object[] arrLocal;
        synchronized (this) {
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }
        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }

    // ...
}
public interface Observer {
    void update(Observable o, Object arg);
}

        $Observable$ 并没有使用接口,而是直接声明一个具体类,在类中存在着注册、移除和通知观察者的方法,是 $Subject$ 的角色。$Observer$ 接口及其实现充当观察者的角色,提供了 $update$ 方法。

17. 中介者模式

        中介者模式是用一个中介对象封装一系列对象间的交互。在中介者模式下,交互的对象间不需要显式地相互作用,起到了松耦合作用,从而使代码更容易维护。
        $Colleague$ 是抽象同事类,$ConcreteColleague$ 是其实现。同事类之间互不了解,只需要依赖中介者对象即可。$Mediator$ 是抽象中介者,$ConcreteMediator$ 是其实现。中介者对象通过一个集合来接受同事的消息,并完成指定操作。

Mediator

        $Colleague$ 中持有 $Mediator$ 对象,可以通过 $Mediator.register$ 方法将本身注册到中介者内。$Mediator$ 内使用 $HashMap$ 实现对 $Colleague$ 的管理。当接受到消息时,$Mediator$ 类中对应的方法会根据消息的类型进行不同的操作。

        使用中介者模式,可以将原先多个类相互耦合形成的网状结构分解成星型结构,减少了类间的依赖和耦合,符合迪米特原则。但要注意的是,在中介者模式中,中介者承担了很多责任,一旦中介者出现了问题,整个系统都会受到影响,如果设计不当,中介者很复杂,那么整个系统都会受到影响。

18. 备忘录模式

        备忘录模式可以在不破坏封装性的情况下捕获并记录一个对象的内部状态。通过备忘录模式,一个对象可以恢复到一个以前的状态。

public class Originator {
  private String state;

  public Memento saveState() {
    return new Memento(state);
  }

  public void readState(Memento memento) {
    state = memento.getState();
  }

  public String getState() {
    return state;
  }

  public void setState(String state) {
    this.state = state;
  }
}
public class Memento {
  private String state;

  public Memento(String state) {
    this.state = state;
  }

  public String getState() {
    return state;
  }
}
public class Caretaker {
  private List<Memento> mementos = new ArrayList<>();

  public void add(Memento memento) {
    mementos.add(memento);
  }

  public void remove(Memento memento) {
    mementos.remove(memento);
  }

  public Memento get(int index) {
    return mementos.get(index);
  }
}

        $Originator$ 是待保存的对象,$Mementor$ 是备忘录对象,$Caretaker$ 管理备忘录对象。

        备忘录提供了一种可以恢复之前状态的机制,实现了信息的封装,用户在恢复时不需要关注状态的细节。但是如果类内成员过多,那么每次备份都会消耗大量时间,还存在着资源浪费。为了节约内存,备忘录模式也可以与原型模式一起使用。

19. 解释器模式

        解释器模式对于给定的表达式,定义了其文法的一种表示,利用一个解释器来对表达式进行解释。
        $Context$ 是环境角色,包含解释器之外的全局信息。$AbstractExpression$ 是抽象表达式,声明一个抽象的解释操作,为抽象语法树中所有节点所共享。$TerminalExpression$ 实现 $AbstractExpression$ 为终结符表达式,实现与终结符相关的解释操作。$NonTerminalExpression$ 实现 $AbstractExpression$ 为非终结符表达式,实现与非终结符相关的解释操作。

Interpreter

        $Expression$ 是一个抽象解释器,内包含一个 $interpreter$ 方法,其实现类分别用于解释不同的字符:数字和加减号。

19.1 Spring源码示例

$SpelExpressionParser$ 类中使用了解释器模式

  1. $Expression$ 接口内包含 $getValue$ 方法,
  2. $SpelExpressionParser$ 间接实现了 $ExpressionParser$,
  3. $ExpressionParser.parseExpression$ 方法会根据不同的情况返回相应的 $Expression$.

        当需要解释执行一个语言时,如果可以用语法树表示句子,那么就可以使用解释器模式。解释器模式相比一般模式具有良好的扩展性,但由于其采用递归调用方法,可能会导致调试复杂,效率降低。

20. 状态模式

        状态模式用于多种状态转换时对外输出不同的问题。状态间可以相互转换,行为与状态一一对应。当对象的状态改变时,其行为也跟着改变,从外部来看类似于变为另一个类。
        $Context$ 为环境角色,维护状态实例。$State$ 为接口,封装与 $Context$ 的一个特定接口相关的操作,$ConcreteState$ 为其实现。

State

        $Activity$ 和 $State$ 之间互相持有,$Activity$ 内的方法调用 $State$ 的方法进行状态对应的行为,$State$ 内的方法调用 $Activity$ 的方法进行状态切换。

        状态模式具有很强的可读性,将状态和行为封装在一起,方便扩展,而且避免了 $if-else$ 带来的问题,符合开闭原则。但相对应的,由于每个状态都对应者一个类,可能会产生很多个状态类,从而增加了维护难度。

21. 策略模式

        策略模式定义了一些算法,分别封装起来,算法间可以互相替换。

Strategy

        $Duck$ 持有 $FlyBehavior$ ,其子类继承 $Duck$ 时通过设置 $FlyBehavior$ ,可以做出不同的行为。

21.1 JDK源码示例

$Arrays$ 类中使用了策略模式

$Arrays.sort$ 方法可以选择传入 $Comparator$ 类来指定排序策略

        策略模式多用组合,少用继承,使用行为类进行组合而不是行为继承,符合开闭原则。

22. 职责链模式

        职责链模式,又叫责任链模式,为请求创建了一个接收者对象的链,对请求的发送者和接收者进行解耦。在职责链模式中,通常每个接收者都包含对另一个接收者的引用,在当前接收者不能处理时,会将请求传递给下一个接收者。

ResponsibilityChain

22.1 SpringMVC源码示例

$HandlerInterceptor$ 类中使用了职责链模式

  1. $DispatcherServlet.doDispatch$ 方法中获取 $HandlerExecutionChain$ 对象,
  2. $HandlerExecutionChain.applyPreHandle$ 方法获取 $HandlerInterceptor$,
  3. $HandlerInterceptor.preHandler$ 方法被调用,
  4. $applyPreHandler$ 方法中调用了 $triggerAfterCompletion$ 方法,
  5. $triggerAfterCompletion$ 方法调用了 $HandlerInterceptor.afterCompletion$ 方法,
  6. $HandlerExecutionChain.applyPostHandle$ 方法再次获取 $HandlerInterceptor$,
  7. $HandlerInterceptor.postHandler$ 方法被调用.

        职责链模式将请求和处理分开,实现了解耦,提高了系统的灵活性。但是性能会受到影响,特别是如果链很长,因此最好设置一个最大节点数加以限制。由于采用了类似于递归的方式,因此也不利于调试。

设计模式