设计模式 Design Pattern

设计模式是指前人从实践中摸索出、并反复验证过的,对常见问题的经典解决方案。而这些解决方案,可以帮我们思考在特定情况下,如何组织、设计类和接口来达成一定目的。

创建型模式 Creational Pattern

如何创建对象。如隐藏一些具体的创建逻辑,从而提高代码的灵活性与可重用性。

工厂 Factory

  • 不直接靠new来创建实例,而是建造一个工厂类,再通过工厂的方法创建“产品”类。
  • 使用者使用产品,不需要知道工厂具体怎样生产、产品的具体细节,只需要知道工厂可以生产产品,且生产出来的产品都具有自己想要的某种用途(产品们需要有统一接口)就可以了。
public interface Product {
    void doSomething();
    void showName();
}

/*------Product A -----*/
public class ProductA implements Product {
    public String name = "Product A";
    void doSomething() {
    	System.out.println("A: do something");
    }
    
    void showName() {
    	System.out.println(name);
    }
}

/*------Product B -----*/
public class ProductB implements Product {
    public String name = "Product B";
    void doSomething() {
    	System.out.println("B: do something");
    }
    
    void showName() {
    	System.out.println(name);
    }
}


/*-----------Factory------------*/
public class MyFactory {
    public Product getProduct(String productType) {
    	if(productType.equals("A") return new ProductA();
        else if(productType.equals("B")) return new ProductB();
        else return null;
    }
}

/* ================== OR ================== */

public abstract class MyFactory {
    public void makeUseOfProduct() {
    	Product p = getProduct();
        p.showName();
        p.doSomething();
    }
    
    public abstract Product getProduct();
}

public class FactoryA extends MyFactory {
    @Override
    public Product getProduct() {
    	return new ProductA();
    }
}

public class FactoryB extends MyFactory {
    @Override
    public Product getProduct() {
    	return new ProductB();
    }
}

// and use the subclasses to generate and make use of products

抽象工厂 Abstract Factory

  • 让工厂不只能生产一个,而是能生产一套产品。
  • 可以保证一个工厂里生产出来的产品都是配套的(比如都是服装工厂,但一个男装,一个女装)
  • 使用者不用知道工厂具体怎么生产,但可以知道从同一个工厂拿出的产品是配套的(只使用从男装工厂生产的产品,就不会出现女装大佬)
public interface Upper {
    void printName();
    void dress();
}

public interface Lower {
    void printName();
    void dress();
}

public abstract class ClothingFactory {
    public abstract Upper produceUpper();
    public abstract Lower produceLower();
    
    public void dressUp() {
        Upper upper = produceUpper();
        Lower lower = produceLower();
        
        upper.dress();
        lower.dress();
    }
}

public class WomensClothingFactory {
    @Override
    public Upper produceUpper() {
        // produce women's upper garment
    }
    
    @Override
    public Lower produceLower() {
        // produce women's lower garment
    }
}

public class MenssClothingFactory {
    @Override
    public Upper produceUpper() {
        // produce men's upper garment
    }
    
    @Override
    public Lower produceLower() {
        // produce men's lower garment
    }
}

建造者 Builder

  • 一步一步构造对象
  • 适用于,有很多步骤有通用性,但具体步骤容易有改变,或者构造对象时并非每个步骤都执行的时候
  • 例:StringBuilder,每个步骤增加或去除字符,最后生产一个字符串
  • 例:构建一个套餐,一道一道点菜,最后结账
  • 例:装修房屋,埋线、刷墙、铺地板、安装厨房、安装卫生间、摆放家具等等
  • 有时也可以安排一个Director负责指挥一个或多个Builder按一定程序执行建造步骤
public interface Builder {
    PartA buildStepA();
    PartB buildStepB();
    void buildStepC();
    
    void reset();
    Product getResult();
}

public class Director {
    private Builder builder;
    
    public Director(builder) {
        this.builder = builder;
    }
    
    public void changeBuilder(builder) {
        this.builder = builder;
    }
    
    public Product build(String type) {
    	if(type.equals("A")) {
            builder.buildStepA();
            builder.buildStepC();
            return builder.getResult();
        } else {
            builder.buildStepB();
            builder.buildStepC();
            return builder.getResult();
        }
    }
}

单例 Singleton

  • 每个单例类只有一个实例
  • 全局要用这个类时,访问的都是同一个实例
  • 用来减少一个被全局共用的类的频繁创建和销毁
  • 例:对某个数据库的访问
  • 例:一份全局通用的配置文件
  • 例:一个可以重用的pipeline
  • 注意:多线程时还需要注意同步问题,避免多个线程产生多个实例
  • 注意:很多测试框架不支持对Singleton的单元测试(因为private constructor还有static的getInstance)
public class MySingleton {
    private MySingleton instance;
    
    // 防止它被从外部创建
    private MySingleton() {}
    
    public static MySingleton getInstance() {
        if(instance==null) {
        	synchronized (MySingleton.class) {
                if(instance==null) {
                    instance = new MySingleton();
                }
            }
        }
        
        return instance;
    }
    
    public void doSomething() {
        // do something
    }
}

原型 Prototype

  • 说白了就是允许Clone的类 (Java里实现Cloneable接口,override clone方法)
  • 允许使用者在不了解类的全貌(没有class文件),只有对象的情况下,也能复制对象

结构型模式 Structural Pattern

如何把对象们组合到一起,让它们在一个系统里可以互相配合的工作。

适配器 Adapter

  • 如果两个类无法配合,比如输入输出不匹配,那就在中间插一个适配器
  • 例:电源是240V电压,手机充电需要12V,插一个从240V转到12V的适配器
  • 例:笔记本没有SD卡插口,往USB口插一个读卡器,再往读卡器里插SD卡
  • 例:A程序输入XML,B程序读JSON,中间插一个XML转JSON的适配器

桥接 Bridge

  • 如果一个类有多个独立维度的变化,则可以把每个维度都拆成一个新的类或接口
  • 例:图形包含(红的圆,白的圆,蓝的圆,红的正方形,蓝的正方形,红的三角,白的三角……),可以解耦成:图形 {形状,颜色}

组合 Composite

  • 树形结构模式
  • 希望用户能够忽略组合对象和单个对象的不同(组合对象和单个对象都实现同一个接口,组合对象的接口对组合中每个单独对象调用该接口)
  • 例:单个图形可以被绘制,组合图形的绘制则是把组合中每个图形都绘制出来

过滤器/标准 Filter/Criteria

  • 允许用户以不同标准过滤一组对象
public interface Criteria {
    public List<T> meet(List<T> toFilter);
}

public class Criteria1 implements Criteria {
    @Override
    public List<T> meet(List<T> toFilter) {
    	List<T> list = new ArrayList<T>();
        for(T item: toFilter){
            if(item.getAttr1().equals("value 1")) {
                list.add(item);
            }
        }
        return list;
    }
}

public class Criteria2 implements Criteria {
    @Override
    public List<T> meet(List<T> toFilter) {
    	List<T> list = new ArrayList<T>();
        for(T item: toFilter){
            if(item.getAttr2().equals("value 2")) {
                list.add(item);
            }
        }
        return list;
    }
}


public class AndCriteria implements Criteria {
 
   private Criteria c1;
   private Criteria c2;
 
   public AndCriteria(Criteria c1, Criteria c2) {
      this.c1 = c1;
      this.c2 = c2; 
   }
 
   @Override
   public List<T> meet(List<T> toFilter) {
      List<T> firstCriteriaList = c1.meet(toFilter);     
      return c2.meet(firstCriteriaList);
   }
}


// or 就求并集

装饰器 Decorator

  • 即在某个类外面多包一层,这一层具有自己的某些作用,且允许透过这一层使用里面包的东西原本有的
  • 使用继承很别扭、不容易的时候,可以使用装饰器
  • 装饰类和被装饰类可以独立发展
  • 可能需要在被装饰的类外面多包一层Wrapper类
  • 一些语言和框架可以在类和方法上直接@注释来进行装饰
  • (也可以理解为往类里注入属性和方法)

外观 Facade

  • 隐藏复杂系统内部的逻辑,提供一个简单的对外接口供客户端使用
  • 客户端不与系统耦合,外观类与系统耦合
  • 外观类可能提供的功能比系统所能提供的全部要少很多,但只要提供可续断所需就可以了
  • 例:觉得办理某些手续的流程复杂难懂,找中介(外观类)来处理
  • 例:公司的接待人员

享元 Flyweight

  • 也被称作缓存 Cache
  • 当有大量对象有相同的部分,且这些部分基本不变时,可以通过把共享的部分抽取出来单独存一份来节省内存
  • 如果有相同的业务请求,直接返回内存中已有的,避免重新创建
  • 相同的部分只存一份,不同的部分各存各的
  • 例:游戏中一群鸟共享一份绘图资源,各存各的位置和飞行速度
  • 例:Java中的String,数据库的数据池
  • 例:在HashMap或字典类等里对共享资源进行缓存,如果命中,直接返回
  • 例:键值对、查询结果等缓存进NoSQL数据库

代理 Proxy

  • 为了增加对访问的控制、方便管理访问、或者方便访问,在访问者和被访问的类之间增加一层代理
  • 与适配器之间的差别:代理不改变被代理的接口
  • 与装饰器之间的差别:代理不增加功能,而是增加控制
  • 例:车票的代售点
  • 例:以银行卡代理银行账户,进行收付款

行为模式 Behavioral Pattern

关乎对象之间的职责划分与通讯、如何设计以完成算法。

责任链 Chain of Responsibility

  • 又称CoR或Chain of Command
  • 在一串不同的Handlers传递Request,每个Handler决定是否处理这个请求、以及是否继续把它传给下一个Handler

可能以两种常见形式存在:

  • 一连串的Handler是一连串的检测或处理,每个Handler是一个步骤。如果通过检测或处理成功,则传给下一个Handler,否则直接返回失败。(e.g. 用户认证:检测ID与口令是否匹配,是否命中缓存,IP是否短时间多次试图认证等一连串处理)(e.g. 拨打客服热线->提示按键咨询详情->自动回复答案, 询问是否转人工->人工客服 ->客服转其他专业部门……)
  • 一连串的Handler中,请求会被第一个能处理它的Handler处理,并返回。如果一个Handler决定自己不能处理,则传给下一个,直到没有更多Handler,返回失败。(e.g. 一个GUI上的点击事件被哪层Handler捕获、处理)

命令 Command

  • 又称Action或Transaction
  • 把请求封装成一个包含所有完成请求所需外部信息的类,以便把请求当参数传入方法、建立请求队列、延迟执行、处理异常(无法执行的操作)等等
  • 便于对行为进行记录、执行、撤销、事务等处理

解释器 Interpreter

  • 给定一种语言和它的语法,定义一个解释器解释语句。
  • 例: SQL解析、符号处理引擎
  • 实现:构建语法树,并定义终结符与非终结符

迭代器 Iterator

  • 提供一种遍历集合的方法,且无序知道该集合的底层表示(不需要知道它是数组、列表、数还是什么别的形式)
  • Iterator接口,需要实现next和hasNext方法

中介者 Mediator

  • 又称 Intermediary 或 Controller
  • 当对象与对象之间存在大量关联时(网状结构),把它们的关联转入一个中介里,解耦成一个星状结构
  • 缺点是中介者可能变得很复杂

备忘录 Memento

  • 又称 Snapshot
  • 存储、恢复对象的某个状态,且无需知道该对象的具体实现
  • 备忘录模式使用三个类 MementoOriginatorCareTaker
  • Memento包含对象的状态/快照,比如某实例序列化后的字符串。这个状态、快照,不可被其他类直接访问。其他类可访问Memento的元信息,如时间戳
  • Originator负责创建Memento里存放的快照,需要有对要存档对象的访问权。只有Originator有对Memento的读写权限,可以修改、恢复Memento里存储的状态
  • 存放Memento的类叫Caretaker,只负责存放、调取Memento,无权查看里面内容
  • 例:编辑器的历史记录,每个操作都是一个Memento,编辑器本身是Originator,历史记录是CareTaker。每次撤销是CareTaker拿出栈顶操作,然后编辑器再(Originator)恢复里面存放的状态

观察者 Observer

  • 又称 Event-Subscriber,或 Listener
  • 当一个对象被修改时,通知所有依赖它的对象 (即多个对象观察着一个对象的变化)
  • 也可以认为是Publisher-Subscriber模式的一种。每次对象更新都向所有订阅者/观察者推送更新消息

状态 State

  • 此模式中,类的行为是基于它的状态改变的
  • 同一个类状态不同时,仿佛它变成了另一个类
  • 状态需要是有穷的,且存在状态间的转化关系
  • 例:文档 {草稿,审阅,修订,已发布}
  • 例:人 {状态好,状态一般,状态差}

策略 Strategy

  • 一个类的行为或其算法可以在运行时更改
  • 一个类可以通过很多种不同方式去做某件事。为每个方式分别创建为一个类或函数,并称它们为策略。这个类随着策略的改变而改变做这件事的行为
  • 主要用于解决if...else太多的问题
  • 例:lambda函数作为策略参数传入方法
  • 例:给每个策略一个相同的接口,让原来的类(Context) 存一个策略的引用,并调用策略的那个接口
  • 例:路线规划 {公交,驾车,步行,骑行}

模板 Template

  • 在一个类里定义某种算法的骨架,然后在子类里实现骨架的具体步骤
  • 把算法分割成若干步骤,每个步骤一个方法,然后在一个方法里执行所有的步骤
  • 封装不变部分,扩展可变部分
  • 父类控制行为,子类控制具体实现

访问者 Visitor

  • 将对象与对象上的操作分离
  • 如果需要作用在类上的方法与类不怎么相关,为了防止大量这样不相关的方法污染这个类,创建一个访问者类并将方法写入访问者,把原来那个类当参数传入。
public class DataNode1 implements DataNode {
    public string data;
    public SpecialObject1 specialObject1;
    public void processData(){
        // ...
        // 与这个类很相关的方法,所以保留了
    }
    
    /* double delegation
       让类自己来选择当有Visitor来时,执行Visitor里哪个方法来处理自己
       因为Visitor里可能有多个处理不同类的方法,而用户也可能不知道选哪个好 */ 
    public void accept(Visitor v) {
    	v.manageDataNode1(this);
    }
}

public class DataNode2 implements DataNode {
    // 总之就是另一个类
    public string data;
    public SpecialObject2 specialObject2;
    public void processData(){
        // ...
    }
    
    // double delegation
    public void accept(Visitor v) {
    	v.manageDataNode2(this);
    }
}

public class Visitor {
    public static String exportDataAsJSON(DataNode d) {
        // 比如这是一个与DataNode很不相关的方法
        // 用得不多,但偶尔需要
        // 不想让它污染DataNode
    }
    
    public static void manageDataNode1(DataNode1 d) {
        // 专门处理DataNode1类的方法
        d.specialObject1.doSomething();
    }
    
    public static void manageDataNode2(DataNode2 d) {
        // 专门处理DataNode2类的方法
        d.specialObject2.doSomething();
    }
    
    public static void manageDataNode(DataNode d) {
    	d.accept(this); // double delegation
        // 用户也不知道应该用哪个方法的时候
        // 让DataNode自己决定用Visitor的哪个方法吧
    }
}

Ref: