0%

设计模式笔记

设计模式

7 大原则

1
2
3
4
5
6
7
单一职责: 每个类只负责一个职责(或每个方法)
接口隔离: 一个类对另一个类的依赖应建立在最小的接口上
依赖倒转: 高层模块不应依赖低层模块, 二者都应该依赖接口而非细节. 细节依赖抽象, 面向接口编程
里式替换: 子类应该做到可以替换父类, 及子类应尽量不重写父类方法.
开闭原则: 对提供者而已可以修改, 对使用者而言不需要修改(即代码兼容性), 尽量使用扩展增加功能, 而非修改原有类
迪米特法则: 一个对象应该对其他对象保持最小了解(最少知道原则)
合成复用原则: 一个类使用另一个类的代码(方法), 尽量使用合成, 而不是继承

创建型

单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
原理: 确保一个类只有一个实例,并提供该实例的全局访问点。

饿汉式:
静态常量
静态代码块
懒汉式:
直接判断(线程不安全)
方法加 synchronized(线程安全, 效率低)
判断后再同步(错误写法)
双重判断(if-同步-if) (推荐写法)
匿名静态内部类 (简单, 推荐)
枚举(简单, 但对象方法写在枚举中, 略有不适)

示例:
java.lang.Runtime#getRuntime()
java.awt.Desktop#getDesktop()

原型模式

1
2
3
4
5
6
7
8
9
原理: 使用原型实例指定要创建对象的类型,通过复制这个原型来创建新对象.
示例: Java 的 Object 对象的 clone 方法, java.util.Arrays.ArrayList#toArray()

浅拷贝: 仅对基础类型及字符串类型的字段拷贝值
深拷贝: 同时对引用类型(如数组,对象) 也进行拷贝

深拷贝实现:
1.重写 clone, 一一处理每个引用对象(调用对象的 clone), 麻烦, 且若对象之间关系复杂, 其中一个未实现深拷贝则导致 bug.
2.利用序列化和反序列化, 如 Json, 或 Java 自带的序列化方式(二进制)

创建者模式(生成器模式)

1
2
3
4
5
6
原理:
封装一个对象的构造过程,并允许按步骤构造.
若对象的生成过于复杂(字段极多且赋值还有依赖关系, 需要顺序调用), 则可将赋值过程封装成一个build(), 并放到一个 Builder 类中. 此类对外提供各个字段的赋值方法并先保存起来, 直到调用 build(), 此方法返回对象实例.
使用此模式, 调用者无需关注构建过程, 只需设置自己想要的值, 然后调用 build() 即可得到对象实例. 且若增加或修改字段, 构造过程变化, 调用者无感知, 无需修改代码. 符合开闭原则.

示例: StringBuilder, 一些框架的 ConfigurationBuilder(如 xmpp), 用于构建配置.

简单工厂模式

1
2
3
4
5
原理:
在创建一个对象时不向客户暴露内部细节,并提供一个创建对象的通用接口。
此模式可避免多个调用者创建对象时判断创建哪个子类的重复代码, 且若多一个子类, 调用者无需修改代码.

示例: Spring ApplicationContext 的 getBean 方法.

工厂方法模式

1
2
3
4
5
6
原理:
定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化操作推迟到子类。
此模式解决了简单工厂每增加一个子类需要修改工厂类的问题.
此模式存在问题, 若新增一个子类, 需同时新增一个子类工厂, 系统复杂性更高.

示例: Calendar, NumberFormat

抽象工厂模式

1
2
3
4
5
6
原理:
提供一个接口,用于创建 相关的对象家族.
同上, 由子类工厂决定创建哪些对象.
此模式是工厂方法的升级版, 不同之处在于它同时创建多个种类的对象(工厂类具有多个方法).
此模式将一个对象家族的新建集合到一个工厂类创建管理, 这些对象家族相互之间一般有关联, 在创建时就可以处理这些关联. 且对于 2 个子类工厂, 一般可以无缝切换, 使得修改代码极为方便(即换一个子类工厂).
此模式在新增一个对象家族的成员时非常麻烦(即所有工厂类需要新增一个方法), 但再新增一类对象家族时比较简单(即新增一个子类工厂).

结构型

适配器模式

1
2
3
4
5
6
原理:
把一个接口转换成另一个用户需要的接口.
定义一个类, 实现用户需要的接口, 并聚合一个需要转换的接口对象, 在重写的方法(用户需要的方法)中调用聚合的对象的方法, 若需要返回值, 且返回值类型不一致, 则还需要在方法中处理一番, 然后返回. 这个过程叫做适配.这个类叫做适配器类.
使用此模式可对一些老旧接口适配兼容.

示例: java.util.Arrays#asList() 将数组适配成 List, Spring MVC的 HandlerAdapter

装饰者模式

1
2
3
4
5
6
原理:
将一个或多个功能(方法)动态的新增到一个类中.
把需要新增功能类称为 A,定义一个类B,实现A的上层接口, 并聚合一个A 的实例对象, B类实现的接口中, 对其他不关心的方法直接调用聚合的对象的方法. 对于关心的方法则可以在调用前后进行加料处理(如一个方法返回一个数, 可以在原来的返回值上乘以 2), 同时, B类也可以新增一些其他方法, 这些方法就是多出的功能. B类就是装饰者类, A就是被装饰类.
此模式的优点是, 装饰类也可以当做被装饰类, 然后再来一层装饰, 可以无限的装饰.

示例: java IO 流

代理模式

1
2
3
4
5
6
7
8
9
10
11
原理:
控制其他对象的访问(方法级), 将一些前置或后置的处理, 通过代理对象注入到目标对象的方法前后. 面向切面编程.

类型:
静态代理: 定义一个代理类实现目标对象的上层接口, 并聚合一个目标对象, 重写方法时将前置后置处理加上.
动态代理:
JDK 动态代理: 需要目标对象有上层接口(自然接口内的方法才可以代理)
使用java.lang.reflect.Proxy#getProxyClass
CGLIB 动态代理: 是个类就行. 实现原理是 ASM 框架动态生成目标对象类的子类字节码, 然后通过反射生成代理对象.

示例: Spring AOP

桥接模式

1
2
3
4
5
原理:
将抽象与实现分离开来,使它们可以独立变化。
桥接的含义是, 一个桥, 放在哪里都有桥的 2 边, 桥的 2 边可以变化, 但桥始终不变. 此处, 桥代表一个操作(如手机上运行软件), 2 边代表 一个操作的 2 个维度(如手机和软件). 同时, 桥接后的操作也可以视为一个维度, 与另一个维度桥接(如手机上运行软件和人这 2 个维度, 可以进行桥接, 组成 3 维度嵌套桥接).

示例: JDBC 获取连接, 获取连接是一个维度, 数据库是一个维度, 数据库有多个, 所以这是一个数据库维度变化, 另一维度不变的桥接模式.

享元模式

1
2
3
4
5
原理:
利用共享的方式来支持大量细粒度的对象,这些对象一部分内部状态是相同的。
如常见的 线程池, 常量池等, 使得对象的获取速度加快.

示例: java.lang.Integer#valueOf() java.lang.Boolean#valueOf()

组合模式

1
2
3
4
5
6
7
8
9
10
原理:
将对象组合成树形结构来表示“整体/部分”层次关系,允许用户以相同的方式处理单独对象和组合对象。
一般需要部分和整体具有一定的相似度, 才能对其进行抽象.
对部分/整体进行抽象, 得出一个公共抽象类或接口, 再实现类中根据具体角色做不同处理.

示例:
javax.swing.JComponent#add(Component)
java.util.Map#putAll(Map)
java.util.List#addAll(Collection)
java.util.Set#addAll(Collection)

外观模式

1
2
原理:
提供了一个统一的接口,用来访问子系统中的一群接口,从而让子系统更容易使用.

行为型

职责链(责任链)模式

1
2
3
4
5
6
原理:
使多个对象都有机会处理请求,将这些对象连成一条链,并沿着这条链发送该请求,直到有一个对象处理它为止, 从而避免请求的发送者和接收者之间的耦合关系。

示例:
javax.servlet.Filter#doFilter()
netty 的 Handler Chain

观察者模式

1
2
3
4
5
6
7
原理:
定义对象之间的一对多依赖,当一个对象状态改变时,它的所有依赖都会收到通知并且自动更新状态。
主题(Subject)是被观察的对象,而其所有依赖者(Observer)称为观察者。

示例:
swing 的事件监听(按钮事件, 鼠标事件)
JS 的 事件监听

状态模式

1
2
3
原理:
允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它所属的类。
状态模式主要是用来解决状态转移的问题,当状态发生转移了,那么 Context 对象就会改变它的行为.

策略模式

1
2
3
4
5
6
原理:
定义一系列算法,封装每个算法,并使它们可以互换。
策略模式可以让算法独立于使用它的客户端。
策略模式主要是用来封装一组可以互相替代的算法族,并且可以根据需要动态地去替换 Context 使用的算法.

示例: java.util.Comparator#compare() javax.servlet.http.HttpServlet

模板方法模式

1
2
3
4
5
原理:
定义算法框架,并将一些步骤的实现延迟到子类。
通过模板方法,子类可以重新定义算法的某些步骤,而不用改变算法的结构。

示例: java.util.Collections#sort()

命令模式

1
2
3
4
5
原理:
将一个对象(命令接收者)的每个操作拆分到每一个命令类中, 再使用一个命令管理类来管理这些命令. 使得命令可以放入队列中有序执行, 且可以统一记录命令的操作日志, 还可以支持撤销操作(每个命令都实现对应的撤销即可).
此模式的好处是, 若将命令抽象为几个标准的命令(如开,关), 然后管理多个命令接收者(如灯,电视机,空调)的操作, 可使新增命令接收者变得简单, 即扩展性好.

又称万能遥控器.

中介模式

1
2
3
4
5
原理:
集中相关对象之间复杂的沟通和控制方式。降低子系统之间的耦合.
类似一个消息收发中心, 负责字系统的消息中转, 使得子系统之间可以进行一定的交互.

示例: 线程池管理者线程和要执行的任务.

备忘录模式

1
2
3
原理:
在不违反封装的情况下获得对象的内部状态,从而在需要时可以将对象恢复到最初状态。
如对游戏的当前状态进行一个保存, 然后在后续游戏中死亡后可以读取这个状态重新开始.

访问者模式

1
2
3
原理: 
为一个对象结构(比如组合结构)增加新能力。
使用访问者模式可实现重载的动态绑定(即伪双分派), 效果与重载方法内使用 instanceof 是一样的, 但使用访问者模式, 可扩展性更好.

迭代器模式

1
2
3
4
原理:
提供一种顺序访问聚合对象元素的方法,并且不暴露聚合对象的内部表示。

示例: java.util.Iterator

解释器模式

1
2
3
4
原理:
为语言创建解释器,通常由语言的语法和语法分析来定义。

示例: EL 表达式, Freemaker模板

空对象模式

1
2
原理:
使用什么都不做的空对象来代替 NULL, 避免空对象判断, 避免空指针异常.
下次一定