JAVA设计模式学习笔记

风尘

文章目录

  1. 1. 遵循原则
    1. 1.1. 开闭原则
    2. 1.2. 里氏替换原则:
    3. 1.3. 依赖倒置原则:
    4. 1.4. 单一职责原则:
    5. 1.5. 接口隔离原则
    6. 1.6. 迪米特法则
    7. 1.7. 合成复用原则
    8. 1.8. Java中的常见规则:
  2. 2. 设计模式
    1. 2.1. 单例模式(Singleton)
      1. 2.1.1. 懒汉模式:
      2. 2.1.2. 饿汉模式:
    2. 2.2. 工厂方法模式(Factory Method)
    3. 2.3. 抽象工厂模式(Abstract Factory)
    4. 2.4. 代理模式(Proxy)
    5. 2.5. 建造者模式(Builder)
    6. 2.6. 原型模式(Prototype)
    7. 2.7. 观察者模式
    8. 2.8. 适配器模式(Adapter)
    9. 2.9. 迭代器模式(Iterator)
    10. 2.10. 模板方法模式(Template Method)
    11. 2.11. 桥接模式(Bridge)
    12. 2.12. 策略模式(Strategy)
    13. 2.13. 组合模式(Composite)
    14. 2.14. 装饰器模式(Decorator)
    15. 2.15. 访问者模式(Visitor)
    16. 2.16. 责任链模式(Chain of Responsibility)
    17. 2.17. 外观模式(Facade)
    18. 2.18. 中介者模式(Mediator)
    19. 2.19. 备忘录模式(Memento)
    20. 2.20. 状态模式(State)
    21. 2.21. 享元模式(Flyweight)
    22. 2.22. 命令模式(Command)
    23. 2.23. 解释器模式(Interpreter)
  3. 3. GoF设计模式分类
    1. 3.1. 创建型设计模式
    2. 3.2. 结构型设计模式
    3. 3.3. 行为型设计模式

[TOC]

遵循原则

开闭原则

一个软件实体(类、模块、函数)应该对外扩展开放,对修改关闭。
运维尽量减少对原代码的修改,保持历史代码的纯洁性,提高系统的稳定性;

里氏替换原则:

里氏替换原则是实现开闭原则的重要方式之一。
子类可以扩展父类的功能,但不能改变父类原有的功能。
即,子类继承父类时,除了添加新方法外,尽量不要重写父类方法。

依赖倒置原则:

依赖倒置原则是实现开闭原则的重要途径之一,它降低了客户与实现模块之间的耦合。
高层模块不应该依赖底层模块,两者都应该依赖其抽象类;
抽象不应该依赖细节;细节应该依赖抽象。
依赖倒置原则的目的是通过要面向接口的编程来降低类间的耦合性。

单一职责原则:

一个类或接口只有一个职责;
应该仅有一个原因引起类的变更,否则类应该被拆分;

在日常使用中很难完全做到。
提高了可读性和可维护性,降低变更引起的风险。

接口隔离原则

尽量将臃肿庞大的接口拆分成更小的和更具体的接口,但是要有限度;
要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。

迪米特法则

核心观念是类间解耦,弱耦合;
如果两个实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。

合成复用原则

在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
如果要使用继承关系,则必须严格遵循里氏替换原则。

Java中的常见规则:

  • 接口负责定义public属性和方法,并且声明与其他对象的依赖关系,抽象类负责公共构造部分的实现,实现类准确的实现业务逻辑,同时在适当的时候对父类进行细化。

  • 各个类或模块的实现彼此独立,不相互影响,实现模块间的松耦合。

  • 接口尽可能小,一个接口只服务于一个子模块或业务逻辑

  • 已经被污染的接口,若变更的风险较大,则采用适配器模式进行处理。

设计模式

单例模式(Singleton)

定义:确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

使用场景
1、要求生成唯一序列号的环境;
2、整个项目需要一个共享访问点或共享数据。
3、创建对象需要消耗的资源过多,如访问IO和数据库等资源。

懒汉模式:

该模式在类加载时没有生成实例,只有当第一次调用 getInstance 方法时才去创建这个实例。

public class SingletonClass1 {

   private static volatile SingletonClass1 instance = null; 

   private SingletonClass1(){}

   public static synchronized SingletonClass1 getInstance(){

       return instance;

   }

}

饿汉模式:

该模式在类加载就创建一个实例,保证在调用 getInstance 方法之前实例已经存在。

public class SingletonClass2 {

	private static final SingletonClass2 instance = new SingletonClass2();

	private SingletonClass2(){}

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

工厂方法模式(Factory Method)

定义: 定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法使一个类的实例化延迟到其子类。
主要角色有:

  • 产品(Product) 属于框架方,负责定义了生成实例的特有接口,具体处理是由子类 ConcreteProduct 角色决定。
  • 创建者(Creator) 即工厂类,属于框架方,负责生成 Product 角色,具体处理由子类 ConcreteCreator 角色决定。
  • 具体产品(ConcreteProduct) 属于加工方,实现 Product 角色,决定了具体产品。
  • 具体创建者(ConcreteCreator) 属于加工方,负责生成具体的产品。
工厂模式工厂模式

优点

  • 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
  • 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;

被创建的对象称为“产品”,创建产品的对象称为“工厂”。如果要创建的产品不多,只要一个工厂类就可以完成,这种模式叫“简单工厂模式”。简单工厂模式缺点是增加新产品时会违背“开闭原则”。

简单工厂模式简单工厂模式
public interface Product {
    void create();
}

public class ConcreteProductA implements Product{
    @Override
    public void create() {
        // TODO:
        System.out.println("简单工厂模式创建实际产品A!");
    }
}

public class ConcreteProductB implements Product{
    @Override
    public void create() {
        // TODO: 
        System.out.println("简单工厂模式创建实际产品B!");
    }
}

public class ProductFactory {
  	/**
  	* 写法一 判断
  	*/
    public static Product getProduct(String type){
	  Product product = null;
      if(type == "A"){
        product =  new ConcreteProductA();
      } else if(type == "B"){
        product =  new ConcreteProductB();
      }

	  return product;
    }
  
  	/**
  	* 写法二 反射
  	*/
    public static <T> T getProduct(Class c) {
        Product product = null;

        try {
            product = (Product) Class.forName(c.getName).newInstance();	
        } catch (Exception e) {
            e.printStackTrace();
        }

        return (T)product;
    }
} 

public class App {
    public static void main(String[] args) {
      	// 写法一
      	Product product = ProductFactory.getProduct("A");
      	product.create();
      	// 写法二
        Product product = ProductFactory.getProduct(ConcreteProductA.class);
        product.create(); // 打印“简单工厂模式创建实际产品A!”
    }
}

简单工厂模式使用static工厂方法,造成工厂角色无法被继承。

简单工厂有两种实现方式,第一种通过判断要返回的产品类型返回实例,此种情况不满足开闭原则 ;第二种通过反射机制返回实例。

工厂方法模式工厂方法模式

工厂方法模式是简单工厂的近一步深化,不同的产品对象提供不同的工厂,即,每个对象都有一个与之对应的工厂工。

// -------->在简单工厂模式基本上增加各产品对象工作
/**
 * 产品创建工厂接口
 *
 * @author Windus
 * @date
 */
public interface CreateFactory {
    Product createProduct();
}

/**
 * 产品 A 创建工厂
 * @date
 * @author Windus
 */
public class ProductAFactory implements CreateFactory{
    @Override
    public Product createProduct() {
        return new ConcreteProductA();
    }
}

/**
 * 产品 B 创建工厂
 * @date
 * @author Windus
 */
public class ProductBFactory implements CreateFactory{
    @Override
    public Product createProduct() {
        return new ConcreteProductB();
    }
}

// -------->修改 app  类调用
/**
 * @author Windus
 * @date 
 */
public class App {
    public static void main(String[] args) {
    	// 创建产品 A
        CreateFactory factoryA = new ProductAFactory();
        Product productA = factoryA.createProduct();
        productA.create();

        // 创建产品 B
        CreateFactory factoryB = new ProductBFactory();
        Product productB = factoryB.createProduct();
        productB.create();
    }
}

抽象工厂模式(Abstract Factory)

抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。

抽象工厂模式除了具有工厂方法模式的优点外,其他主要优点有

  • 可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
  • 当增加一个新的产品族时不需要修改原代码,满足开闭原则。
抽象工厂模式抽象工厂模式

示例:小米和华为两个工厂生产不同品牌电视和手机。

/**
* 电视产品
* @date 2021-07-18 16:18
* @author Windus
*/
public interface Television {
	void create();	
}

/**
* 手机产品
* @date 2021-07-18 16:19
* @author Windus
*/
public interface Mobile {
	void create();	
}

/**
 * 华为电视
 * @date 2021-07-18 16:20
 * @author Windus
 */
public class Zhihuiping implements Television {
  @Override
	public void create() {
		System.out.println("创建华为电视!");
	}
}

/**
* 小米电视
* @date 2021-07-18 16:21
* @author Windus
*/
public class XiaoMiProTv implements Television {
  @Override
	public void create() {
		System.out.println("创建小米电视!");
	}
}

/**
* 华为手机
* @date 2021-07-18 16:21
* @author Windus
*/
public class P40 implements Mobile {
  @Override
	public void create() {
		System.out.println("创建华为P40手机!");	
	}
}

/**
* 小米手机
* @date 2021-07-18 16:22
* @author Windus
*/
public class RedMi implements Mobile {
  @Override
	public void create() {
		System.out.println("创建红米手机!");	
	}
}

/**
* 工厂抽象类
* @date 2021-07-18 16:22
* @author Windus
*/
public interface ProductFactory {
	/**
	* 创建电视
	* @return
	* @date 2021-07-18 16:23
	* @author Windus
	*/
	Television creaTelevision();

	/**
	* 创建手机
	* @return
	* @date 2021-07-18 16:23
	* @author Windus
	*/
	Mobile createMobile();
}

/**
* 华为工厂
* @date 2021-07-18 16:23
* @author Windus
*/
public class HuaWeiFactory implements ProductFactory {
	@Override
	public Television creaTelevision() {
		System.out.println("华为工厂创建!");
		return new Zhihuiping();
	}

	@Override
	public Mobile createMobile() {
		System.out.println("华为工厂创建!");
		return new P40();
	}
}

/**
* 小米工厂
* @date 2021-07-18 16:33
* @author Windus
*/
public class XiaoMiFactory implements ProductFactory {
	@Override
	public Television creaTelevision() {
		System.out.println("小米工厂创建!");
		return new XiaoMiProTv();
	}

	@Override
	public Mobile createMobile() {
		System.out.println("小米工厂创建!");
		return new RedMi();
	}
}

public class App {
	public static void main(String[] args) {
		// 华为
		ProductFactory hwProductFactory = new HuaWeiFactory();
		Television hwTelevision = hwProductFactory.creaTelevision();
		hwTelevision.create();

		Mobile hwMobile = hwProductFactory.createMobile();
		hwMobile.create();

		// 小米
		ProductFactory xmProductFactory = new XiaoMiFactory();
		Television xmTelevision = xmProductFactory.creaTelevision();
		xmTelevision.create();

		Mobile xmMobile = xmProductFactory.createMobile();
		xmMobile.create();
	}
}
  1. 当增加一个新的产品族时只需增加一个新的具体工厂,不需要修改原代码,满足开闭原则。
  2. 当产品族中需要增加一个新种类的产品时,则所有的工厂类都需要进行修改,不满足开闭原则。

代理模式(Proxy)

定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。 由于访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

角色

  • 主体(Subject) 定义了使 Proxy 和 Real Subject 角色之间具有一致性接口。
  • 真实主体(Real Subject) 实现 Subject 角色接口,是代理对象所代表的真实对象。
  • 代理(Proxy) 提供与 Real Subject 相同方法,内部引用 Real Subject 角色。

优点

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  • 代理对象可以扩展目标对象的功能;
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;

普通代理:通过代理来访问角色,不能直接访问真实角色;真实角色的初始化放在代理类中。

普通代理模式普通代理模式
public interface Game {
    void play();
}

public class Contra implements Game{
    @Override
    public void play() {
        System.out.println("魂斗罗游戏 开始!");
    }
}

public class ContraProxy implements Game{
    private Contra contra;

    public ContraProxy(Contra contra) {
        this.contra = contra;
    }

    @Override
    public void play() {
        prePlay();
        contra.play();
        afterPlay();
    }

    public void prePlay(){
        System.out.println("玩魂斗罗游戏 开始前!");
    }

    public void afterPlay(){
        System.out.println("玩魂斗罗游戏 结束后!");
    }
}

public class App {
    public static void main(String[] args) {
        Contra contra = new Contra();
        Game game = new ContraProxy(contra);
        game.play();
    }
}

强制代理:真实角色初始化后,不能直接使用,必须获取代理类,用代理类调用对应的方法。
强制代理通常通过增加getProxy()方法实现获取方法,在真实类增加私有方法判断是否通过代理类调用。

代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。

动态代理:在实现阶段不用关心代理谁,在运行阶段才指定代理那个对象。
静态代理中(普通代理和强制代理),每一个代理类只能为一个接口服务,而且,所有的代理操作除了调用的方法不一样之外,其他的操作都一样,则此时导致代码是重复代码,可以通过动态代理解决这个问题。

动态代理通过调用Proxy的静态方法执行实现InvocationHandler接口的类实现动态创建代理。

动态代理模式动态代理模式
// 增加一款新游戏
public class KingofGlory implements Game{
    @Override
    public void play() {
        System.out.println("王者荣耀 开始!");
    }
}

// 增加游戏媒介类(小孩玩游戏)
public class Kid implements InvocationHandler {

    private Game game;

    public Kid(Game game) {
        this.game = game;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        prePlay();
        method.invoke(game, args);
        afterPlay();
        return null;
    }

    public void prePlay() {
        System.out.println("玩游戏 开始前!");
    }

    public void afterPlay() {
        System.out.println("玩游戏 结束后!");
    }
}

// 动态代理实现
public class App {
    public static void main(String[] args) {
        // 玩魂斗罗
        Contra contra = new Contra();
        InvocationHandler invocationHandler1 = new Kid(contra);
        Game gameDynamicProxy1 = (Game) Proxy.newProxyInstance(Contra.class.getClassLoader(),
                Contra.class.getInterfaces(), invocationHandler1);

        gameDynamicProxy1.play();

        // 玩王者荣耀
        KingofGlory kingofGlory = new KingofGlory();
        InvocationHandler invocationHandler2 = new Kid(kingofGlory);
        Game gameDynamicProxy2 = (Game) Proxy.newProxyInstance(KingofGlory.class.getClassLoader(),
                KingofGlory.class.getInterfaces(), invocationHandler2);

        gameDynamicProxy2.play();
    }
}

建造者模式(Builder)

将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。
主要角色有:

  • 建造者(Builder) 负责定义用于生成实例的接口
  • 具体建造者(ConcreteBuilder) 实现 Builder 接口,定义生成实例时实际被调用的方法。
  • 监工(Director) 负责使用 Builder 接口生成实例。它并不依赖于 ConcreteBuilder ,保证不论 ConcreteBuilder 如何定义,Director 都能正常工作。

优点

  • 各个具体的建造者相互独立,有利于系统的扩展。
  • 客户端不必知道产品内部组成的细节,便于控制细节风险。

缺点

  • 产品的组成部分必须相同,这限制了其使用范围。
  • 如果产品的内部变化复杂,该模式会增加很多的建造者类。

示例:将电子邮件对象分解成发件人、收件人、主题、内容、附件等内容对象。

建造者模式建造者模式
@Data
public class Email {
    /**
     * 收件人
     */
    private String receiver;

    /**
     * 发件人
     */
    private String addresser;

    /**
     * 主题
     */
    private String subject;

    /**
     * 内容
     */
    private String content;

    /**
     * 附件
     */
    private String attachment;

}

/**
 * 建造者
 *
 * @author Windus
 */
public interface Builder {

    Email getResult();

    void buildReceiver();

    void buildAddresser();

    void buildSubject();

    void buildContent();

    void buildAttachment();
}

/**
 * 实际建造者
 *
 * @author Windus
 */
public class ConcreteBuilder implements Builder {

    private Email email = new Email();

    @Override
    public Email getResult() {
        return email;
    }

    @Override
    public void buildReceiver() {
        this.email.setReceiver("建造发送者!");
        System.out.println("建造发送者!");
    }

    @Override
    public void buildAddresser() {
        this.email.setAddresser("建造收件者!");
        System.out.println("建造收件者!");
    }

    @Override
    public void buildSubject() {
        this.email.setSubject("建造主题!");
        System.out.println("建造主题!");
    }
}

/**
 * 指挥者
 *
 * @author Windus
 */
public class Director {
    private Builder builder;

    public Director(Builder builder) {
        this.builder = builder;
    }

    public void send() {
        builder.buildReceiver();
        builder.buildAddresser();
        builder.buildSubject();
        builder.buildContent();
        builder.buildAttachment();
    }
}

public class App {
    public static void main(String[] args) {
        Builder builder = new ConcreteBuilder();
        Director director = new Director(builder);

        director.send();

        System.out.println(builder.getResult().toString());
    }
}

原型模式(Prototype)

定义:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。通过对象 clone() 方法实现。

使用场景

  • 资源优化场景:类初始化需要消耗非常多的资源。
  • 性能和安全要求的场景:通过new产生对象需要繁琐的数据准备或访问权限。
  • 一个对象多个修改者场景。

角色

  • 原型(Prototype) 定义用于复制现有实例来生成新实例的方法。
  • 具体原型(ConcretePrototype) 负责实现复制现有实例并生成新实例的方法。
原型模式原型模式
/**
 * 复制功能接口(原型角色)
 * 
 * @date 2021-05-24 14:03
 * @author Windus
 */
public interface Product extends Cloneable {
    /**
     * 产品方法
     * 
     * @param s
     * @return
     * @date 2021-05-24 14:08
     * @author Windus
     */
    void use(String s);

    /**
     * 创建克隆错象
     * 
     * @return
     * @date 2021-05-24 14:10
     * @author Windus
     */
    Product createClone();
}

/**
 * 具体原型
 * 
 * @date 2021-05-24 14:11
 * @author Windus
 */
public class Apple implements Product {

    @Override
    public Product createClone() {
        Product product = null;
        try {
            product = (Product) super.clone();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return product;
    }

    @Override
    public void use(String s) {
        System.out.println(s);
    }
}

/**
 * 管理器
 * 
 * @date 2021-05-24 14:17
 * @author Windus
 */
public class Manager {
    private HashMap<String, Product> cacheMap = new HashMap<>();

    public void register(String name, Product proto) {
        this.cacheMap.put(name, proto);
    }

    public Product create(String name) {
        return cacheMap.get(name);
    }
}

/**
 * Client 使用类
 * 
 * @date 2021-05-24 14:20
 * @author Windus
 */
public class App {

    public static void main(String[] args) {
        // 注册
        Manager manager = new Manager();
        Apple apple = new Apple();
        manager.register("ap", apple);

        // 复制
        Product product = manager.create("ap");

        // 使用
        product.use("一斤苹果!");
    }
}

观察者模式

定义:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象会得到通知并被自动更新。这种模式有时又称作发布-订阅模式。
该模式主要角色有:

  • 观察对象(Subject),即被观察者角色。提供用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
  • 具体观察对象(Concrete Subject)。它实现观察对象中的通知方法,当具体观察者的内部状态发生改变时,通知所有注册过的观察者对象。
  • 观察者(Observer)。包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
  • 具体观察者(Concrete Observer)。实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。

使用场景

  • 关联行为场景;
  • 事件多级触发场景;
  • 跨系统的消息交换场景,如消息队列的处理机制。

优点

  • 将被观察者和观察者进行解耦,使得他们之间的依赖性更小,甚至做到毫无依赖

缺点

  • 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
  • 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
观察者模式观察者模式
/**
 * 彩票
 *
 * @author Windus
 */
@Data
public class Lottery {
    /**
     * 号码
     */
    private String no;

    /**
     * 期号
     */
    private String issue;

    public Lottery(String no, String issue) {
        this.no = no;
        this.issue = issue;
    }
}

/**
 * 被观察对象
 *
 * @author Windus
 */
public interface Subject {

    /**
     * 增加观察者
     *
     * @param observer
     * @return
     * @author Windus
     */
    void addObserver(Observer observer);

    /**
     * 删除观察者
     *
     * @param observer
     * @return
     * @author Windus
     */
    void delObserver(Observer observer);

    /**
     * 通知观察者
     *
     * @param lottery
     * @return
     * @author Windus
     */
    void notifyObservers(Lottery lottery);

    /**
     * 开奖
     *
     * @param lottery
     * @return
     * @author Windus
     */
    void lottery(Lottery lottery);
}

/**
 * 彩票管理中心(具体被观察对象)
 *
 * @author Windus
 */
public class LotteryCenter implements Subject {
    private List<Observer> observerList = new ArrayList<>();

    @Override
    public void addObserver(Observer observer) {
        this.observerList.add(observer);
    }

    @Override
    public void delObserver(Observer observer) {
        this.observerList.remove(observer);
    }

    @Override
    public void notifyObservers(Lottery lottery) {
        for (Observer observer : observerList) {
            observer.lottery(lottery);
        }
    }

    @Override
    public void lottery(Lottery lottery) {
        notifyObservers(lottery);
    }
}

/**
 * 观察者
 *
 * @author Windus
 */
public interface Observer {

    /**
     * 开奖
     *
     * @param lottery
     * @return
     * @author Windus
     */
    void lottery(Lottery lottery);
}

/**
 * 彩票持有者A(具体观察者)
 *
 * @author Windus
 */
public class LotteryHoldersA implements Observer {
    private Lottery lottery;

    public LotteryHoldersA(String no, String issue) {
        lottery = new Lottery(no, issue);
    }

    @Override
    public void lottery(Lottery lottery) {
        System.out.println("LotteryHoldersA 接收到开奖通知:" + lottery.toString());

        if (this.lottery.getIssue().equals(lottery.getIssue())) {
            if (this.lottery.getNo().equals(lottery.getNo())) {
                System.out.println("|");
                System.out.println("|___ 中奖啦~\n");
            }
        }
    }
}

/**
 * 彩票持有者B(具体观察者)
 *
 * @author Windus
 */
public class LotteryHoldersB implements Observer {
    private Lottery lottery;

    public LotteryHoldersB(String no, String issue) {
        lottery = new Lottery(no, issue);
    }

    @Override
    public void lottery(Lottery lottery) {
        System.out.println("LotteryHoldersB 接收到开奖通知:" + lottery.toString());

        if (this.lottery.getIssue().equals(lottery.getIssue())) {
            if (this.lottery.getNo().equals(lottery.getNo())) {
                System.out.println("|");
                System.out.println("|___ 中奖啦~\n");
            }
        }
    }
}

public class App {
    public static void main(String[] args) {
        Subject subject = new LotteryCenter();
        Observer obsA = new LotteryHoldersA("82 43 23 32 67 75 07 92", "202005");
        Observer obsB = new LotteryHoldersB("82 43 23 32 67 75 07 93", "202005");

        // 添加观察者
        subject.addObserver(obsA);
        subject.addObserver(obsB);

        // 中奖号码
        Lottery lottery = new Lottery("82 43 23 32 67 75 07 92", "202005");

        // 开奖
        subject.lottery(lottery);
    }
}

适配器模式(Adapter)

定义:将一个类的接口变成客户端所期待的另一种接口,从而使原本不匹配而无法在一起工作的两个类一起工作。
通常包含三个角色:

  • Target(目标角色) 当前系统已定义的接口或抽象类。
  • Adaptee(适配者角色) 需要被适配的现存组件库接口。
  • Adapter(适配器角色) 使用 Adaptee 角色的方法来满足 Target 角色的需求转换器。

使用场景:已经投产的项目修改时,经常使用。

优点

  • 客户端通过适配器可以透明地调用目标接口。
  • 复用了现存的类,不需要修改原有代码而重用现有的适配者类。
  • 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。

适配器模式有两种形式

  • 类适配器模式(使用继承的适配器)
类适配器模式类适配器模式
  • 对象适配器模式(使用委托的适配器)
对象适配器模式对象适配器模式

示例
适配器模式适配器模式

/**
 * 游戏手柄(Target 目标角色)
 *
 * @author Windus
 */
public interface GamePad {
    void control();
}

/**
 * GTA 游戏
 *
 * @author Windus
 */
public class GTAGame {
    private GamePad gamePad;

    public GTAGame(GamePad gamePad) {
        this.gamePad = gamePad;
    }

    public void start() {
        gamePad.control();
    }
}

/**
 * xbox 游戏手柄
 *
 * @author Windus
 */
public class XBoxPad implements GamePad {

    @Override
    public void control() {
        System.out.println("xbox 游戏开始!");
    }
}

/**
 * 特殊游戏手柄(Adaptee 适配者角色)
 *
 * @author Windus
 */
public interface SpecialGamePad {
    void specialControl();
}

/**
 * BT 游戏手柄
 *
 * @author Windus
 */
public class BTPad implements SpecialGamePad {
    @Override
    public void specialControl() {
        System.out.println("BT 游戏开始!");
    }
}

/**
 * 游戏手柄适配器(Adapter 适配器角色)
 * @author Windus
 */
public class GamePadAdapter implements GamePad {

    private SpecialGamePad specialGamePad;

    public GamePadAdapter(SpecialGamePad specialGamePad) {
        this.specialGamePad = specialGamePad;
    }

    @Override
    public void control() {
        specialGamePad.specialControl();
    }
}

/**
 * 模式应用(客户端角色)
 * @author Windus
 */
public class App {
    public static void main(String[] args) {
        // 原手柄调用 xbox
        GamePad gamePad = new XBoxPad();
        GTAGame gtaGame = new GTAGame(gamePad);
        gtaGame.start();

        // 适配其他手柄后调用 bt
        SpecialGamePad btPad = new BTPad();
        GamePadAdapter gamePadAdapter = new GamePadAdapter(btPad);
        gtaGame = new GTAGame(gamePadAdapter);
        gtaGame.start();
    }
}

迭代器模式(Iterator)

定义:提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部信息。
该模式主要角色有:

  • Iterator(迭代器) 负责定义访问、遍历集合元素的接口,如:hasNext()、first()、next()等方法。
  • ConcreteIterator(具体迭代器) 实现迭代器接口中定义的方法,包含遍历集合所必需的信息,记录遍历的当前位置。
  • Aggregate(集合) 定义存储、添加、删除集合对象以及创建迭代器对象接口。
  • ConcreteAggregate(具体集合) 实现集合中的接口,返回迭代器实例。
迭代器模式迭代器模式
/**
 * 迭代器接口
 * 
 * @date 2021-05-18 10:40
 * @author Windus
 */
public interface Iterator {

    /**
     * 是否有下一个
     * 
     * @return
     * @date 2021-05-18 10:43
     * @author Windus
     */
    boolean hasNext();

    /**
     * 下一个元素
     * 
     * @return
     * @date 2021-05-18 10:43
     * @author Windus
     */
    Object next();
}

/**
 * 书架迭代类
 * 
 * @date 2021-05-18 11:04
 * @author Windus
 */
public class BookshelfIterator implements Iterator {
    /**
     * 书架
     */
    private BookShelf bookShelf;

    /**
     * 索引
     */
    private int index;

    public BookshelfIterator(BookShelf bookShelf) {
        this.bookShelf = bookShelf;
        this.index = 0;
    }

    @Override
    public boolean hasNext() {
        if (this.index < bookShelf.getLength()) {
            return true;
        }
        return false;
    }

    @Override
    public Object next() {
        Book book = bookShelf.getBook(index);
        index++;
        return book;
    }
}

/**
 * 集合接口
 * 
 * @date 2021-05-18 10:27
 * @author Windus
 */
public interface Aggregate {
    /**
     * 迭代方法
     * 
     * @return
     * @date 2021-05-18 10:29
     * @author Windus
     */
    Iterator iterator();
}

/**
 * 书架类
 * 
 * @date 2021-05-18 10:52
 * @author Windus
 */
public class BookShelf implements Aggregate {

    /**
     * 图书集合
     */
    private Book[] book;

    /**
     * 最后一个
     */
    private int last = 0;

    public BookShelf(int maxsize) {
        this.book = new Book[maxsize];
    }

    public Book getBook(int index) {
        return this.book[index];
    }

    public void appendBook(Book book) {
        this.book[last] = book;
        last++;
    }

    public int getLength() {
        return last;
    }

    @Override
    public Iterator iterator() {
        return new BookshelfIterator(this);
    }

}

import lombok.AllArgsConstructor;
import lombok.Data;

/**
 * 图书类
 * 
 * @date 2021-05-18 10:44
 * @author Windus
 */
@Data
@AllArgsConstructor
public class Book {
    /**
     * 书名
     */
    private String name;
}

/**
 * 执行类
 * 
 * @date 2021-05-18 11:12
 * @author Windus
 */
public class App {
    public static void main(String[] args) {
        BookShelf bookShelf = new BookShelf(5);
        bookShelf.appendBook(new Book("图书1"));
        bookShelf.appendBook(new Book("图书2"));
        bookShelf.appendBook(new Book("图书3"));
        bookShelf.appendBook(new Book("图书4"));
        bookShelf.appendBook(new Book("图书5"));

        Iterator it = bookShelf.iterator();
        while (it.hasNext()) {
            Book book = (Book) it.next();
            System.out.println(book.getName());
        }
    }
}

引入 Iterator 后可以将遍历与集合实现分离,如上 159 行代码通过 Iterator 的 hasNext 方法和 next 方法获得 Book 信息,并不需要依赖 BookShelf 的实现。当开发人员打算使用 List 替换数组管理书架时,只需保证返回正确的 Iterator 实例,迭代相关代码无需任何修改即可正常工作。

模板方法模式(Template Method)

定义:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
主要角色有:

  • AbstractClass(抽象类) 负责实现模板方法,声明在模板方法中使用的抽象方法。这些抽象方法由子类 ConcreteClass 负责实现。
  • ConcreteClass(具体类) 负责实现 AbstractClass 角色中定义的抽象方法。

Java8中接口支持 defaultstatic 关键字,接口已经和抽象类很接近,但是模板方法模式依然不能使用接口实现,因为,其一是接口不支持构造器;其次接口方法只能是 public 范围等限制。

示例:把大象放冰箱,总共分几步?

模板方法模式模板方法模式
/**
 * 物品接口
 * 
 * @date 2021-05-20 18:25
 * @author Windus
 */
public interface Goods {
    /**
     * 物品名字
     * 
     * @return
     * @date 2021-05-20 18:27
     * @author Windus
     */
    String name();
}

/**
 * 大象
 * 
 * @date 2021-05-20 18:28
 * @author Windus
 */
public class Elephant implements Goods {
    /**
     * 名称
     */
    private String name;

    public Elephant(String name) {
        this.name = name;
    }

    @Override
    public String name() {
        return name;
    }
}

/**
 * 冰箱存储抽象类
 * 
 * @date 2021-05-20 18:21
 * @author Windus
 */
public abstract class AbstractFridgeStore {
    /**
     * 物品
     */
    protected Goods goods;

    public AbstractFridgeStore(Goods goods) {
        this.goods = goods;
    }

    /**
     * 
     * @param
     * @return
     * @date 2021-05-20 18:23
     * @author Windus
     */
    public final void store() {
        openDoor();
        putGoogs();
        closeDoor();
    }

    /**
     * 把冰箱门打开
     * 
     * @return
     * @date 2021-05-20 18:32
     * @author Windus
     */
    private void openDoor() {
        System.out.println("把冰箱门打开!");
    }

    /**
     * 把冰箱门关上
     * 
     * @return
     * @date 2021-05-20 18:32
     * @author Windus
     */
    private void closeDoor() {
        System.out.println("把冰箱门关上!");

    }

    /**
     * 放入物品(抽象方法交给子类实现)
     * 
     * @return
     * @date 2021-05-21 09:35
     * @author Windus
     */
    protected abstract void putGoogs();
}

/**
 * 冰箱存储大象类
 * 
 * @date 2021-05-20 18:39
 * @author Windus
 */
public class ElephnatFridgeStore extends AbstractFridgeStore {

    public ElephnatFridgeStore(Goods goods) {
        super(goods);
    }

    @Override
    protected void putGoogs() {
        System.out.println(String.format("把%s放入冰箱!", goods.name()));
    }
}

/**
 * 问:把大象放冰箱,总共分几步?
 * 
 * @date 2021-05-20 18:38
 * @author Windus
 */
public class App {
    public static void main(String[] args) {
        Goods elephant = new Elephant("大象");
        AbstractFridgeStore fridgeStore = new ElephnatFridgeStore(elephant);
        fridgeStore.store();
    }
}

-------------------------------------
把冰箱门打开!
把大象放入冰箱!
把冰箱门关上!

桥接模式(Bridge)

类的层次结构

  • 类的功能层次结构 在一个类需要增加新功能时(如:增加一个方法),编写一个新的子类来继承当前类,此时就产生了一个小的功能层次结构。

ParentClass.java
|_ ChildClass.java
|_ AnotherChildClass.java


- **类的实现层次结构** 当在抽象类或接口中定义了一些抽象方法,然后子类去负责实现这些方法。父类的任务是定义接口,而子类的任务是实现接口中的方法,以便写出具有高可替换性的类。这里产生的层次结构并非用于增加功能,这种的层次结构叫做类的实现层次结构。

AbstractClass.java
|_ ConcreteClass.java
|_ AnotherConcreteClass.java




`定义`:桥接模式就是在上面两种结构之间搭建一座桥梁将它们连接起来。

主要角色:

- **抽象化( Abstraction )** 角色位于类的功能层次结构最顶层,并包含一个 Implementor 角色的引用,用于使用该角色中的定义的方法。
- **精确的抽象化( Refined Abstraction )** 是 Abstraction 角色的子类,在 Abstraction 角色基础上增加了新的功能。
- **实现化( Implementor )** 角色位于类的层次结构最上层,定义实现化角色的接口,供扩展抽象化角色调用。
- **具体实现化( Concrete Implementor )** 负责实现 Implementor 角色中定义的接口。

`示例`:PhotoShop 画图时可以画各种形状(如:圆形、方形等),各形状可以填充任意颜色(如红色、蓝色等)。两者之间可以任意组合,此时可以使用桥接模式解决这个问题。

<img src='bridge.png' alt="桥接模式" style="height:400px;"/>

```java
/**
* 颜色(实现化角色)
* 
* @date 2021-06-01 10:10
* @author Windus
*/
public interface Color {
  /**
   * 颜色填充
   * 
   * @return
   * @date 2021-06-01 10:12
   * @author Windus
   */
  void rawFill();
}

/**
* 红色(具体实现化角色)
* 
* @date 2021-06-01 10:23
* @author Windus
*/
public class Red implements Color {
  @Override
  public void rawFill() {
      System.out.println("红色");
  }
}

/**
* 蓝色(具体实现化角色)
* 
* @date 2021-06-01 10:24
* @author Windus
*/
public class Bule implements Color {
  @Override
  public void rawFill() {
      System.out.println("蓝色");
  }
}

/**
* 形状抽象类(抽象化角色)
* 
* @date 2021-06-01 10:09
* @author Windus
*/
public abstract class AbstractSharp {
  private Color color;

  protected AbstractSharp(Color color) {
      this.color = color;
  }

  private void fill() {
      color.rawFill();
  }

  protected void draw() {
      fill();
  }
}

/**
* 圆形(精确的抽象化角色)
* 
* @date 2021-06-01 10:14
* @author Windus
*/
public class CircleSharp extends AbstractSharp {
  public CircleSharp(Color color) {
      super(color);
  }

  public void drawCircle() {
      System.out.print("圆形:");
      draw();
  }
}

/**
* 长方形(精确的抽象化角色)
* 
* @date 2021-06-01 10:20
* @author Windus
*/
public class SquareSharp extends AbstractSharp {
  public SquareSharp(Color color) {
      super(color);
  }

  public void drawSquare() {
      System.out.print("方形:");
      draw();
  }
}

public class App {
  public static void main(String[] args) {
      Color red = new Red();
      Color blue = new Bule();
      
      // 抽象化调用
      AbstractSharp abstractSharp = new CircleSharp(red);
      abstractSharp.draw();

      // 圆形精确抽象化调用
      CircleSharp circleSharp = new CircleSharp(red);
      circleSharp.drawCircle();
      
      // 方形精确抽象化调用
      SquareSharp squareSharp = new SquareSharp(blue);
      squareSharp.drawSquare();
  }
}

策略模式(Strategy)

定义:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。

优点

  • 避免使用多重条件语句,如 if…else 语句、switch…case 语句。
  • 对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
  • 把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。

角色

  • 抽象策略(Strategy) 定义一个接口,负责决定实现策略所必要的接口。
  • 具体策略(ConcreteStrategy) 负责实现具体的策略(战略、方向、方法和算法等)。
  • 上下文(Context) 保存具体策略的实例,给客户端调用。

示例:猜拳游戏,两个玩家多次出拳。

策略模式策略模式
/**
 * 手掌类
 * 
 * @date 2021-06-17 17:50
 * @author Windus
 */
@Getter
public class Hand {
	public static final int HAND_VALUE_ROCK = 0;
	public static final int HAND_VALUE_SCISSORS = 1;
	public static final int HAND_VALUE_PAPER = 2;

	public static final String[] HAND_NAMES = { "石头", "剪刀", "布" };

	private int value;
	private String name;

	public Hand(int value) {
		this.value = value;
		this.name = HAND_NAMES[value];
	}

	/**
	 * 赢得对方
	 * 
	 * @param value
	 * @return true/false
	 * @date 2021-06-18 10:00
	 * @author Windus
	 */
	public boolean isWinThan(Hand hand) {
		return fight(hand.getValue()) == 1;
	}

	/**
	 * 输给对方
	 * 
	 * @param value
	 * @return true/false
	 * @date 2021-06-18 10:01
	 * @author Windus
	 */
	public boolean isLoseThan(Hand hand) {
		return fight(hand.getValue()) == -1;
	}

	/**
	 * 出拳比较
	 * 
	 * @param value
	 * @return 0 平 1 胜 -1 败
	 * @date 2021-06-18 09:43
	 * @author Windus
	 */
	private int fight(int value) {
		if (this.value == value) {
			return 0;
		} else if ((this.value + 1) % HAND_NAMES.length == value) {
			return 1;
		} else {
			return -1;
		}
	}
}

/**
 * 策略接口
 * 
 * @date 2021-06-17 17:48
 * @author Windus
 */
public interface Strategy {
	/**
	 * 下一次出拳
	 * 
	 * @param
	 * @return
	 * @date 2021-06-17 17:48
	 * @author Windus
	 */
	Hand nextHand();

	/**
	 * 学习上一局结果
	 * 
	 * @param win 是否获胜
	 * @return
	 * @date 2021-06-17 18:22
	 * @author Windus
	 */
	void study(boolean win);
}

/**
 * 玩家类
 * 
 * @date 2021-06-17 17:52
 * @author Windus
 */
@ToString
public class Player {
	/**
	 * 玩家姓名
	 */
	private String name;

	/**
	 * 玩家策略
	 */
	private Strategy strategy;

	/**
	 * 赢次数
	 */
	private int windCount;

	/**
	 * 输次数
	 */
	private int loseCount;

	/**
	 * 游戏次数
	 */
	private int gameCount;

	public Player(String name, Strategy strategy) {
		this.name = name;
		this.strategy = strategy;
	}

	public Hand nexHand() {
		return strategy.nextHand();
	}

	public void win() {
		strategy.study(true);
		windCount++;
		gameCount++;
	}

	public void lose() {
		strategy.study(false);
		loseCount++;
		gameCount++;
	}

	public void even() {
		gameCount++;
	}
}

/**
 * 赢时策略 <br/>
 * 如果上一局获胜,则下局继续出相同手势
 * 
 * @date 2021-06-18 10:11
 * @author Windus
 */
public class WinningStrategy implements Strategy {
	private boolean won;
	private Integer prevHandValue;

	@Override
	public Hand nextHand() {
		if (!won) {
			Random random = new Random();
			prevHandValue = random.nextInt(Hand.HAND_NAMES.length);
		}
		return new Hand(prevHandValue);
	}

	@Override
	public void study(boolean win) {
		won = win;
	}
}

/**
 * 概率策略<br/>
 * 计算每种出拳概率决定下次出拳 <br/>
 * 
 * histories[0][0] 两局分别出 石头 <-> 石头,时胜利次数 <br/>
 * histories[0][1] 两局分别出 石头 <-> 剪刀,时胜利次数 <br/>
 * histories[0][2] 两局分别出 石头 <->布,胜利次数 <br/>
 * 
 * 例如:<br/>
 * histories[0][0] = 3 <br/>
 * histories[0][1] = 5 <br/>
 * histories[0][7] = 7 <br/>
 * 
 * 下一局出石头、剪刀、布的比率就是 3:5:7,然后在 15(3+5+7) 中取一个随机数,小于 3 则出石头,小于 8(3+5) 则出剪刀,小于 15
 * 则出布。
 * 
 * @date 2021-06-18 10:38
 * @author Windus
 */
public class PropStrategy implements Strategy {

	/**
	 * 上次出拳
	 */
	private int prevHandValue = 0;

	/**
	 * 本次出拳
	 */
	private int currentHandValue = 0;

	/**
	 * 出拳纪录 [上一次出拳][本次出拳]<br/>
	 */
	private int[][] histories;

	public PropStrategy() {
		// 初始化纪录数组
		int historiesLen = Hand.HAND_NAMES.length;
		histories = new int[historiesLen][historiesLen];
		for (int i = 0; i < historiesLen; i++) {
			for (int j = 0; j < historiesLen; j++) {
				histories[i][j] = 1;
			}
		}
	}

	@Override
	public Hand nextHand() {
		int handValue;
		Random random = new Random();
		int propValue = random.nextInt(getSum(currentHandValue));
		if (propValue < histories[currentHandValue][Hand.HAND_VALUE_ROCK]) {
			handValue = Hand.HAND_VALUE_ROCK;
		} else if (propValue < histories[currentHandValue][Hand.HAND_VALUE_ROCK]
				+ histories[currentHandValue][Hand.HAND_VALUE_SCISSORS]) {
			handValue = Hand.HAND_VALUE_SCISSORS;
		} else {
			handValue = Hand.HAND_VALUE_PAPER;
		}
		prevHandValue = currentHandValue;
		currentHandValue = handValue;
		return new Hand(handValue);
	}

	@Override
	public void study(boolean win) {
		if (win) {
			// 赢了当前局手势次数加 1 
			histories[prevHandValue][currentHandValue]++;
		} else {
			// 输了当前局除了当前手势,其他手势次数加 1
			histories[prevHandValue][(currentHandValue + 1) % Hand.HAND_NAMES.length]++;
			histories[prevHandValue][(currentHandValue + 2) % Hand.HAND_NAMES.length]++;
		}
	}

	/**
	 * 计算指定手势值分别出石头、剪刀、步的次数的总和
	 * 
	 * @param value 手势值
	 * @return
	 * @date 2021-06-18 10:56
	 * @author Windus
	 */
	private int getSum(int value) {
		int sum = 0;
		for (int i = 0; i < Hand.HAND_NAMES.length; i++) {
			sum += histories[value][i];
		}
		return sum;
	}
}

public class App {
	public static void main(String[] args) {
		Player zhangsan = new Player("张三", new WinningStrategy());
		Player wangwu = new Player("王五", new PropStrategy());

		for (int i = 0; i < 100; i++) {
			Hand zsHand = zhangsan.nexHand();
			Hand wwHand = wangwu.nexHand();

			if (zsHand.isWinThan(wwHand)) {
				zhangsan.win();
				wangwu.lose();
			} else if (zsHand.isLoseThan(wwHand)) {
				zhangsan.lose();
				wangwu.win();
			} else {
				zhangsan.even();
				wangwu.even();
			}
		}

		System.out.println("----------------------------");
		System.out.println(zhangsan.toString());
		System.out.println(wangwu.toString());
	}
}

组合模式(Composite)

定义:一种将对象组合成树状的层次结构的模式,用来表示“整体-部分”的关系,整体可以看作是容器,部分可以看作是内容。在容器中可以放入内容,也可以放入小容器,然后在小容器中可以放入更小的容器。这种结构模式,能够使容器与内容具有一致性,创造出递归模式。

优点

  • 组合模式可以一致的使用容器和内容,无须关心自己处理的是单个对象,还是组合对象,简化了客户端代码。
  • 组合休内增加新对象,不需要修改源代码,满足开闭原则。

角色

  • 树叶(Leaf) 叶节点对象,表示内容,该角色不能放入其他对象。

  • 复合物(Composite) 表示容器,可以放入 Leaf 和 Composite 角色。

  • 抽象组件(Component) 为 Leaf 和 Composite 角色定义公共接口,是其父类,使其具有一致性的角色。

示例:系统目录与文件结构

组合模式组合模式
/**
 * 抽象组件角色
 * 
 * @date 2021-06-22 10:59
 * @author Windus
 */
public interface Entry {
	/**
	 * 获取名字
	 * 
	 * @return
	 * @date 2021-06-22 11:02
	 * @author Windus
	 */
	String getName();

	/**
	 * 获取文件大小
	 * 
	 * @return
	 * @date 2021-06-22 11:02
	 * @author Windus
	 */
	int getSize();

	/**
	 * 添加条目
	 * 
	 * @param entry 条目(容器|内容)
	 * @throws Exception
	 * @return
	 * @date 2021-06-22 11:04
	 * @author Windus
	 */
	default Entry add(Entry entry) throws Exception {
		throw new Exception();
	}

	/**
	 * 打印列表
	 * 
	 * @return
	 * @date 2021-06-22 11:06
	 * @author Windus
	 */
	default void printList() {
		printList("");
	}

	/**
	 * 打印带前缀列表
	 * 
	 * @param prefix 前缀
	 * @return
	 * @date 2021-06-22 11:07
	 * @author Windus
	 */
	default void printList(String prefix) {
		System.out.println(prefix + "/" + printString());
	}

	/**
	 * 打印字符串
	 * 
	 * @return
	 * @date 2021-06-22 14:07
	 * @author Windus
	 */
	default String printString() {
		return getName() + "(" + getSize() + ")";
	}
}

/**
 * 目录类<br/>
 * 复合物角色
 * 
 * @date 2021-06-22 11:00
 * @author Windus
 */
public class Directory implements Entry {
    /**
     * 文件夹名
     */
    private String name;

    /**
     * 文件夹中条目集合
     */
    private ArrayList<Entry> items = new ArrayList<>();

    public Directory(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public int getSize() {
        int size = 0;

        for (Entry entry : items) {
            size += entry.getSize();
        }

        return size;
    }

    @Override
    public void printList(String prefix) {
        // 打印文件夹本身
        System.out.println(prefix + "/" + printString());

        // 打印文件夹内条目
        for (Entry entry : items) {
            entry.printList(prefix + "/" + name);
        }
    }

    @Override
    public Entry add(Entry entry) throws Exception {
        items.add(entry);

        return this;
    }
}

/**
 * 文件类<br/>
 * 树叶角色
 * 
 * @date 2021-06-22 11:00
 * @author Windus
 */
@AllArgsConstructor
public class File implements Entry {
    /**
     * 文件名
     */
    private String name;

    /**
     * 文件大小
     */
    private int size;

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public int getSize() {
        return this.size;
    }
}

public class App {
	public static void main(String[] args) {
		Directory rootDir = new Directory("root");
		Directory tmpDir = new Directory("tmp");
		Directory cacheDir = new Directory("cache");
		rootDir.add(tmpDir);
		tmpDir.add(cacheDir);

		File setting = new File("setting", 1024);
		cacheDir.add(setting);

		File log = new File("catalina.log", 800);
		tmpDir.add(log);

		// 打印 root 目录
		rootDir.printList();

		// 继续增加 bin 目录并打印
		Directory binDir = new Directory("bin");
		rootDir.add(binDir);

		File rm = new File("rm", 104);
		binDir.add(rm);

		rootDir.printList();
	}
}

上例中由于 File类不添加子类,所以不应该有 add 方法,于是就产生了 add 去哪的问题?

  • 定义在 Entry 中 使其什么都不做,或者抛出异常。这种模式叫做 透明组合模式
  • 声明在 Entry 中 只声明但不实现,子类根据需要实现该方法。使用这种方式,即使子类中不需要该方法,也必须定义该方法。
  • 只定义在 Directory 中 使用这种方式时,如果要向父类 Entry 变量(实际保存的是 Directory)中的 add 时需将它们的类型转换为 Directory 类型。这种模式叫做 安全组合模式

装饰器模式(Decorator)

定义:在不改变现有对象结构的情况下,动态的地给对象增加额外的功能。

优点

  • 在不改变原有对象的结构情况下,为对象扩充功能。
  • 使用不同装饰类,可以实现不同的效果,比继承更灵活。
  • 完全遵循开闭原则

角色

  • 抽象组件(Component) 增加功能时的核心角色,定义抽象接口。
  • 具体组件(ConcreteComponent) 实现 Component 角色所定义的接口。
  • 装饰物(Decorator) 具有与 Component 角色相同的接口,内部保存了被装饰的对象(Component)。
  • 具体装饰物(ConcreteDecorator) 实现 Decorator 角色的相关方法,给具体构件对象增加额外功能。

示例:数码宝贝进化

装饰器模式装饰器模式
/**
 * 数码宝贝<br/>
 * 抽象组件角色
 * 
 * @date 2021-06-24 09:43
 * @author Windus
 */
public abstract class AbstractDigimon {
    protected String name;
    protected String display;
    protected String uniqueSkills;

    public AbstractDigimon(String name) {
        this.name = name;
    }

    /**
     * 战斗
     * 
     * @return
     * @date 2021-06-24 10:15
     * @author Windus
     */
    protected abstract void fight();
}

/**
 * 浮游兽<br/>
 * 具体组件角色
 * 
 * @date 2021-06-24 09:52
 * @author Windus
 */
public class Poyomon extends AbstractDigimon {

    public Poyomon(String name) {
        super(name);
        this.display = "外观:「水母状漂浮」";
        this.uniqueSkills = "必杀技:「强酸泡泡」";
    }

    @Override
    protected void fight() {
        System.out.println(this.name);
        System.out.println(this.display);
        System.out.println(this.uniqueSkills);
        System.out.println("---------------------------------------");
    }
}

/**
 * 进化抽象类<br/>
 * 装饰物角色
 * 
 * @date 2021-06-24 09:56
 * @author Windus
 */
public abstract class AbstractEvolution extends AbstractDigimon {
    /**
     * 被装饰物
     */
    protected AbstractDigimon digimon;

    public AbstractEvolution(String name, AbstractDigimon digimon) {
        super(name);
        this.digimon = digimon;
    }

    @Override
    protected void fight() {
        digimon.fight();
        System.out.print("「" + digimon.name + "」进化 --->");
    }
}

/**
 * 巴达兽</br/>
 * 具体装饰物角色
 * 
 * @date 2021-06-24 09:55
 * @author Windus
 */
public class Patamon extends AbstractEvolution {

    public Patamon(String name, AbstractDigimon digimon) {
        super(name, digimon);
        this.display = "外观:「大耳朵哺乳类型数码兽」";
        this.uniqueSkills = "必杀技:「羽翼耳光」";
    }

    @Override
    protected void fight() {
        super.fight();
        System.out.println("「" + this.name + "」");
        System.out.println(this.display);
        System.out.println(this.uniqueSkills);
        System.out.println("---------------------------------------");
    }
}

/**
 * 天使兽</br>
 * 具体装饰物角色
 * 
 * @date 2021-06-24 09:49
 * @author Windus
 */
public class Angel extends AbstractEvolution {

    public Angel(String name, AbstractDigimon digimon) {
        super(name, digimon);
        this.display = "外观:「纯白之衣的天使数码兽」";
        this.uniqueSkills = "必杀技:「天堂之拳」";
    }

    @Override
    protected void fight() {
        super.fight();
        System.out.println("「" + this.name + "」");
        System.out.println(this.display);
        System.out.println(this.uniqueSkills);
        System.out.println("---------------------------------------");
    }
}

public class App {
    public static void main(String[] args) {
        AbstractDigimon poyomon = new Poyomon("浮游兽");
        AbstractDigimon patamon = new Patamon("巴达兽", poyomon);
        AbstractDigimon angel = new Angel("天使兽", patamon);

        poyomon.fight();
        patamon.fight();
        angel.fight();

        System.out.println("\n\n**********************************************************************\n\n");

        // 无限装饰
        AbstractDigimon evolution = new Angel("天使兽", new Patamon("巴达兽", new Poyomon("浮游兽")));
        evolution.fight();
    }
}

访问者模式(Visitor)

定义:将某种数据结构中各元素的操作分离出来,使其可以在不改变数据结构的情况下添加新的操作,为元素提供多种访问方式。

角色

  • 访问者(Visitor) 对数据结构中每个具体元素定义一个用于访问的 visit() 方法。
  • 具体访问者(ConcreteVisitor) 实现 Visitor 角色定义的接口。
  • 抽象元素(Element) 表示 Visitor 角色的访问对象,定义一个 accept() 方法,接收参数是 Visitor 角色对象。
  • 具体元素(ConcreteElement) 实现 Element 角色中定义的接口。
  • 对象结构(ObjectStructure) 负责处理 Element 角色的集合,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。

优缺点

  • 易于增加具体访问者。
  • 难于增加具体元素,因为需要修改原有代码。因此该模式通常用于对象结构相对稳定,但其操作算法经常变化的程序。

示例:结合组合模式,用访问者模式实现文件系统的访问。

访问者模式访问者模式
/**
 * 抽象元素角色
 * 
 * @date 2021-07-06 11:05
 * @author Windus
 */
public interface Element {
    /**
     * 接收 visitor 方法
     * 
     * @param visitor
     * @return
     * @date 2021-07-06 11:05
     * @author Windus
     */
    void accept(Visitor visitor);
}

/**
 * 组合模式抽象组件角色
 * 
 * @date 2021-07-06 11:08
 * @author Windus
 */
public interface Entry extends Element {

	/**
	 * 获取名字
	 * 
	 * @return
	 * @date 2021-07-06 11:09
	 * @author Windus
	 */
	String getName();

	/**
	 * 获取大小
	 * 
	 * @return
	 * @date 2021-07-06 11:09
	 * @author Windus
	 */
	int getSize();

	/**
	 * 添加条目
	 * 
	 * @param entry
	 * @return
	 * @throws Exception
	 * @date 2021-07-06 11:10
	 * @author Windus
	 */
	default Entry add(Entry entry) throws Exception {
		throw new Exception();
	}

	/**
	 * 迭代
	 * 
	 * @return
	 * @throws Exception
	 * @date 2021-07-06 11:12
	 * @author Windus
	 */
	default Iterator iterator() throws Exception {
		throw new Exception();
	}

	/**
	 * 打印字符串
	 * 
	 * @return
	 * @date 2021-07-06 11:11
	 * @author Windus
	 */
	default String printString() {
		return getName() + "(" + getSize() + ")";
	}
}

/**
 * 文件夹类 <br/>
 * 组合模式复合物角色<br/>
 * 具体元素角色<br/>
 * 对象结构角色 -> 实现了 iterator() 方法
 * 
 * @date 2021-07-06 11:16
 * @author Windus
 */
@Data
public class Directory implements Entry {
	/**
	 * 文件夹名
	 */
	private String name;

	private List<Entry> dir = new ArrayList<>();

	public Directory(String name) {
		this.name = name;
	}

	@Override
	public String getName() {
		return this.name;
	}

	@Override
	public int getSize() {
		int size = 0;
		Iterator<Entry> iterator = iterator();
		while (iterator.hasNext()) {
			Entry entry = iterator.next();
			size += entry.getSize();
		}
		return size;
	}

	@Override
	public Entry add(Entry entry) throws Exception {
		dir.add(entry);
		return this;
	}

	@Override
	public Iterator<Entry> iterator() throws Exception {
		return dir.iterator();
	}

	@Override
	public void accept(Visitor visitor) {
		visitor.visit(this);
	}
}

/**
 * 文件类<br/>
 * 组合模式树叶角色<br/>
 * 具体元素角色
 * 
 * @date 2021-07-06 11:14
 * @author Windus
 */
@Data
@AllArgsConstructor
public class File implements Entry {
	/**
	* 文件名
	*/
	private String name;

	/**
	* 文件大小
	*/
	private int size;

	@Override
	public String getName() {
		return this.name;
	}

	@Override
	public int getSize() {
		return this.size;
	}

	@Override
	public void accept(Visitor visitor) {
		visitor.visit(this);
	}
}

/**
 * 访问者角色
 * 
 * @date 2021-07-06 11:03
 * @author Windus
 */
public interface Visitor {
	/**
	 * 访问文件
	 * 
	 * @param file
	 * @return
	 * @date 2021-07-06 11:15
	 * @author Windus
	 */
	void visit(File file);

	/**
	 * 访问文件夹
	 * 
	 * @param directory
	 * @return
	 * @date 2021-07-06 11:17
	 * @author Windus
	 */
	void visit(Directory directory);
}

/**
 * 具体访问者角色
 * 
 * @date 2021-07-06 13:39
 * @author Windus
 */
public class ListVisitor implements Visitor {
	/**
	 * 当前目录
	 */
	private String currentDir = "";

	@Override
	public void visit(File file) {
		System.out.println(currentDir + java.io.File.separator + file.printString());
	}

	@Override
	public void visit(Directory directory) {
		System.out.println(currentDir + java.io.File.separator + directory.printString());
		String tempDir = currentDir;
		currentDir = currentDir + java.io.File.separator + directory.getName();
		Iterator<Entry> iterator = directory.iterator();
		while (iterator.hasNext()) {
			Entry entry = iterator.next();
			entry.accept(this);
		}
		currentDir = tempDir;
	}
}

public class App {
	public static void main(String[] args) {
		Directory root = new Directory("root");
		Directory bin = new Directory("bin");
		Directory tmp = new Directory("tmp");

		File vi = new File("vi", 10000);
		File latex = new File("latex", 20000);

		root.add(bin);
		root.add(tmp);

		bin.add(vi);
		bin.add(latex);

		root.accept(new ListVisitor());
	}
}

责任链模式(Chain of Responsibility)

定义:将多个对象组成一条链表结构,然后将一个请求发送这个链表,让对象按照它们在链上的顺序一个一个地找出到底该谁来负责处理请求。

可以把它想像成推卸责任的结构,有利于记忆。

角色

  • 抽象处理者 (Handler) 定义处理请求的接口,包含一个处理方法和下一个处理者(Handler)。
  • 具体处理者 (Concrete Handler) 实现抽象处理者的方法,实际处理请求。当自己无法处理请求时,将请求转给下一个处理者。
  • 请求者 (Client) 创建处理请求链,并向链头第一个处理者发送请求。

优点

  • 弱化了发出请求和处理请求人之间的关系。
  • 可以动态的改变职责链,增强了扩展性。
  • 各个职责独立实现业务处理互不干扰。

缺点

  • 导致请求处理速度延迟,如果请求者和处理者关系明确,不建议使用。
  • 当责任链过长时,处理对象增长,导致性能下降。

示例:公司员工请假审批流程

责任链模式责任链模式
/**
 * 请假流程对象
 * 
 * @date 2021-07-23 14:28
 * @author Windus
 */
@Data
@AllArgsConstructor
public class LeaveProcess {
	/**
	 * 请假标题
	 */
	private String title;

	/**
	 * 请假原因
	 */
	private String content;

	/**
	 * 请假天数
	 */
	private Integer days;
}

/**
 * 领导抽象类(抽象处理者角色)
 * 
 * @date 2021-07-23 14:13
 * @author Windus
 */
@ToString
public abstract class AbstractLeader {
	/**
	 * 职称
	 */
	protected String title;

	/**
	 * 上级领导
	 */
	protected AbstractLeader superior;

	public AbstractLeader(String title) {
		this.title = title;
	}

	public AbstractLeader setSuperior(AbstractLeader superior) {
		this.superior = superior;
		return superior;
	}

	protected void done(LeaveProcess leaveProcess) {
		System.out.println(leaveProcess + "被" + this.title + "审核通过!");
	}

	protected void failed(LeaveProcess leaveProcess) {
		System.out.println(leaveProcess + "被" + this.title + "审核拒绝!");
	}

	/**
	 * 处理请求
	 * 
	 * @param leaveProcess 请假流程
	 * 
	 * @date 2021-07-23 14:29
	 * @author Windus
	 */
	public abstract void toProcess(LeaveProcess leaveProcess);
}

/**
 * 主管(具体处理者角色)
 * 
 * @date 2021-07-23 14:53
 * @author Windus
 */
public class Direcoter extends AbstractLeader {
	/**
	 * 权限天数
	 */
	private final int PERMISSIONS = 2;

	public Direcoter(String title) {
		super(title);
	}

	@Override
	public void toProcess(LeaveProcess leaveProcess) {
		if (leaveProcess.getDays() > PERMISSIONS) {
			superior.toProcess(leaveProcess);
		} else {
			done(leaveProcess);
		}
	}
}

/**
 * 部门经理
 * 
 * @date 2021-07-23 15:46
 * @author Windus
 */
public class Manager extends AbstractLeader {
	/**
	 * 权限天数(具体处理者角色)
	 */
	private final int PERMISSIONS = 5;

	public Manager(String title) {
		super(title);
	}

	@Override
	public void toProcess(LeaveProcess leaveProcess) {
		if (leaveProcess.getDays() > PERMISSIONS) {
			superior.toProcess(leaveProcess);
		} else {
			done(leaveProcess);
		}
	}
}

/**
 * 总监(具体处理者角色)
 * 
 * @date 2021-07-23 15:07
 * @author Windus
 */
public class Supervisor extends AbstractLeader {
	/**
	 * 权限天数
	 */
	private final int PERMISSIONS = 10;

	public Supervisor(String title) {
		super(title);
	}

	@Override
	public void toProcess(LeaveProcess leaveProcess) {
		if (leaveProcess.getDays() > PERMISSIONS) {
			failed(leaveProcess);
		} else {
			done(leaveProcess);
		}
	}
}

/**
 * 请求者角色
 * 
 * @date 2021-07-23 17:10
 * @author Windus
 */
public class App {
	public static void main(String[] args) {
		AbstractLeader direcoter = new Direcoter("主管");
		AbstractLeader manager = new Manager("经理");
		AbstractLeader supervisor = new Supervisor("总监");

		// 设置审批流程
		direcoter.setSuperior(manager).setSuperior(supervisor);

		// 请假两天
		LeaveProcess twoDays = new LeaveProcess("请假", "家里有事儿!", 2);
		direcoter.toProcess(twoDays);

		// 请假三天
		LeaveProcess threeDays = new LeaveProcess("请假", "身体不舒服!", 3);
		direcoter.toProcess(threeDays);

		// 请假六天
		LeaveProcess sixDays = new LeaveProcess("请假", "休年假!", 6);
		direcoter.toProcess(sixDays);

		// 请假一年
		LeaveProcess oneYear = new LeaveProcess("请假", "世界那么大,我想去看看!", 365);
		direcoter.toProcess(oneYear);
	}
}

执行结果:
----------------------------------------------------------------------------
LeaveProcess(title=请假, content=家里有事儿!, days=2)被主管审核通过!
LeaveProcess(title=请假, content=身体不舒服!, days=3)被经理审核通过!
LeaveProcess(title=请假, content=休年假!, days=6)被总监审核通过!
LeaveProcess(title=请假, content=世界那么大,我想去看看!, days=365)被总监审核拒绝!

外观模式(Facade)

定义:又叫门面模式,为互相关联在一起错综复杂的子系统整理出高层接口,让系统对外只开放一些简单的接口。外部应用程序不用关心内部子系统的具体细节,降低应用程序的复杂度,提高了程序的可维护性。

角色

  • 外观角色(Facade) 也叫窗口角色,为子系统对外提供简单的高层接口。
  • 子系统角色(Sub System) 实现系统的部分功能,请求者角色可以通过外观角色访问它,而它们并不知道外观角色的存在。
  • 请求者角色(Client) 负责调用外观角色,但其并不包含在外观模式中。

优点

  • 降低了子系统与客户端之间的耦合度,是“迪米特法则”的典型应用。
  • 对客户屏蔽了子系统组件,减少了客户的API调用接口,使子系统更加容易被使用。

缺点

  • 不能完全的限制客户使用子系统,容易存在未知风险。
  • 增加子类系统时,需要修改外观,违背了“开闭原则”。

示例

门面模式门面模式
/**
 * 子系统(一)
 * 
 * @date 2021-07-26 10:27
 * @author Windus
 */
public class SubSystemOne {
	public void excute(){
		System.out.println("子系统一被调用!");
	}
}

/**
 * 子系统(二)
 * 
 * @date 2021-07-26 10:27
 * @author Windus
 */
public class SubSystemTwo {
	public void excute() {
		System.out.println("子系统二被调用!");
	}
}

/**
 * 子系统(三)
 * 
 * @date 2021-07-26 10:27
 * @author Windus
 */
public class SubSystemThree {
	public void excute() {
		System.out.println("子系统三被调用!");
	}
}

/**
 * 外观角色
 * 
 * @date 2021-07-26 10:30
 * @author Windus
 */
public class Facade {
	private SubSystemOne systemOne = new SubSystemOne();
	private SubSystemTwo systemTwo = new SubSystemTwo();
	private SubSystemThree systemThree = new SubSystemThree();

	public void excute() {
		systemOne.excute();
		systemTwo.excute();
		systemThree.excute();
	}
}

/**
 * 请求者角色
 * 
 * @date 2021-07-26 10:31
 * @author Windus
 */
public class App {
	public static void main(String[] args) {
		Facade facade = new Facade();
		facade.excute();
	}
}

如果门面接口很少,可以将它跟非门面接口放到一块,不需要特殊标记,当作普通接口调用即可。如果门面接口很多,可以在已有的接口之上,抽象出一层,专门放置门面接口,如果门面接口特别多,并且很多都是跨多个子系统的,可以将门面接口抽象出一个门面子系统单独调用和维护。

中介者模式(Mediator)

定义:用一个中介者对象封装一系列的对象交互,使各个对象不需要显示的互相引用,松散耦合,并且可以独立改变一组对象之间的交互关系,而不影响其他对象。

角色

  • 中介者(Mediator) 定义了 Colleague 角色注册与转发的接口。
  • 具体中介者(Concrete Mediator) 实现中介者接口,协调 Colleague 角色之间的交互关系。
  • 同事(Colleague) 定义与 Mediator 角色进行通信的接口
  • 具体同事(Concrete Colleague) 实现 Colleague 接口,当需要与其他同事交互时,由 Mediator 负责交互。

优点

  • 降低了对象之间的耦合性,符合迪米特法则。
  • 将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。

缺点

  • 当同事类越多时,中介者越臃肿。

示例:中介公司买房与卖房

中介者模式中介者模式
/**
 * 中介公司(中介者角色)
 * 
 * @date 2021-08-02 14:40
 * @author Windus
 */
public interface IntermediaryCompany {
	/**
	 * 登记请求
	 * 
	 * @param trader
	 * @return
	 * @date 2021-08-02 14:44
	 * @author Windus
	 */
	void register(AbstractTrader trader);

	/**
	 * 转发请求
	 * 
	 * @param trader
	 * @return
	 * @date 2021-08-02 14:49
	 * @author Windus
	 */
	void relay(AbstractTrader trader);
}

/**
 * 链家(具体中介者角色
 * 
 * @date 2021-08-02 14:56
 * @author Windus
 */
public class Lianjia implements IntermediaryCompany {
	private List<AbstractTrader> sellerList = new ArrayList<>();
	private List<AbstractTrader> buyerList = new ArrayList<>();

	@Override
	public void register(AbstractTrader trader) {
		List<AbstractTrader> registerList = null;
		if (trader instanceof Seller) {
			registerList = sellerList;
		} else if (trader instanceof Buyer) {
			registerList = buyerList;
		}

		if (!registerList.contains(trader)) {
			registerList.add(trader);
		}
		trader.setIntermediaryCompany(this);
	}

	@Override
	public void relay(AbstractTrader trader) {
		List<AbstractTrader> relayList = null;
		if (trader instanceof Buyer) {
			relayList = sellerList;
		} else if (trader instanceof Seller) {
			relayList = buyerList;
		}

		for (AbstractTrader t : relayList) {
			House house = trader.getHouse();
			if (house != null) {
				if (house.equals(t.getHouse())) {
					trader.receive(t);
				}
			}
		}
	}
}

/**
 * 房子对象
 * 
 * @date 2021-08-02 15:00
 * @author Windus
 */
@Data
@AllArgsConstructor
@EqualsAndHashCode
public class House {
	/**
	 * 建筑面积(平方米)
	 */
	private Integer builtUpArea;

	/**
	 * 房间数
	 */
	private Integer rooms;

	/**
	 * 总价(万元)
	 */
	private Integer totalPrice;

	@Override
	public boolean equals(Object obj) {
		if (obj != null && obj instanceof House) {
			House house = (House) obj;
			if (Math.abs(this.totalPrice - house.totalPrice) <= 50) {
				return true;
			}
		}

		return false;
	}
  
  @Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((builtUpArea == null) ? 0 : builtUpArea.hashCode());
		result = prime * result + ((rooms == null) ? 0 : rooms.hashCode());
		result = prime * result + ((totalPrice == null) ? 0 : totalPrice.hashCode());
		return result;
	}
}

/**
 * 交易人(同事角色)
 * 
 * @date 2021-08-02 14:42
 * @author Windus
 */
@Getter
@ToString(exclude = "intermediaryCompany")
public abstract class AbstractTrader {
	/**
	 * 名字
	 */
	protected String name;

	/**
	 * 房子信息
	 */
	protected House house;

	/**
	 * 中介公司
	 */
	protected IntermediaryCompany intermediaryCompany;

	public AbstractTrader(String name) {
		this.name = name;
	}

	/**
	 * 设置中介公司
	 * 
	 * @param intermediaryCompany 中介公司
	 * @return
	 * @date 2021-08-02 14:51
	 * @author Windus
	 */
	protected void setIntermediaryCompany(IntermediaryCompany intermediaryCompany) {
		this.intermediaryCompany = intermediaryCompany;
	}

	/**
	 * 发送请求
	 * 
	 * @param house 房子信息
	 * @return
	 * @date 2021-08-02 14:52
	 * @author Windus
	 */
	public abstract void send(House house);

	/**
	 * 接收应答
	 * 
	 * @param trader 交易人
	 * @return
	 * @date 2021-08-02 14:52
	 * @author Windus
	 */
	public abstract void receive(AbstractTrader trader);
}

/**
 * 卖方(具体同事角色)
 * 
 * @date 2021-08-02 15:21
 * @author Windus
 */
@Getter
public class Seller extends AbstractTrader {

	public Seller(String name) {
		super(name);
	}

	@Override
	public void receive(AbstractTrader trader) {
		System.out.println("|");
		System.out.println("|___ 买家[" + trader + "]");
	}

	@Override
	public void send(House house) {
		this.house = house;
		System.out.println("卖家[" + this.name + "],发布信息" + house);
		intermediaryCompany.relay(this);
	}
}

/**
 * 买方(具体同事角色)
 * 
 * @date 2021-08-02 15:21
 * @author Windus
 */
@Getter
public class Buyer extends AbstractTrader {

	public Buyer(String name) {
		super(name);
	}

	@Override
	public void receive(AbstractTrader trader) {
		System.out.println("|");
		System.out.println("|___ 卖家[" + trader + "]");
	}

	@Override
	public void send(House house) {
		this.house = house;
		System.out.println("买家[" + this.name + "],发布信息" + house);
		intermediaryCompany.relay(this);
	}
}

public class App {
	public static void main(String[] args) {
		System.out.println("\n");
		// 定义中介公司
		IntermediaryCompany lianjia = new Lianjia();

		// 登记卖房人
		AbstractTrader zhangsanSeller = new Seller("张三");
		lianjia.register(zhangsanSeller);

		AbstractTrader lisiSeller = new Seller("李四");
		lianjia.register(lisiSeller);

		AbstractTrader qianqiSeller = new Seller("钱七");
		lianjia.register(qianqiSeller);

		// 登记买房人
		AbstractTrader wangwuBuyer = new Buyer("王五");
		lianjia.register(wangwuBuyer);

		AbstractTrader zhaoliuBuyer = new Buyer("赵六");
		lianjia.register(zhaoliuBuyer);

		// 发布卖房信息
		zhangsanSeller.send(new House(80, 2, 300));
		lisiSeller.send(new House(120, 3, 350));
		System.out.println("\n");

		// 发布买房信息
		wangwuBuyer.send(new House(80, 2, 310));
		System.out.println("\n");
		zhaoliuBuyer.send(new House(150, 4, 800));
		System.out.println("\n");

		// 发布卖房信息
		qianqiSeller.send(new House(160, 4, 850));

	}
}

备忘录模式(Memento)

定义:又叫快照模式,在不破坏封装性的前提下,通过引入表示实例状态的角色,可以保存和恢复实例的模式。

角色

  • 发起人(Originator) 保存自己最新状态时,创建 Memento(备忘录)。当把以前保存的 Memento(备忘录) 传递给 Originator(发起人) 时,会将自己恢复到生成该 Memento(备忘录) 时的状态。
  • 备忘录(Memento) 负责存储 Originator(发起人) 内部信息,但不会向外公开这些信息。
  • 管理者(Caretaker) 对 Memento(备忘录) 进行管理,负责发起保存或恢复 Memento(备忘录) 功能,不能对 Memento(备忘录) 内容进行访问和修改。

备忘录(Memento) 对外提供两种接口:

  • 宽接口(wide interface),用于恢复对象信息的接口集合,会暴露 Memento(备忘录) 内部所有信息,因此只对 Originator(发起人) 开放。
  • 窄接口(narrow interface),只能获取有限的内部信息,防止信息泄露,通常对 Caretaker(管理者) 开放。

生成 Memento(备忘录) 实例后,Caretaker(管理者) 会一直保存该实例,用于将来恢复该实例。不过 Caretaker(管理者) 只能使用 窄接口(narrow interface),因此它无法访问 Memento(备忘录) 中所有信息,它只是将 Memento(备忘录) 当作是一个黑盒子保存起来。

优点

  • 它提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤。
  • 备忘录实现了对信息的封装,只允许发起人进行访问和修改。
  • 管理者负责发起创建和撤销备忘录动作,发起人负责生成和恢复备忘录。两者各司其职,当需求改变时不需要修改发起人角色代码,并且满足单一职责原则。

缺点

  • 资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。

示例:掷骰子游戏,在路上。

备忘录模式备忘录模式
/**
 * 备忘录角色
 * 
 * @date 2021-08-03 15:45
 * @author Windus
 */
public class Memento {
	/**
	 * 游戏总步数
	 */
	private int totalStep;

	/**
	 * 当前步数
	 */
	private int currentStep;

	/**
	 * 游戏关卡
	 */
	private String[] gamePoint;

	/**
	 * 构造函数 wide interface
	 * 
	 * @date 2021-08-03 17:29
	 * @author Windus
	 */
	Memento(int totalStep, int currentStep, String[] gamePoint) {
		this.totalStep = totalStep;
		this.currentStep = currentStep;
		this.gamePoint = gamePoint;
	}

	/**
	 * 获取总步数 narrow interface
	 * 
	 * @return
	 * @date 2021-08-03 17:31
	 * @author Windus
	 */
	int getTotalStep() {
		return this.totalStep;
	}

	/**
	 * 获取游戏关卡 wide interface
	 * 
	 * @return
	 * @date 2021-08-03 17:30
	 * @author Windus
	 */
	String[] getGamePoint() {
		return this.gamePoint;
	}

	/**
	 * 获取当前步数 narrow interface
	 * 
	 * @return
	 * @date 2021-08-03 17:31
	 * @author Windus
	 */
	public int getCurrentStep() {
		return this.currentStep;
	}
}

/**
 * 掷骰子游戏(发起人角色)
 * 
 * @date 2021-08-03 15:46
 * @author Windus
 */
public class RollDice {
	/**
	 * 游戏总步数
	 */
	private int totalStep;

	/**
	 * 当前步数
	 */
	@Getter
	private int currentStep;

	/**
	 * 游戏关卡
	 */
	private String[] gamePoint;

	/**
	 * 关卡符号<br/>
	 * + 前进步数 - 后退步数 * 直接到终点 / 回退到起点
	 */
	private String[] pointSymbols = { "+1", "-2", "*", "+0", "/", "+2" };

	private Random random = new Random();

	public RollDice(int point) {
		// 初始化关卡
		this.totalStep = point;
		gamePoint = new String[totalStep];
		System.out.println("\n---------------生成游戏地图------------------");
		for (int i = 0; i < point; i++) {
			int index = random.nextInt(6);
			gamePoint[i] = pointSymbols[index];
			System.out.print("[" + (i + 1) + "]" + pointSymbols[index] + "\t");
		}
		System.out.println("\n---------------生成游戏地图------------------");
	}

	/**
	 * 掷骰子
	 * 
	 * @return
	 * @date 2021-08-03 15:58
	 * @author Windus
	 */
	public void roll() {
		int dice = random.nextInt(6) + 1;
		currentStep += dice;

		System.out.print("骰子点数[" + dice + "]\t");
		if (currentStep < totalStep) {
			String pointSymbol = gamePoint[currentStep - 1];
			switch (pointSymbol.charAt(0)) {
				case '+':
					currentStep += Integer.parseInt(pointSymbol.substring(1));
					System.out.print("前进[" + pointSymbol.charAt(1) + "]步");
					break;
				case '-':
					currentStep -= Integer.parseInt(pointSymbol.substring(1));
					System.out.print("后退[" + pointSymbol.charAt(1) + "]步");
					break;
				case '*':
					currentStep = totalStep;
					System.out.print("搭上顺风车,到达终点^_^");
					break;
				default:
					currentStep = 0;
					System.out.print("遇到情况,回到起点-_-!");
					break;
			}
		}

		correctionStep();

		System.out.println(",当前步数[" + currentStep + "]");

		if (currentStep >= totalStep) {
			System.out.println("游戏结束!");
		}
	}

	/**
	 * 创建备忘录
	 * 
	 * @return
	 * @date 2021-08-03 17:34
	 * @author Windus
	 */
	public Memento createMemento() {
		return new Memento(totalStep, currentStep, gamePoint);
	}

	/**
	 * 恢复备忘录
	 * 
	 * @param memento 恢复对象
	 * @return
	 * @date 2021-08-03 17:35
	 * @author Windus
	 */
	public void restoreMemento(Memento memento) {
		this.currentStep = memento.getCurrentStep();
		this.totalStep = memento.getTotalStep();
		this.gamePoint = memento.getGamePoint();
	}

	/**
	 * 校正当前步数
	 * 
	 * @return
	 * @date 2021-08-03 17:10
	 * @author Windus
	 */
	private void correctionStep() {
		if (this.currentStep < 0) {
			this.currentStep = 0;
		} else if (this.currentStep > this.totalStep) {
			this.currentStep = totalStep;
		}
	}
}

/**
 * 管理者角色
 * 
 * @date 2021-08-03 15:46
 * @author Windus
 */
public class App {
	public static void main(String[] args) {
		int totalStep = 10;
		RollDice rollDice = new RollDice(totalStep);
		// 创建备忘录
		Memento memento = rollDice.createMemento();

		int loopIndex = 0;
		while (rollDice.getCurrentStep() < totalStep) {
			loopIndex++;
			rollDice.roll();

			if (rollDice.getCurrentStep() >= totalStep && loopIndex < 3) {
				System.out.println("\n\n太简单了,再来一次......\n\n");
				// 恢复备忘录
				rollDice.restoreMemento(memento);
			}
		}
	}
}

状态模式(State)

定义:用类对象表示状态,把复杂的“逻辑判断”提取到不同的状态对象中,用类表示状态,用方法来判断状态。

角色

  • 状态(State) 定义根据不同状态进行不同处理的接口。
  • 具体状态(Concrete State) 实现 State 角色,表示各个具体的状态。
  • 上下文(Context) 定义了客户端调用 State 模式的接口,内部维护一个状态,负责具体状态的切换。

优点

  • 结构清晰,将不同状态的行为分割开来,容易增加新状态,易扩展,满足“单一职责原则”。
  • 状态交给上下文类管理,减少对象间的相互依赖。

缺点

  • 类对象数量增多,增加系统复杂性。
  • 增加新状态时,需要修改上下文类,违背“开闭原则”。

示例:公路电子测速

状态模式状态模式
/**
 * 状态角色
 * 
 * @date 2021-08-04 10:30
 * @author Windus
 */
@Getter
public abstract class AbstractState {
	public final int speedLimit;

	public AbstractState(int speed) {
		this.speedLimit = speed;
	}

	/**
	 * 低速
	 * 
	 * @return
	 * @date 2021-08-04 10:32
	 * @author Windus
	 */
	abstract void lowSpeed();

	/**
	 * 超速 10%
	 * 
	 * @return
	 * @date 2021-08-04 10:32
	 * @author Windus
	 */
	abstract void overPercentOfTen();

	/**
	 * 超速 20%
	 * 
	 * @return
	 * @date 2021-08-04 10:32
	 * @author Windus
	 */
	abstract void overPercentOfTwenty();

	/**
	 * 超速 50%
	 * 
	 * @return
	 * @date 2021-08-04 10:32
	 * @author Windus
	 */
	abstract void overPercentOfFifty();

	/**
	 * 超速 70%
	 * 
	 * @return
	 * @date 2021-08-04 10:32
	 * @author Windus
	 */
	abstract void overPercentOfSeventy();
}

/**
 * 乡村道路(具体状态角色)
 * 
 * @date 2021-08-04 10:38
 * @author Windus
 */
public class CountryRoad extends AbstractState {

	public CountryRoad(int speed) {
		super(speed);
	}

	@Override
	public void lowSpeed() {
		System.out.println("低速行驶,予以警告!");
	}

	@Override
	public void overPercentOfTen() {
		System.out.println("超速10%以上,扣3分,罚款100!");
	}

	@Override
	public void overPercentOfTwenty() {
		System.out.println("超速20%以上,扣3分,罚款150!");
	}

	@Override
	public void overPercentOfFifty() {
		System.out.println("超速50%以上,扣12分,罚款500!");
	}

	@Override
	public void overPercentOfSeventy() {
		System.out.println("超速70%以上,扣12分,罚款1000!");
	}

	@Override
	public String toString() {
		return "CountryRoad 限速:[" + speedLimit + "]";
	}
}

/**
 * 城市道路(具体状态角色)
 * 
 * @date 2021-08-04 10:53
 * @author Windus
 */
public class CityRoad extends AbstractState {

	public CityRoad(int speed) {
		super(speed);
	}

	@Override
	public void lowSpeed() {
		System.out.println("低速行驶,扣3分,罚款100!");
	}

	@Override
	public void overPercentOfTen() {
		System.out.println("超速10%以上,扣3分,罚款150!");
	}

	@Override
	public void overPercentOfTwenty() {
		System.out.println("超速20%以上,扣3分,罚款200!");
	}

	@Override
	public void overPercentOfFifty() {
		System.out.println("超速50%以上,扣12分,罚款1000!");
	}

	@Override
	public void overPercentOfSeventy() {
		System.out.println("超速70%以上,扣12分,罚款1500!");
	}

	@Override
	public String toString() {
		return "CityRoad 限速:[" + speedLimit + "]";
	}
}

/**
 * 高速公路(具体状态角色)
 * 
 * @date 2021-08-04 10:54
 * @author Windus
 */
public class Freeway extends AbstractState {

	public Freeway(int speed) {
		super(speed);
	}

	@Override
	public void lowSpeed() {
		System.out.println("低速行驶,扣3分,罚款200!");
	}

	@Override
	public void overPercentOfTen() {
		System.out.println("超速10%以上,扣3分,罚款200!");
	}

	@Override
	public void overPercentOfTwenty() {
		System.out.println("超速10%以上,扣3分,罚款200!");
	}

	@Override
	public void overPercentOfFifty() {
		System.out.println("超速50%以上,扣12分,罚款1500!");
	}

	@Override
	public void overPercentOfSeventy() {
		System.out.println("超速70%以上,扣12分,罚款2000!");
	}

	@Override
	public String toString() {
		return "Freeway 限速:[" + speedLimit + "]";
	}
}

/**
 * 电子眼上下文
 * 
 * @date 2021-08-04 13:42
 * @author Windus
 */
public class ElectronicEyeContext {
	private AbstractState state;

	public ElectronicEyeContext(AbstractState state) {
		changeState(state);
	}

	/**
	 * 改变状态
	 * 
	 * @param state 状态
	 * @return
	 * @date 2021-08-04 14:00
	 * @author Windus
	 */
	public void changeState(AbstractState state) {
		System.out.println("切换公路类型" + state);
		this.state = state;
	}

	/**
	 * 监控
	 * 
	 * @param speed 车速
	 * @return
	 * @date 2021-08-04 13:59
	 * @author Windus
	 */
	public void monitor(int speed) {
		int overSpeed = speed - state.speedLimit;
		if (state.getSpeedLimit() * 0.7 < overSpeed) {
			state.overPercentOfSeventy();
		} else if (state.getSpeedLimit() * 0.5 < overSpeed) {
			state.overPercentOfFifty();
		} else if (state.getSpeedLimit() * 0.2 < overSpeed) {
			state.overPercentOfTwenty();
		} else if (state.getSpeedLimit() * 0.1 < overSpeed) {
			state.overPercentOfTen();
		} else if (speed > 10 && speed < 30) {
			state.lowSpeed();
		}
	}
}

public class App {
	public static void main(String[] args) {
		// 道路状态
		AbstractState countryRoad = new CountryRoad(80);
		AbstractState cityRoad = new CityRoad(100);
		AbstractState freeway = new Freeway(120);

		ElectronicEyeContext electronicEyeContext = new ElectronicEyeContext(countryRoad);

		int speed = 0;
		while ((speed += 10) < 204) {
			System.out.print("当前车速:[" + speed + "]\t");
			electronicEyeContext.monitor(speed);
			System.out.println("");

			if (speed == 170) {
				electronicEyeContext.changeState(freeway);
			} else if (speed == 140) {
				electronicEyeContext.changeState(cityRoad);
			}
		}
	}
}

享元模式(Flyweight)

定义:Flyweight是拳击比赛中选手体重最轻的等级,用在这里表示特别小的对象,所以享元模式也就可以理解为共享元对象,即尽量共享实例减少对象占用的内存空间。

角色

  • 享元角色(Flyweight) 抽象对象,定义享元规范的公共接口。
  • 具体享元角色(Concrete Flyweight) 可共享对象,实现 Flyweight 角色中定义的接口。
  • 非享元角色(Unsharable Concrete Flyweight) 非共享对象。
  • 享元工厂(Flyweight Factory) 负责创建和管理享元对象。

状态

  • 内部状态,指对象共享出来的信息,存储在享元对象内部并且不会随环境的改变而改变,其他对象只能读取但不能修改其数值。 (比如,连接池中的连接对象,保存在连接对象中的用户名、密码、连接url等信息,在创建对象的时候就设置好了,不会随环境的改变而改变,这些是内部状态)。
  • 外部状态,指对象得以依赖得一个标志,是随环境改变而改变的、不可共享的状态。

享元模式建议不在对象中存储外在状态, 而是将其传递给依赖于它的一个特殊方法。 程序只在对象中保存内在状态, 以方便在不同情景下重用。 这些对象的区别仅在于其内在状态 (与外在状态相比, 内在状态的变体要少很多), 因此所需的对象数量会大大削减。

优点

  • 减少对象创建,降低内存,提高效率。

缺点

  • 为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。

示例:下围棋

享元模式享元模式
/**
 * 棋子(享元角色)
 * @date 2021-08-09 16:52
 * @author Windus
 * 
 * 控制台打印颜色
#格式:
  设置颜色开始 :\033[显示方式;前景色;背景色m
#说明:
前景色            背景色           颜色
---------------------------------------
30                40              黑色
31                41              红色
32                42              绿色
33                43              黃色
34                44              蓝色
35                45              紫红色
36                46              青蓝色
37                47              白色
显示方式           意义
-------------------------
0                终端默认设置
1                高亮显示
4                使用下划线
5                闪烁
7                反白显示
8                不可见
-----------------------------
#例子:
\033[1;31;40m    <!--1-高亮显示 31-前景色红色  40-背景色黑色-->
\033[0m          <!--采用终端默认设置,即取消颜色设置-->
 */
public interface Piece {
	/**
	 * 下子
	 * 
	 * @param point 位置
	 * @return
	 * @date 2021-08-09 16:52
	 * @author Windus
	 */
	void downPieces(Point point);
}

/**
 * 白子(具体享元角色)
 * 
 * @date 2021-08-09 16:53
 * @author Windus
 */
public class WhitePiece implements Piece {
	/**
	 * 颜色
	 */
	private String color;

	/**
	 * 半径
	 */
	private Integer radius;

	public WhitePiece() {
		this.color = "\033[0;37;43m";
		this.radius = 30;
	}

	@Override
	public void downPieces(Point point) {
		System.out.println(this.color + "白方落子,大小:[" + this.radius + "],位置:[" + point + "]\033[0m");
	}
}

/**
 * 黑子(具体享元角色)
 * 
 * @date 2021-08-09 17:12
 * @author Windus
 */
public class BlackPiece implements Piece {
	/**
	 * 颜色
	 */
	private String color;

	/**
	 * 半径
	 */
	private Integer radius;

	public BlackPiece() {
		this.color = "\033[0;30;43m";
		this.radius = 30;
	}

	@Override
	public void downPieces(Point point) {
		System.out.println(this.color + "落子,大小:[" + this.radius + "],位置:[" + point + "]\033[0m");
	}
}

/**
 * 围棋工厂类(享元工厂角色)
 * 
 * @date 2021-08-09 17:39
 * @author Windus
 */
public class WeiqiFactory {
	private static WeiqiFactory weiqiFactory = new WeiqiFactory();

	/**
	 * 享元对象 map
	 */
	private Map<String, Piece> pieceMap = new ConcurrentHashMap<>();

	private WeiqiFactory() {
	}

	/**
	 * 获取工厂实例(单例模式)
	 * 
	 * @return
	 * @date 2021-08-09 17:41
	 * @author Windus
	 */
	public static WeiqiFactory getInstance() {
		return weiqiFactory;
	}

	/**
	 * 获取棋子
	 * 
	 * @param key 享元对象存储key
	 * @return
	 * @date 2021-08-09 17:42
	 * @author Windus
	 */
	public Piece getPiece(String key) {
		Piece piece = pieceMap.get(key);
		if (piece == null) {
			if ("w".equals(key)) {
				piece = new WhitePiece();
			} else {
				piece = new BlackPiece();
			}
			pieceMap.put(key, piece);
		}

		return piece;
	}
}

/**
 * 落子位置(非享元角色)
 * 
 * @date 2021-08-09 17:15
 * @author Windus
 */
@ToString
@AllArgsConstructor
public class Point {
	/**
	 * 横坐标
	 */
	private Integer x;

	/**
	 * 纵坐标
	 */
	private Integer y;
}

public class App {
	public static void main(String[] args) {
		WeiqiFactory weiqiFactory = WeiqiFactory.getInstance();
		Random random = new Random();
		for (int i = 0; i < 10; i++) {
			// 白子先行		
			Point wPoint = new Point(random.nextInt(), random.nextInt());
			Piece white = weiqiFactory.getPiece("w");
			white.downPieces(wPoint);

			// 围棋落子
			Point bPoint = new Point(random.nextInt(), random.nextInt());
			Piece black =  weiqiFactory.getPiece("b");
			black.downPieces(bPoint);
		}
	}
}

命令模式(Command)

定义:将一个请求封装为一个对象,该转换可以根据不同的请求将方法参数化、 延迟请求执行或将其放入队列中, 且能实现可撤销操作。

角色

  • 命令(Command) 定义命令接口,拥有执行命令方法excute()
  • 具体命令(Concrete Command) 实现 Command 接口。拥有 Receiver 对象,调用其方法完成命令要执行的操作。
  • 接收者(Receiver) 执行命令时的对象,也可以称为命令接收者。
  • 调用者(Invoker) 请求的发送者,负责对请求进行初始化,触发命令, 而不向接收者直接发送请求。
  • 请求者(Client) 具体命令的创建者,并分配接收者。

优点

  • 通过引入中间件(抽象接口)降低系统的耦合度。
  • 增加与删除命令不会影响其他类,且满足“开闭原则”。
  • 方便实现 Undo 和 Redo 操作。可以与备忘录模式结合,实现命令的撤销与恢复。
  • 可以在现有命令的基础上,增加额外功能。比如日志记录,结合装饰器模式会更加灵活。

缺点

  • 每一个具体操作都需要设计一个具体命令类,产生大量类,增加系统复杂性。

示例:模拟 git 提交文件命令,用户可以看作是请求者,bash环境可以看作是调用者,操作系统可以看作是接收者,git的add、commit、reset看作是具体命令角色。

享元模式享元模式
/**
 * 命令角色
 * 
 * @date 2021-08-16 10:36
 * @author Windus
 */
public abstract class AbstractCommand {

	/**
	 * 接收者
	 */
	protected OperatingSystem operatingSystem;

	/**
	 * 命令参数
	 */
	protected String param;

	/**
	 * 命令执行结果描述
	 */
	@Getter
	protected String result;

	public AbstractCommand(OperatingSystem operatingSystem) {
		this.operatingSystem = operatingSystem;
	}

	public AbstractCommand(OperatingSystem operatingSystem, String param) {
		this.operatingSystem = operatingSystem;
		this.param = param;
	}

	/**
	 * 执行命令
	 * 
	 * @param param 参数
	 * @return
	 * @date 2021-08-16 14:12
	 * @author Windus
	 */
	public abstract String excute();
}

/**
 * Git add 命令 <br/>
 * 具体命令角色
 * 
 * @date 2021-08-16 10:39
 * @author Windus
 */
public class GitAddCommand extends AbstractCommand {

	public GitAddCommand(OperatingSystem operatingSystem) {
		super(operatingSystem);
	}

	public GitAddCommand(OperatingSystem operatingSystem, String param) {
		super(operatingSystem, param);
	}

	@Override
	public String excute() {
		this.result = "文件[" + this.param + "]已被添加到暂存区!";
		operatingSystem.action(this);
		return null;
	}
}

/**
 * Git commit 命令 <br/>
 * 具体命令角色
 * 
 * @date 2021-08-16 10:39
 * @author Windus
 */
public class GitCommitCommand extends AbstractCommand {

	public GitCommitCommand(OperatingSystem operatingSystem) {
		super(operatingSystem);
	}

	public GitCommitCommand(OperatingSystem operatingSystem, String param) {
		super(operatingSystem, param);
	}

	@Override
	public String excute() {
		this.result = "暂存区已被提交到版本库!";
		operatingSystem.action(this);
		return UUID.randomUUID().toString().replace("-", "");
	}
}

/**
 * Git reset 命令 <br/>
 * 具体命令角色
 * 
 * @date 2021-08-16 10:39
 * @author Windus
 */
public class GitResetCommand extends AbstractCommand {

	public GitResetCommand(OperatingSystem operatingSystem) {
		super(operatingSystem);
	}

	public GitResetCommand(OperatingSystem operatingSystem, String param) {
		super(operatingSystem, param);
	}

	@Override
	public String excute() {
		this.result = "版本库已被回退到[" + this.param + "]提交!";
		operatingSystem.action(this);
		return null;
	}
}

/**
 * 操作系统 <br/>
 * 命令接收者
 * 
 * @date 2021-08-16 11:06
 * @author Windus
 */
public interface OperatingSystem {
	/**
	 * 执行命令
	 * 
	 * @param command 命令对象
	 * @return
	 * @date 2021-08-16 11:10
	 * @author Windus
	 */
	void action(AbstractCommand command);
}

/**
 * Linux 系统 <br>
 * 具体接收者
 * 
 * @date 2021-08-16 11:49
 * @author Windus
 */
public class Linux implements OperatingSystem {
	@Override
	public void action(AbstractCommand command) {
		System.out.println("Linux系统执行命令--->" + command.getResult());
	}
}

/**
 * 调用者
 * 
 * @date 2021-08-16 11:59
 * @author Windus
 */
public abstract class AbstractShell {
	/**
	 * 命令执行历史
	 */
	private MacroCommand history;

	public AbstractShell(MacroCommand history) {
		this.history = history;
	}

	public String call(AbstractCommand command) {
		history.append(command);
		return command.excute();
	}
}

/**
 * bash Shell <br/>
 * 具体调用者
 * 
 * @date 2021-08-16 14:02
 * @author Windus
 */
public class Bash extends AbstractShell {
	public Bash(MacroCommand history) {
		super(history);
	}
}

/**
 * 请求者
 * 
 * @date 2021-08-16 14:27
 * @author Windus
 */
public class App {
	public static void main(String[] args) {
		// 接收者
		OperatingSystem linuxOS = new Linux();
		// 命令历史对象
		MacroCommand history = new MacroCommand(linuxOS);
		// 调用者
		AbstractShell bash = new Bash(history);

		// 添加文件到暂存区
		AbstractCommand gitAddCommand = new GitAddCommand(linuxOS, "test.java");
		bash.call(gitAddCommand);
		System.out.println("------------------------------------");

		// 提交暂存区
		AbstractCommand gitCommitCommand = new GitCommitCommand(linuxOS);
		String commitId = bash.call(gitCommitCommand);
		System.out.println(commitId);
		System.out.println("------------------------------------");

		// 回退版本
		AbstractCommand gitResetCommand = new GitResetCommand(linuxOS, commitId);
		bash.call(gitResetCommand);
		System.out.println("------------------------------------");

		// undo
		AbstractCommand undoCommand = history.undo();
		if (undoCommand != null) {
			System.out.println("撤销命令");
			System.out.println(undoCommand.getResult());
		}
	}
}

解释器模式(Interpreter)

定义:为分析对象定义一个语言,并定义该语言的***文法***(语法规则)表示,再设计一个解释器来解析语言中的***句子***(语言集中的元素)。

角色

  • 抽象表达式(Abstract Expression) 定义解释器的接口,主要包含 interpret() 方法。
  • 终结符表达式(Terminal Expression) 实现 Abstract Expression 角色,对应 BNF 中的***终结符表达式***。
  • 非终结符表达式(Nonterminal Expression) 实现 Abstract Expression 角色,对应 BNF 中的***非终结符表达式***。
  • 上下文(Context) 为解释器进行语法解释提供必要的信息,一般用来传递共享数据,以便后面解释器从这里获得信息。
  • 客户端(Client) 将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法。

优点

  • 易扩展,易实现

缺点

  • 解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。
  • 解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护。

示例:定义前进、向左、向右、重复等指令解释器。

<program> ::= program <command list>
<command list> ::= <command>* end
<command> ::= <repeat command> | <primitive command>
<repeat command> ::= repeat <number> <command list>
<primitive command> ::= go | right | left

上面表达式使用的是EBNF,因此循环使用*表示,程序按照自上而下顺序进行解析。

在定义的 <repeat command> 时又使用了<command list> 这种定义为递归定义。

  • 终结符表达式<primitive command>这样不会被进一步展开的表达式,表示语法规则的终点。
  • 非终结符表达式<program><command>需要被进一步展开的表达式。
解释器模式解释器模式
/**
 * 抽象表达式角色
 * 
 * @date 2021-08-17 14:18
 * @author Windus
 */
public interface Expression {
	/**
	 * 语法解析
	 * 
	 * @param ctx 上下文
	 * @return
	 * @throws ParseException
	 * @date 2021-08-17 11:58
	 * @author Windus
	 */
	void interpret(Context ctx) throws ParseException;
}

/**
 * program 命令 <br/>
 * 非终结表达式角色
 * 
 * @date 2021-08-17 14:07
 * @author Windus
 */
@ToString
public class Program implements Expression {
	private Expression commandList;

	@Override
	public void interpret(Context ctx) throws ParseException {
		ctx.skipToken(Context.PROGRAM_TOKEN);
		commandList = new CommandList();
		commandList.interpret(ctx);
	}
}

/**
 * <command list> 非终结符 <br/>
 * 非终结符表达式角色
 * 
 * @date 2021-08-17 14:19
 * @author Windus
 */
public class CommandList implements Expression {
	private ArrayList<Expression> list = new ArrayList<>();

	@Override
	public void interpret(Context ctx) throws ParseException {
		while (true) {
			if (ctx.getCurrentToken() == null) {
				throw new ParseException("丢失 end 标记!");
			} else if (ctx.getCurrentToken().equals(Context.END_TOKEN)) {
				ctx.skipToken(Context.END_TOKEN);
				break;
			} else {
				Expression command = new Command();
				command.interpret(ctx);
				list.add(command);
			}
		}
	}

	@Override
	public String toString() {
		return "CommandList [list=" + list.toString() + "]";
	}
}

/**
 * <command> 非终结符 <br/>
 * 非终结符表达式角色
 * 
 * @date 2021-08-17 14:23
 * @author Windus
 */
@ToString
public class Command implements Expression {
	private Expression expression;

	@Override
	public void interpret(Context ctx) throws ParseException {
		if (ctx.getCurrentToken().equals(Context.REPEAT_TOKEN)) {
			expression = new RepeatCommand();
			expression.interpret(ctx);
		} else {
			expression = new PrimitiveCommand();
			expression.interpret(ctx);
		}
	}
}

/**
 * <repeat command> 非终结符 <br/>
 * 非终结符表达式角色
 * 
 * @date 2021-08-17 14:56
 * @author Windus
 */
@ToString
public class RepeatCommand implements Expression {
	/**
	 * 重复命令次数
	 */
	private int number;

	private Expression commandList;

	@Override
	public void interpret(Context ctx) throws ParseException {
		ctx.skipToken(Context.REPEAT_TOKEN);
		number = ctx.currentNumber();
		ctx.nextToken();
		commandList = new CommandList();
		commandList.interpret(ctx);
	}
}

/**
 * <primitive command> 终结符 <br/>
 * 终结符表达式角色
 * 
 * @date 2021-08-17 15:01
 * @author Windus
 */
@ToString
public class PrimitiveCommand implements Expression {
	/**
	 * 结束符标记
	 */
	private String primitiveToken;

	@Override
	public void interpret(Context ctx) throws ParseException {
		primitiveToken = ctx.getCurrentToken();
		ctx.skipToken(primitiveToken);
		if (!primitiveToken.equals(Context.GO_TOKEN) && !primitiveToken.equals(Context.RIGHT_TOKEN)
				&& !primitiveToken.equals(Context.LEFT_TOKEN)) {
			throw new ParseException("primitiveToken[" + primitiveToken + "]无效!");
		}
	}
}

/**
 * 上下文角色
 * 
 * @date 2021-08-17 14:26
 * @author Windus
 */
public class Context {
	/**
	 * 当前标记
	 */
	@Getter
	private String currentToken;

	/**
	 * 字符串分词对象
	 */
	private StringTokenizer tokenizer;

	public static final String PROGRAM_TOKEN = "program";
	public static final String END_TOKEN = "end";
	public static final String REPEAT_TOKEN = "repeat";
	public static final String GO_TOKEN = "go";
	public static final String RIGHT_TOKEN = "right";
	public static final String LEFT_TOKEN = "left";

	public Context(String text) {
		tokenizer = new StringTokenizer(text);
		nextToken();
	}

	/**
	 * 跳过当前标记
	 * 
	 * @param toke 标记
	 * @return
	 * @date 2021-08-17 14:28
	 * @author Windus
	 */
	public void skipToken(String token) throws ParseException {
		if (!token.equals(currentToken)) {
			throw new ParseException("非法标记!");
		}
		nextToken();
	}

	/**
	 * 下一个标记
	 * 
	 * @return
	 */
	public String nextToken() {
		if (tokenizer.hasMoreTokens()) {
			currentToken = tokenizer.nextToken();
		} else {
			currentToken = null;
		}
		return currentToken;
	}

	/**
	 * 当前标记数值
	 * 
	 * @return
	 * @date 2021-08-17 14:36
	 * @author Windus
	 */
	public int currentNumber() throws ParseException {
		int number = 0;

		try {
			number = Integer.parseInt(currentToken);
		} catch (NumberFormatException e) {
			throw new ParseException("无效数值!");
		}

		return number;
	}
}

/**
 * 自定义转换异常类
 * 
 * @date 2021-08-17 14:43
 * @author Windus
 */
public class ParseException extends Exception {
	public ParseException(String message) {
		super(message);
	}
}

/**
 * 客户端角色
 * 
 * @date 2021-08-17 15:13
 * @author Windus
 */
public class App {
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		while (true) {
			System.out.print("my-language > ");
			String inputText = scanner.nextLine();
			if (!"".equals(inputText)) {
				if ("exit".equals(inputText)) {
					System.out.print("bye~   ");
					System.exit(0);
				}

				Expression program = new Program();
				Context ctx = new Context(inputText);
				try {
					program.interpret(ctx);
					System.out.println(program);
				} catch (ParseException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

GoF设计模式分类

创建型设计模式

单例模式 工厂方法模式 抽象工厂模式
建造者模式 原型模式

结构型设计模式

适配器模式 桥接模式 组合模式
装饰器模式 外观模式 享元模式
代理模式

行为型设计模式

责任链模式 迭代器模式 观察者模式
策略模式 模板方法模式 访问者模式
状态模式 备忘录模式 中介者模式
命令模式 解释器模式