设计模式万字详解

201 阅读26分钟

一、设计模式概述

设计模式

1. 定义

  • 从许多优秀软件系统中总结出的成功的可复用的解决方案。

2. 基本要素

  • 名称:高度概括其本质;
  • 问题:何时使用,在什么环境下使用;
  • 方案:设计的组成成分、相互关系及各自职责和协作方式;
  • 效果:灵活性、扩充性、复用性。

设计原则

1. 面向抽象原则

  • 抽象类:
    • 可以有 abstract 或非 abstract 方法; 
    • 不能用 new 运算符创建对象;
    • 非抽象类继承抽象类必须重写抽象类的abstract方法,且去掉 abstract 修饰符; 
    • 抽象类作为上转型对象(虽然抽象类不能用new 创建对象,但可以让抽象类声明的对象成为其子类的上转型对象,并调用子类重写方法)。
  • 接口:
    • 只能有 public 权限的 abstract 方法,不能有非 abstract方法;
    • 一个类实现接口,必须重写接口的 abstract 方法;
    • 接口回调(可以把实现接口的类的对象的引用赋给 该接口声明的接口变量中)。

2. 开闭原则

  • 设计对扩展开放,对修改关闭;
  • 用抽象构建框架,用实现扩展细节。

3. 多用组合少用继承

  • 继承复用:
    • 优点:
      • 子类可以重写父类方法。
    • 缺点:
      • 无法在运行期间改变从父类继承的方法行为;
      • 子父类强耦合关系,父类改变必导致子类改变;
      • 白盒复用 (父类的内部细节对子类是可见的)。
  • 组合复用:
    • 优点:
      • 对象与所包含对象属于弱耦合关系;
      • 当前对象可在运行时动态指定所包含对象;
      • 黑盒复用 (当前对象只能委托所包含的对象调用方法,当前对象所包含对象的方法的细节当前对象是不可见的)。
    • 缺点:
      • 导致系统对象过多;
      • 为组合多个对象,必须仔细对接口定义。

4. 高内聚低耦合原则

  • 利于类的维护。

5. 单一职责原则

  • 一个类只负责一项职责,不要存在多于一个导致类变更的原因。

6. 里氏替换原则

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法;
  • 子类中可以增加自己特有的方法;当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松;
  • 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

7. 依赖倒置原则/面向接口编程

  • 高层模块不依赖低层模块,二者都应该依赖其抽象;
  • 抽象不应该依赖细节;
  • 细节应该依赖抽象。

8. 接口隔离原则

  • 使用多个专门的接口来替代一个统一的接口;
  • 一个类对另一个类的依赖应建立在最小的接口上。

9. 迪米特法则

  • 一个类对自己依赖的类知道的越少越好。
  • 即,对于被依赖的类,无论逻辑多么复杂,尽量将逻辑封装在类的内部。

设计模式分类

  • 创建型模式

    设计对象实例化,不让用户依赖于对象的创建或排列方式,避免用户直接用new 创建对象。
    共五种:工厂方法模式、抽象工厂模式、单例模式;建造者模式、原型模式。

  • 行为型模式

    合理设计对象之间相互通信,为对象分配职责,让设计富有弹性,易维护、易复用。
    共十一种:命令模式、迭代器模式、观察者模式、备忘录模式、状态模式、模板方法模式;策略模式、责任链模式、访问者模式、中介者模式、解释器模式。

  • 结构型模式

    组合类和对象形成更大的结构。
    和类有关的结构型涉及合理使用继承机制;
    和对象有关的结构型涉及使用用对象组合机制。
    共七种:适配器模式、装饰模式、外观模式;代理模式、桥接模式、组合模式、享元模式。

UML类图

  • 包括三层:名字层、属性层(变量层)、方法层(操作层)。

  • 四种关系:

    • 泛化关系
      即继承(表示方法:实线+三角形)。
    • 关联关系
      A 中变量是用 B 类(接口)来声明的变量(表示方法:实线的箭头)。
    • 依赖关系
      A 中某个方法参数是用 B 类来声明的变量或某个方法返回的数据类型是 B 型(表示方法:虚线的箭头)。
    • 实现关系
      即实现接口(表示方法:虚线+三角形)。

下面从**定义、分类、别名、优点、缺点、适用场景、UML类图、代码实现 等方面具体介绍各个设计模式。

二、工厂方法模式

1、定义

  •  使一个类的实例化延续到其子类,定义一个用于创建对象的接口,让子类决定实例化哪一个类。
    (该模式关键是在一个接口或抽象类中定义一个抽象方法,该方法返回某个类的子类的实例,让子类通过重写抽象方法返回子类的实例。)

2、分类&别名

  • 分类:创建型模式
  • 别名:虚拟构造

3、优点&缺点

  • 优点:

    • 使用户与子类解耦;
      使用户不必知道它所使用的对象是怎样被创建的,只需知道该对象有哪些方法;
      满足开闭原则。
  • 缺点:

    • 创建的类多。

4、适用场景

  • 用户需要该类的子类的实例,但不希望与该类的子类形成形成耦合;
  • 用户需要该类的子类的实例,但用户不知道该类有哪些子类可用。

5、UML类图

图片

  • 四种角色:
    • 抽象产品 Product:定义具体产品必须实现的方法。
    • 具体产品 ConcreteProduct:继承抽象产品类或者实现抽象产品接口,工厂类所创建的对象就是具体产品的实例。
    • 工厂构造者 Creator:定义一个称作工厂方法的抽象方法,该方法返回具体产品类的实例。
    • 具体工厂构造者 ConcreteCreator:重写工厂方法,使该方法返回具体产品的实例。
  • 总结:
    • 一个抽象产品类,可以派生出多个具体产品类。
    • 一个抽象工厂类,可以派生出多个具体工厂类。
    • 每个具体工厂类只能创建一个具体产品类的实例。

6、代码实现

//step1. 抽象产品 Product:定义具体产品必须实现的方法
abstract class Laptop {
  protected String mark;
  public abstract String getId();
}
//step2. 具体产品 ConcreteProduct:继承抽象产品类或者实现抽象产品接口
class LenovoLaptop extends Laptop {
  LenovoLaptop() {
    mark = "Lenovo";
  }
  public String getId() {
    return "使用" + mark + "牌子的笔记本";
  }
}
class AppleLaptop extends Laptop {
  AppleLaptop() {
    mark = "Apple";
  }
  public String getId() {
    return "使用" + mark + "牌子的笔记本";
  }
}
//step3. 构造者 Creator:定义一个称作工厂方法的抽象方法,该方法返回具体产品类的实例
abstract class LaptopFactory {
  LaptopFactory() {
    System.out.println("生产了一台" + getLaptop().mark +"牌子的笔记本...");
  }
  public abstract Laptop getLaptop();
}
//step4. 具体构造者 ConcreteCreator:重写工厂方法,使该方法返回具体产品的实例
class LenovoFactory extends LaptopFactory {
  public Laptop getLaptop() {
    return new LenovoLaptop();
  }
}
class AppleFactory extends LaptopFactory {
  public Laptop getLaptop() {
    return new AppleLaptop();
  }
}
//step5. Demo
public class TestAwen {
  public static void main(String[] args) {
    Laptop laptop;
    LaptopFactory LenovoFactory = new LenovoFactory();
    laptop = LenovoFactory.getLaptop();
    System.out.println(laptop.getId());
    LaptopFactory appleFactory = new AppleFactory();
    laptop = appleFactory.getLaptop();
    System.out.println(laptop.getId());
  }
}

运行结果截图:

图片

三、抽象工厂模式

1、定义

  • 提供一个创建一系列或相互依赖对象的接口,而无须指定它们具体的类。

2、分类&别名

  • 分类:创建型模式
  • 别名:配套

3、优点&缺点

  • 优点:
    • 为用户创建一系列相关的对象,使用户和创建这些对象的类脱耦;
    • 方便为用户创建一系列对象;(用户使用不同的具体工厂就能得到一组相关的对象,同时也能避免用户混用不同系列中对象。)
    • 满足开闭原则。(可随时增加“具体工厂”为用户提供一组相关对象。)
  • 缺点:
    • 不满足开闭原则。(增加新的产品等级结构麻烦,需修改代码。)

4、适用场景

  • 希望用户与创建对象的类脱耦;(系统需为用户提供多个对象,但不希望用户直接用 new 运算符实例化这些对象。)
  • 系统需为用户提供多个相关的对象,以便用户联合使用它们,但又不需要用户来决定这些对象是如何关联的;
  • 系统需对用户提供一系列对象,但只需要用户知道这些对象有哪些方法可用,不需要用户知道这些对象的创建过程。

5、四种角色

  • 抽象产品 Product:负责定义具体产品必须实现的方法
  • 具体产品 ConcreteProduct
  • 抽象工厂 AbstractFactory:负责定义若干个抽象方法
  • 具体工厂 ConcreteFactory

6、总结

  • 多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。
  • 一个抽象工厂类,可以派生出多个具体工厂类。
  • 每个具体工厂类可以创建多个具体产品类的实例。

7、工厂方法模式与抽象工厂模式区别

  • 工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。

  • 工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。

四、单例模式

1、定义

  • 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
  • (其关键是将类的构造方法设置为 private 权限,保证任何其他类无法使用单件类来创建对象;并提供一个返回它唯一实例的类方法(static 方法)。)

2、分类&别名

  • 分类:创建型模式
  • 别名:单件模式

3、优点

  • 单件类的唯一实例由单件类本身控制,可以很好的控制其他类用户何时访问它;
  • 缩小命名空间,避免命名污染;
  • 允许单例有子类;
  • 允许可变数目的实例。

4、适用场景

  • 当系统需要某个类只能有一个实例。

5、UML类图

图片

6、饿汉式&懒汉式

  • 饿汉式:在程序启动或单件类被加载时,单件模式实例就已经被创建。
  • 懒汉式:当程序第一次访问单件模式实例才能进行创建。

7、代码实现

// 饿汉式:在程序启动或单件类被加载时,单件模式实例就已经被创建。
public class Singleton {
  private static Singleton uniqueInstance = new Singleton();
  private Singleton() {}
  public static Singleton getInstance() {
    return uniqueInstance;
  }
}

// 懒汉式:当程序第一次访问单件模式实例才能进行创建。
public class Singleton {
  private static Singleton uniqueInstance;
  private Singleton() {}
  // 加synchronized 线程安全,在多线程下不能正常工作
  // 不加synchronized 线程不安全,效率低,一般不需要同步
  public static synchronized Singleton getInstance() {
    if (uniqueInstance == null)
      uniqueInstance = new Singleton();
  }
}

// 得到单件类的唯一实例:
Singleton s = Singleton.getInstance()

五、命令模式

1、定义

  • 将请求封装为一个对象,使用户可用不同请求对客户进行参数化。
  • (处理对象请求另一个对象调用其方法完成某项任务。其核心是使用命令对象来封装方法调用。)

2、分类&别名

  • 分类:行为型模式
  • 别名:动作、事物

3、优点&缺点

  • 优点:
    • 彻底消除请求者与接收者的耦合;
    • 满足开闭原则;
    • 可记录日志;
    • 可对请求者请求进行排队。
  • 缺点:
    • 可能导致某些系统有过多具体类。

4、适用场景

  • 需要在不同时刻指定、排列和执行请求;

  • 需要提供撤销操作;

  • 需要支持宏操作。(执行一个宏命令相当于执行了很多具体命令)

    撤销的两种方式:

  • 1.反操作(命令模式);
  • 2.ctr + Z 存储恢复(备忘录模式)。

5、UML类图(模拟小电器实验)

图片

  • 角色:
    • 请求者 Invoker: 包含 Command 接口变量的实例。
    • 命令 Command: 封装请求的若干个方法,比如 execute()、undo()等方法。
    • 具体命令 ConcreteCommand: 实现命令接口。

6、代码实现

//step1. 请求者 Invoker: 包含 Command 接口变量的实例
public class Invoker {
  public Command command;
  public void setCommand(Command command) {
    this.command = command;
  }
  public void executeCommand() {
    command.execute();
  }
}
//step2. 命令 Command: 封装请求的若干个方法,比如 execute()、undo()等方法
public interface Command {
  public abstract void execute();
}
//step3. 具体命令 ConcreteCommand: 实现命令接口
public class OnLight implements Command {
  public Light light;
  OnLight(Light light) {
    this.light = light;
  }
  public void execute() {
    light.on();
  }
}
public class OffLight implements Command {
  public Light light;
  OffLight(Light light) {
    this.light = light;
  }
  public void execute() {
    light.off();
  }
}
public class OnCamera implements Command {
  public Camera camera;
  OnCamera(Camera camera) {
    this.camera = camera;
  }
  public void execute() {
    camera.on();
  }
}
public class OffCamera implements Command {
  public Camera camera;
  OffCamera(Camera camera) {
    this.camera = camera;
  }
  public void execute() {
    camera.off();
  }
}
//step4. Demo
public class TestAwen {
  public static void main(String[] args) {
    Invoker invoker = new Invoker();
    Command onlight = new OnLightCommand(new Light());
    invoker.setCommand(onlight);
    invoker.executeCommand();
    Command offlight = new OffLightCommand(new Light());
    invoker.setCommand(offlight);
    invoker.executeCommand();
    Command oncamera = new OnCameraCommand(new Camera());
    invoker.setCommand(oncamera);
    invoker.executeCommand();
    Command offcamera = new OffCameraCommand(new Camera());
    invoker.setCommand(offcamera);
    invoker.executeCommand();
  }
}

六、观察者模式

1、定义

  • 一对多的依赖关系,当一个对象的状态发生变化时,所有依赖它的对象得到通知并被自动更新。
  • (是关于多个对象想知道一个对象中数据变化情况。)

2、分类&别名

  • 分类:行为型模式
  • 别名:依赖、发布订阅

3、推数据&拉数据

  • 推数据方式:指具体主题将变化后的数据全部交给具体观察者;
  • 拉数据方式:指具体主题不将变化后的数据交给具体观察者,而是提供了获得数据的方法,可以调用具体主题提供的方法得到数据。(处理多主题应采用拉数据方式。)

4、适用场景

  • 当一个对象数据更新需要通知其他对象,但又不希望与其他对象形成紧耦合;
  • 当一个对象数据更新时,这个对象不知道具体有多少对象需要更新。

5、UML类图

图片

  • 四种角色:
    • 主题 Subject:规定具体主题需实现的方法,比如:添加、 删除、通知观察者更新数据。
    • 观察者 Observer:观察者规定更新数据的方法。
    • 具体主题 ConcreteSubject:使用一个集合(如 ArrayList)存放观察者的引用。以便数据变化时通知观察者。
    • 具体观察者 ConcreteObserver:存放具体主题引用的接口变量,以便具体主题将自己的引用添加到主题的集合中,使自己成为它的观察者或删除不成为其观察者。

6、代码实现

import java.util.ArrayList;

//step1. 主题 Subject:规定具体主题需实现的方法,比如:添加、 删除、通知观察者更新数据
interface Subject {
  public void addObserver(Observer o);
  public void deleteObserver(Observer o);
  public void notifyObservers();
}
//step2. 观察者 Observer:观察者规定更新数据的方法
interface Observer {
  public void update();
}
//step3. 具体主题 ConcreteSubject:使用一个集合(如 ArrayList)存放观察者的引
//       用。以便数据变化时通知观察者
class ConcreteSubject implements Subject{
  ArrayList<Observer> observers = new ArrayList<Observer>();
  public void addObserver(Observer o) {
    if(!(observers.contains(o)))
      observers.add(o);
  }
  public void deleteObserver(Observer o) {
    if(observers.contains(o))
      observers.remove(o);
  }
  public void notifyObservers() {
    for(int i=0; i<observers.size(); i++) {
      Observer observer = observers.get(i);
      observer.update();
    }
    // 或者
    // for(Observer observer:observers)
    // observer.update();
  }
}
//step4. 具体观察者 ConcreteObserver:存放具体主题引用的接口变量,以便具体主题
//       将自己的引用添加到主题的集合中,使自己成为它的观察者或删除不成为其观察者
class ConcreteObserver1 implements Observer{
  Subject subject;
  ConcreteObserver1(Subject subject) {
    this.subject = subject;
    subject.addObserver(this);
  }
  public void update() {
    System.out.println("Concrete Observer 1");
  }
}
class ConcreteObserver2 implements Observer{
  Subject subject;
  ConcreteObserver2(Subject subject) {
    this.subject = subject;
    subject.addObserver(this);
  }
  public void update() {
    System.out.println("Concrete Observer 2");
  }
}
//step5. Demo
public class TestAwen {
  public static void main(String[] args) {
    ConcreteSubject subject = new ConcreteSubject();
    ConcreteObserver1 observer1 = new ConcreteObserver1(subject);
    ConcreteObserver2 observer2 = new ConcreteObserver2(subject);
    subject.notifyObservers();
  }
}

运行结果截图:

图片

七、装饰模式

1、定义

  • 动态给对象添加额外的职责。
  • (动态的扩展一个对象的功能,不需要改变原始类代码,其模式比生成子类更灵活。)

2、分类&别名

  • 分类:结构型模式
  • 别名:包装器

3、优点&缺点

  • 优点:
    • 被装饰者与装饰者是松耦合关系;(由于装饰仅依赖于抽象组件,因此具体装饰只知道它装饰的对象是抽象组件某个子类的实例。)
    • 满足开闭原则;
    • 可使用多个具体装饰来装饰具体组件的实例。
  • 缺点:
    • 将产生很多小对象,一定程度影响程序,比继承更易出错,排错困难。

4、适用场景

  • 希望动态增加某个对象功能而不影响该类的其他对象;
  • 采用继承增强对象功能不利于系统扩展和维护。

5、UML类图

图片

  • 四种角色:
    • 抽象组件 Component:定义被装饰者进行装饰的方法。
    • 具体组件 ConcreteComponent:被装饰者 (定义成 final 类,是为了排除继承,动态实现)。
    • 装饰 Decorator:是抽象组件的子类。包含抽象组件声明的变量以保存被装饰者的使用。
    • 具体组件 ConcreteDecorator:装饰者。

6、代码实现

//step1. 抽象组件 Component:定义被装饰者进行装饰的方法
abstract class Component {
  public abstract void method();
}
//step2. 具体组件 ConcreteComponent:被装饰者 (定义成 final 类,是为了排除继承,动态实现)
final class ConcreteComponent extends Component{
  public void method() {
    System.out.println("method");
  }
}
//step3. 装饰 Decorator:是抽象组件的子类。包含抽象组件声明的变量以保存被装饰者的
abstract class Decorator extends Component{
  protected Component component;
  public Decorator(Component component) {
    this.component = component;
  }
}
//step4. 具体组件 ConcreteDecorator:装饰者
class ConcreteDecorator extends Decorator{
  public ConcreteDecorator(Component component) {
    super(component);
  }
  public void method() {
    component.method();
    this.othermethod();
  }
  private void othermethod() {
    System.out.println("other method");
  }
}
//step5. Demo
public class TestAwen {
  public static void main(String[] args) {
    Component concreteComponent, concreteDecorator;
    concreteComponent = new ConcreteComponent();
    concreteDecorator = new ConcreteDecorator(concreteComponent);
    concreteDecorator.method();
  }
}

运行结果截图:

图片

八、适配器模式

1、定义

  • 转换匹配,功能复用。使原本由于接口不兼容而不能一起工作的那些类可以一起工作,将一个类的接口转换成客户希望的另一个接口。

2、分类&别名

  • 分类:结构型模式
  • 别名:包装器

3、优点

  • 目标与被适配者完全解耦;
  • 满足开闭原则。

4、适用场景

  • 想使用已经存在的类,但该类所实现的接口和当前出现所使用的接口不一致。
  • 想创建一个可以复用的类,且该类可以与不相关的类协同工作。

5、UML类图

图片

  • 角色:
    • 目标 Target:客户想使用的接口。
    • 被适配者 Adaptee:该接口需要适配。
    • 适配者 Adapter:实现目标接口,且包含被适配者的引用(对被适配者接口与目标接口进行适配)。

6、代码实现

//step1. 目标 Target:客户想使用的接口
interface Target {
  public abstract void methodA();
}
//step2. 被适配者 Adaptee:该接口需要适配
interface Adaptee {
  public abstract void methodB();
}
class ConcreteAdaptee implements Adaptee {
  public void methodB() {
    System.out.println("method B");
  }
}
//step3. 适配者 Adapter:实现目标接口,且包含被适配者的引用(对被适配者接口与目标接口进行适配)
class Adapter implements Target {
  Adaptee adaptee;
  Adapter(Adaptee adaptee) {
    this.adaptee = adaptee;
  }
  public void methodA() {
    adaptee.methodB();
  }
}
//step4. Demo
public class TestAwen {
  public static void main(String[] args) {
    Adaptee adaptee = new ConcreteAdaptee();
    Target target = new Adapter(adaptee);
    target.methodA();
  }
}

运行结果截图:

图片

九、外观模式

1、定义

  • 定义一个高层接口,使子系统更易使用,提供一致界面。(简化用户与子系统的交互。)

2、分类

  • 分类:结构型模式。

3、优点&缺点

  • 优点:
    • 使客户和子系统的类无耦合,子系统使用更方便;
    • 只提供更简洁界面,不影响用户直接使用子系统的类;
    • 子系统中任何类的方法的改变,不影响外观类。
  • 缺点:
    • 不满足开闭原则;(在不引入抽象外观类时,增加新的子系统可能会改变外观类代码。)

    • 不能很好限制用户使用子系统。

    遵循迪米特法则:一个类对自己依赖的类知道的越少越好。即,对于被依赖的类,无论逻辑多么复杂,尽量将逻辑封装在类的内部。

4、适用场景

  • 对于复杂子系统,需为用户提供简单交互操作;
  • 不希望客户与子系统的类耦合,提高子系统独立性和可移植性;
  • 当整个系统需要构建一个层次结构的子系统,不希望子系统相互直接交互。

5、UML类图

图片

  • 角色:
    • 子系统 Subsystem:若干类的集合,这些类的实例协同为用户提供所需功能。
    • 外观 Facade:包含子系统全部或部分类的实例引用。

6、代码实现


//step1. 子系统 Subsystem:若干类的集合,这些类的实例协同为用户提供所需功能
class ClassA {
  public void methodA() {
    System.out.println("method A");
  }
}
class ClassB {
  public void methodB() {
    System.out.println("method B");
  }
}
//step2. 外观 Facade:包含子系统全部或部分类的实例引用
class Facade {
  private ClassA classA;
  private ClassB classB;
  public Facade() {
    classA = new ClassA();
    classB = new ClassB();
  }
  public void method() {
    classA.methodA();
    classB.methodB();
  }
}
//step3. Demo
public class TestAwen {
  public static void main(String[] args) {
    Facade facade = new Facade();
    facade.method();
  }
}

运行结果截图:

图片

十、迭代器模式

1、定义

  • 提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
  • (将遍历集合的任务交给迭代器。)

2、分类&别名

  • 分类:行为型模式
  • 别名:游标

3、优点

  • 用户使用迭代器访问集合中的对象,不需要知道这些对象在集合中是如何表示的;
  • 可同时使用多个迭代器遍历一个集合。
  • 简化聚合接口;
  • 方便添加新的聚合类或者迭代器类;
  • 支持以不同的方式遍历一个聚合对象。

4、适用场景

  • 希望对遍历不同的集合提供一个统一接口;
  • 让用户访问集合中的对象,但不想暴露对象在集合中的存储结构。

5、UML类

图片

  • 四种角色:
    • 集合 Aggregate:规定具体集合需实现的操作。
    • 迭代器 Iterator:规定遍历具体集合的方法。
    • 具体集合 ConcreteAggregate:设置一个方法返回针对该集合的具体迭代器。
    • 具体迭代器 ConcreteIterator

6、代码实现


//step1. 集合 Aggregate:规定具体集合需实现的操作
interface Aggregate {
  public Iterator createIterator();
}
//step2. 迭代器 Iterator:规定遍历具体集合的方法
interface Iterator {
  public Object next();
  public boolean hasNext();
}
//step3. 具体集合 ConcreteAggregate:设置一个方法返回针对该集合的具体迭代器
class ConcreteAggregate implements Aggregate {
  private Object[] tv = {"cctv-1", "cctv-2", "cctv-3"};
  public Iterator createIterator() {
    return new ConcreteIterator();
  }
  //step4. 具体迭代器 ConcreteIterator
  class ConcreteIterator implements Iterator {
    public int cursorIndex = 0;
    public Object next() {
      if(this.hasNext())
        return tv[cursorIndex++];
      return null;
    }
    public boolean hasNext() {
      if(cursorIndex < tv.length)
        return true;
      return false;
    }
  }
}
//step5. Demo
public class TestAwen {
  public static void main(String[] args) {
    Aggregate aggregate = new ConcreteAggregate();
    Iterator iterator = aggregate.createIterator();
    while (iterator.hasNext())
      System.out.println(iterator.next().toString());
  }
}

十一、组合模式

1、定义

  • 将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象的使用具有一致性。(一个对象包含其他对象的引用,称这样的对象为组合对象。)

2、分类

  • 分类:结构型模式

3、优点

  • 用户可方便处理个体对象和组合对象;
  • 组合对象和个体对象实现了相同的接口,用户一般无需区分个体对象和组合对象;
  • 当增加新的 Component 节点和 Leaf 节点时,用户的重要代码不需要做修改,更容易添加新类型的组件。

4、适用场景

  • 想表示对象的部分-整体层次结构;
  • 希望用户用一致的方式处理个体对象和组合对象。

5、UML类图

图片

  • 角色:
    • Component 抽象组件:定义了叶子节点和容器构件的共同点;
    • Composite 节点:有容器特征,可以包含子节点;
    • Leaf 节点:没有子节点。

6、代码实现

import java.util.ArrayList;

//step1. 抽象组件 Component
interface Component {
  public void add(Component component);
  public void remove(Component component);
  public Component getChild(int index);
  public abstract void operation();
}
//step2. Composite 节点
class Composite implements Component {
  private ArrayList<Component> list = new ArrayList<Component>();
  public void add(Component c) {
    if(!(list.contains(c)))
      list.add(c);
  }
  public void remove(Component c) {
    if(list.contains(c))
      list.remove(c);
  }
  public Component getChild(int i) {
    if(i>=0&i<list.size())
      return (Component) list.get(i);
    return null;
  }
  public void operation() {
    System.out.println("Composite Node");
    for(Component c:list)
      c.operation();
  }
}
//step3. Leaf 节点
class Leaf implements Component {
  public void add(Component component) {
    System.err.println("Don't support add");
  }
  public void remove(Component component) {
    throw new UnsupportedOperationException("对象不支持此功能");
  }
  public Component getChild(int index) {
    return null;
  }
  public void operation() {
    System.out.println("Leaf Node");
  }
}
//step4. Demo
public class TestAwen {
  public static void main(String[] args) {
    Composite root = new Composite();
    Component c1 = new Composite();
    Component c2 = new Composite();
    Component leaf1 = new Leaf();
    Component leaf2 = new Leaf();
    Component leaf3 = new Leaf();
    root.add(c1);
    root.add(c2);
    root.add(leaf1);
    c1.add(leaf2);
    c2.add(leaf3);
    root.operation();
  }
}

运行结果截图:

图片

十二、状态模式

1、定义

  • 允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
  • (其关键是将对象的状态封装成独立的类,对象调用方法时,可以委托当前对象所具有的状态调用相应的方法。)

2、分类&别名

  • 分类:行为型模式
  • 别名:状态对象

3、优点&缺点

  • 优点:
    • 使得一个类封装对象的一种状态,很容易增加新的状态;
    • 环境中不必出现大量的条件判断语句,环境实例所呈现的状态变得更加清晰容易理解;
    • 可以让用户程序很方便地切换环境实例的状态;
    • 不会让环境的实例中出现内部状态不一致的情况;
    • 当状态对象没有实例变量时,环境的各个实例可共享一个状态对象。
  • 缺点:
    • 不满足开闭原则;
    • 增加类和对象的个数,导致系统开销增大;
    • 结构和实现较复杂,如果使用不当导致程序结构和代码混乱,增加系统设计难度。

4、适用场景

  • 一个状态的行为依赖于它的状态,并且在运行时根据状态改变它的行为;
  • 需要编写大量的分支语句来决定一个操作的行为,而且这些条件恰好表示对象的一种状态。

5、UML类图

图片

  • 角色:
    • 环境状态 Context:该类含抽象状态声明的变量,可以引用状态类的实例;
    • 抽象状态 State:定义与环境的一个特定状态相关的若干个方法;
    • 具体状态 ConcreteState:实现抽象状态。

6、状态转换&共享状态

  • 状态转换:环境实例在某种状态下执行一个方法后,可能导致该实例的状态发生变化。
  • 共享状态:环境的多个实例可共享一个状态。(将状态声明为环境的静态成员,保证共享的状态没有自己的实例。)

7、部分代码实现

public void request() { 
  state.handle(); 
} 

public void setState(State state) {
  this.state = state;
}

十三、模板方法模式

1、定义

  • 定义一个操作中算法的骨架,将一些步骤延续到子类中,使子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
  • (其关键是在一个抽象类中定义一个算法的骨架,即将若干个方法集成到一个模板方法中。)
  • (处理步骤在父类中定义好,具体实现在子类中定义。)

2、分类

  • 分类:行为型模式

3、优点&缺点

  • 优点:
    • 可以定义模板方法给出成熟的算法步骤,同时不限制步骤的细节,具体模板实现算法细节,不会改变整个算法的骨架;
    • 可以通过钩子方法对某些步骤进行挂钩,具体模板通过钩子可以选择算法骨架中的某些步骤。
  • 缺点:
    • 如果父类的可变方法太多,会导致类的个数增多。

4、适用场景

  • 设计一个需要给出一个算法的特定步骤,并将某些步骤的具体实现留给子类来实现;
  • 需对代码进行重构,将各个子类公共行为提取出来集中到一个共同的父类中,以避免代码重复。

5、钩子方法

  • 是抽象模板中定义的具体方法,但给出了空实现或默认的实现,并允许子类重写这个具体方法。(如果不希望是钩子方法,就需要将具体方法用 final 修饰,要求子类必须继承该具体方法,不能重写。)
  • 在开发中,通过父类调用子类,而子类不能调用父类,这些调用步骤在父类中已经定义好,完全由父类控制整个过程。

6、UML类图

图片

  • 角色:
    • 抽象模板 AbstractTemplate:定义若干个方法表示算法的各个步骤,还定义一个模板方法用来定义算法骨架。
    • 具体模板 ConcreteTemplate:实现抽象模板中的抽象方法。

7、部分代码实现

final void templateMehtod() {
  primitiveOperation1();
  primitiveOperation2();
}

十四、备忘录模式

1、定义

  • 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可将该对象恢复到原先保存状态。

2、分类&别名

  • 分类:行为型模式
  • 别名:标记

3、优点&缺点

  • 优点:
    • 使用备忘录可把原发者的内部状态保存起来;
    • 将状态的刻画与保存分开。
  • 缺点:
    • 消耗资源。如果类的成员变量过多,会占用较大资源,每一次保存都会消耗内存。

4、适用场景

  • 必须保存一个对象在某一时刻的全部或部分状态,以便在需要时恢复先前的一个状态;
  • 一个对象不想通过提供 public 权限的诸如 getX()方法让其他对象得到自己的内部状态。

5、UML类图

图片

  • 角色:
    • 原发者 Originator:需要在某个时刻保存其状态的对象。
    • 备忘录 Memento:负责存储原发者状态的对象(创建备忘录的类与原发者在同一个包中)。
    • 负责人 Caretaker:负责管理保存备忘录的对象(负责人不能和原发者及备忘录放在同一个包中,保证原发者的安全性)。

6、代码实现

  • 核心代码

图片

  • 完整代码
//step1. 原发者 Originator:需要在某个时刻保存其状态的对象
package originator_memento;

public class Originator {
  private int state;
  public Memento createMemento() { // 创建备忘录
    Memento mem = new Memento();
    mem.setState(state);
    return mem;
  }
  public void restoreFormMemento(Memento m) { // 恢复备忘录
    state = m.getState();
  }
  public int getState() {
    return state;
  }
  public void setState(int state) {
    this.state = state;
  }
}
//step2. 备忘录 Memento:负责存储原发者状态的对象(创建备忘录的类与原发者在同一个包中。)
package originator_memento;
public class Memento {
  private int state;
  int getState() {
    return state;
  }
  void setState(int m) {
    state = m;
  }
}
//step3. 负责人 Caretaker:负责管理保存备忘录的对象(负责人不能和原发者及备忘录放在同一个包中,保证原发者的安全性。)
package caretaker_demo;
import originator_memento.Memento;
public class Caretaker { //只可恢复一次
  private Memento m;
  public Memento getMemento() {
    return m;
  }
  public void setMemnto(Memento m) {
    this.m = m;
  }
}
///////////////////////////
//public class Caretaker { //可恢复多次
//  private ArrayList<Memento> mementoList = new ArrayList<Memento>();
//  public Memento get(int index) {
//    return mementoList.get(index);
//  }
//  public void add(Memento m) {
//    mementoList.add(m);
//  }
//}
//step4. Demo
package caretaker_demo;
import originator_memento.Originator;
public class Demo {
  public static void main(String[] args) {
    Originator originator = new Originator();
    originator.setState(10); // 初始状态
    System.out.println(originator.getState());
    System.out.println("--------------------");
    Caretaker ct = new Caretaker();
    ct.setMemnto(originator.createMemento()); // 负责人保存原发者当前状态的备忘录
    originator.setState(20); // 改变原发者状态
    System.out.println(originator.getState());
    System.out.println("====================");
    originator.restoreFormMemento(ct.getMemento()); // 由原发者恢复
    System.out.println(originator.getState());
  }
}