设计模式 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
- 存储、恢复对象的某个状态,且无需知道该对象的具体实现
- 备忘录模式使用三个类 Memento、Originator 和 CareTaker
- 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: