design_pattern
自述
背景
安装
用法
目录
Log
ToDolist
维护者
设计模式是解决问题的方案,学习现有的设计模式可以做到经验复用。拥有设计模式词汇,在沟通时就能用更少的词汇来讨论,并且不需要了解底层细节
该项目优点:
- 24 中设计模式, 并按照三种类型分类, 便于查找
- 用例选择简明, 避免"冗长"代码例子, 重点关注阐述设计模式**
- 每种设计模式案例皆可直接运行查看结果. 避免空讲理论不知所云.
- 注解详实, 编写前明确任务目标, 便于实施和复习.
文档说明: 每种"设计模式"皆由如下几部分组成.
"Intent" : 该设计模式定义
"Class Diagram" : 该设计模式类图
"Implementation" : 该设计模式代码实现
"JDK" : Java 开发工具包中该设计模式经典应用.
环境 JDK1.8. 操作系统无要求( Java 跨平台嘛 ),编译器无要求
笔者在 MAC 下 IDEA2019 版本, JDK1.8 环境 测试无误.
使用说明 : 无论 Windows / MAC / DeepIn OS 等, 无论是 IDEA / Eclipse 等编译器, 在 DownLoad 该项目后, 在cn.yydcyy.design 包下找到想验证的设计模式, 运行对应 Client.java 即可查看结果.
在创建一个对象时不向客户暴露内部细节,并提供一个创建对象的通用接口。
简单工厂把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪个具体子类来实例化。
这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及应当实例化哪个子类。客户类往往有多个,如果不使用简单工厂,那么所有的客户类都要知道所有子类的细节。而且一旦子类发生改变,例如增加子类,那么所有的客户类都要进行修改。
定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化操作推迟到子类。
-
在简单工厂中,创建对象的是另一个类,而在工厂方法中,是由子类来创建对象。
-
下图中,Factory 有一个 doSomething() 方法,这个方法需要用到一个产品对象,这个产品对象由 factoryMethod() 方法创建。该方法是抽象的,需要由子类去实现。
![img](http://qin.yydcyy.top/Typora/2019-12-07-041739.png)
JDK
java.util.Calendar
java.util.ResourceBundle
java.text.NumberFormat
java.nio.charset.Charset
java.net.URLStreamHandlerFactory
java.util.EnumSet
javax.xml.bind.JAXBContext
- 提供一个接口,用于创建 相关的对象家族 。
-
抽象工厂模式创建的是对象家族,也就是很多对象而不是一个对象,并且这些对象是相关的,也就是说必须一起创建出来。而工厂方法模式只是用于创建一个对象,这和抽象工厂模式有很大不同。
-
抽象工厂模式用到了工厂方法模式来创建单一对象,AbstractFactory 中的 createProductA() 和 createProductB() 方法都是让子类来实现,这两个方法单独来看就是在创建一个对象,这符合工厂方法模式的定义。
-
至于创建对象的家族这一概念是在 Client 体现,Client 要通过 AbstractFactory 同时调用两个方法来创建出两个对象,在这里这两个对象就有很大的相关性,Client 需要同时创建出这两个对象。
-
从高层次来看,抽象工厂使用了组合,即 Cilent 组合了 AbstractFactory,而工厂方法模式使用了继承。
![img](http://qin.yydcyy.top/Typora/2019-12-07-41741.png)
![img](http://qin.yydcyy.top/Typora/2019-12-07-041757.png)
-
JDK
- javax.xml.parsers.DocumentBuilderFactory
- javax.xml.transform.TransformerFactory
- javax.xml.xpath.XPathFactory
- 封装一个对象的构造过程,并允许按步骤构造。
![img](http://qin.yydcyy.top/Typora/2019-12-07-041744.png)
- java.lang.StringBuilder
- java.nio.ByteBuffer
- java.lang.StringBuffer
- java.lang.Appendable
- Apache Camel builders
- 使用原型实例指定要创建对象的类型,通过复制这个原型来创建新对象。
- [ 原型模式涉及"深浅复制"概念, 具体是深复制还是浅复制, 看自己业务逻辑实现 ]
![img](http://qin.yydcyy.top/Typora/2019-12-07-041750.png)
#####Implementation
![img](http://qin.yydcyy.top/Typora/2019-12-07-041752.png)
- java.lang.Object#clone()
#####Intent
- 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链发送该请求,直到有一个对象处理它为止。
- Handler:定义处理请求的接口,并且实现后继链(successor)
![img](https://img.mubu.com/document_image/c9523bde-1dda-49f6-af37-9d556d2f3484-4484563.jpg)
![img](http://qin.yydcyy.top/Typora/2019-12-07-041803.png)
- java.util.logging.Logger#log()
- Apache Commons Chain
- javax.servlet.Filter#doFilter()
#####Intent
- 将命令封装成对象中,具有以下作用:
- 使用命令来参数化其它对象
- 将命令放入队列中进行排队
- 将命令的操作记录到日志中
- 支持可撤销的操作
#####Class Diagram
-
Command:命令
-
Receiver:命令接收者,也就是命令真正的执行者
-
Invoker:通过它来调用命令
-
Client:可以设置命令与命令的接收者
![img](http://qin.yydcyy.top/Typora/2019-12-07-41753.png)
#####Implementation
-
设计一个遥控器,可以控制电灯开关。
![img](http://qin.yydcyy.top/Typora/2019-12-07-041738.png)
#####JDK
- java.lang.Runnable
- Netflix Hystrix
- javax.swing.Action
#####Intent
- 为语言创建解释器,通常由语言的语法和语法分析来定义。
#####Class Diagram
-
TerminalExpression:终结符表达式,每个终结符都需要一 个TerminalExpression。
-
Context:上下文,包含解释器之外的一些全局信息。
![img](https://img.mubu.com/document_image/5797353e-bdfc-421e-81e9-d2b264909f61-4484563.jpg)
#####Implementation
-
以下是一个规则检验器实现,具有 and 和 or 规则,通过规则可以构建一颗解析树,用来检验一个文本是否满足解析树定义的规则。
-
例如一颗解析树为 D And (A Or (B C)),文本 "D A" 满足该解析树定义的规则。
-
这里的 Context 指的是 String。
![img](http://qin.yydcyy.top/Typora/2019-12-07-41735.png)
#####JDK
java.util.Pattern
java.text.Normalizer
All subclasses of java.text.Format
javax.el.ELResolver
####4. 迭代器
#####Intent
提供一种顺序访问聚合对象元素的方法,并且不暴露聚合对象的内部表示。
Class Diagram
-
Aggregate 是聚合类,其中 createIterator() 方法可以产生一个 Iterator;
-
Iterator 主要定义了 hasNext() 和 next() 方法。
-
Client 组合了 Aggregate,为了迭代遍历 Aggregate,也需要组合 Iterator。
![img](https://img.mubu.com/document_image/ebba064d-193b-4e06-9ea2-bf604253ecda-4484563.jpg)
![img](http://qin.yydcyy.top/Typora/2019-12-07-041802.png)
java.util.Iterator
java.util.Enumeration
- 集中相关对象之间复杂的沟通和控制方式。
-
Mediator:中介者,定义一个接口用于与各同事(Colleague)对象通信。
-
Colleague:同事,相关对象
![img](http://qin.yydcyy.top/Typora/2019-12-07-041758.png)
-
Alarm(闹钟)、CoffeePot(咖啡壶)、Calendar(日历)、Sprinkler(喷头)是一组相关的对象,在某个对象的事件产生时需要去操作其它对象,形成了下面这种依赖结构:
![img](http://qin.yydcyy.top/Typora/2019-12-07-41802.png)
-
使用中介者模式可以将复杂的依赖结构变成星形结构:
![img](http://qin.yydcyy.top/Typora/2019-12-07-041747.png)
![img](http://qin.yydcyy.top/Typora/2019-12-07-41738.png)
- All scheduleXXX() methods of java.util.Timer
- java.util.concurrent.Executor#execute()
- submit() and invokeXXX() methods of java.util.concurrent.ExecutorService
- scheduleXXX() methods of java.util.concurrent.ScheduledExecutorService
- java.lang.reflect.Method#invoke()
- 在不违反封装的情况下获得对象的内部状态,从而在需要时可以将对象恢复到最初状态。
-
Originator:原始对象
-
Caretaker:负责保存好备忘录
-
Menento:备忘录,存储原始对象的的状态。备忘录实际上有两个接口,一个是提供给 Caretaker 的窄接口:它只能将备忘录传递给其它对象;一个是提供给 Originator 的宽接口,允许它访问到先前状态所需的所有数据。理想情况是只允许 Originator 访问本备忘录的内部状态。
![img](https://img.mubu.com/document_image/2f5bd9b7-cd54-407a-82fa-23d57a68ffca-4484563.jpg)
-
以下实现了一个简单计算器程序,可以输入两个值,然后计算这两个值的和。备忘录模式允许将这两个值存储起来,然后在某个时刻用存储的状态进行恢复。
![img](http://qin.yydcyy.top/Typora/2019-12-07-041754.png)
-
定义对象之间的一对多依赖,当一个对象状态改变时,它的所有依赖都会收到通知并且自动更新状态。
-
主题(Subject)是被观察的对象,而其所有依赖者(Observer)称为观察者。
![img](http://qin.yydcyy.top/Typora/2019-12-07-041804.png)
主题(Subject)具有注册和移除观察者、并通知所有观察者的功能,主题是通过维护一张观察者列表来实现这些操作的。
-
观察者(Observer)的注册功能需要调用主题的 registerObserver() 方法。
![img](http://qin.yydcyy.top/Typora/2019-12-07-041742.png)
-
天气数据布告板会在天气信息发生改变时更新其内容,布告板有多个,并且在将来会继续增加。
![img](https://img.mubu.com/document_image/3637ae76-77fe-413a-b5fc-32a99b4f556b-4484563.jpg)
- java.util.Observer
- java.util.EventListener
- javax.servlet.http.HttpSessionBindingListener
- RxJava
- 允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它所属的类。
![img](http://qin.yydcyy.top/Typora/2019-12-07-041743.png)
-
糖果销售机有多种状态,每种状态下销售机有不同的行为,状态可以发生转移,使得销售机的行为也发生改变。
![img](http://qin.yydcyy.top/Typora/2019-12-07-041753.png)
![img](http://qin.yydcyy.top/Typora/2019-12-07-041741.png)
- 定义一系列算法,封装每个算法,并使它们可以互换。
- 策略模式可以让算法独立于使用它的客户端。
-
Strategy 接口定义了一个算法族,它们都实现了 behavior() 方法。
-
Context 是使用到该算法族的类,其中的 doSomething() 方法会调用 behavior(),setStrategy(Strategy) 方法可以动态地改变 strategy 对象,也就是说能动态地改变 Context 所使用的算法。
![img](http://qin.yydcyy.top/Typora/2019-12-07-041735.png)
-
与状态模式的比较
- 状态模式的类图和策略模式类似,并且都是能够动态改变对象的行为。但是状态模式是通过状态转移来改变 Context 所组合的 State 对象,而策略模式是通过 Context 本身的决策来改变组合的 Strategy 对象。所谓的状态转移,是指 Context 在运行过程中由于一些条件发生改变而使得 State 对象发生改变,注意必须要是在运行过程中。
- 状态模式主要是用来解决状态转移的问题,当状态发生转移了,那么 Context 对象就会改变它的行为;而策略模式主要是用来封装一组可以互相替代的算法族,并且可以根据需要动态地去替换 Context 使用的算法。
-
设计一个鸭子,它可以动态地改变叫声。这里的算法族是鸭子的叫声行为。
![img](http://qin.yydcyy.top/Typora/2019-12-07-041736.png)
- java.util.Comparator#compare()
- javax.servlet.http.HttpServlet
- javax.servlet.Filter#doFilter()
- 定义算法框架,并将一些步骤的实现延迟到子类。
- 通过模板方法,子类可以重新定义算法的某些步骤,而不用改变算法的结构。
![img](http://qin.yydcyy.top/Typora/2019-12-07-041751.png)
-
冲咖啡和冲茶都有类似的流程,但是某些步骤会有点不一样,要求复用那些相同步骤的代码。
![img](http://qin.yydcyy.top/Typora/2019-12-07-41759.png)
![img](http://qin.yydcyy.top/Typora/2019-12-07-41805.png)
- java.util.Collections#sort()
- java.io.InputStream#skip()
- java.io.InputStream#read()
- java.util.AbstractList#indexOf()
为一个对象结构(比如组合结构)增加新能力。
-
Visitor:访问者,为每一个 ConcreteElement 声明一个 visit 操作
-
ConcreteVisitor:具体访问者,存储遍历过程中的累计结果
-
ObjectStructure:对象结构,可以是组合结构,或者是一个集合。
![img](http://qin.yydcyy.top/Typora/2019-12-07-041748.png)
- javax.lang.model.element.Element and javax.lang.model.element.ElementVisitor
- javax.lang.model.type.TypeMirror and javax.lang.model.type.TypeVisitor
- 使用什么都不做
- 的空对象来代替 NULL。
- 一个方法返回 NULL,意味着方法的调用端需要去检查返回值是否是 NULL,这么做会导致非常多的冗余的检查代码。并且如果某一个调用端忘记了做这个检查返回值,而直接使用返回的对象,那么就有可能抛出空指针异常。
![img](http://qin.yydcyy.top/Typora/2019-12-07-041755.png)
-
把一个类接口转换成另一个用户需要的接口。
![img](http://qin.yydcyy.top/Typora/2019-12-07-41744.png)
![img](http://qin.yydcyy.top/Typora/2019-12-07-041740.png)
- 鸭子(Duck)和火鸡(Turkey)拥有不同的叫声,Duck 的叫声调用 quack() 方法,而 Turkey 调用 gobble() 方法。
- 要求将 Turkey 的 gobble() 方法适配成 Duck 的 quack() 方法,从而让火鸡冒充鸭子!
- java.util.Arrays#asList()
- java.util.Collections#list()
- java.util.Collections#enumeration()
- javax.xml.bind.annotation.adapters.XMLAdapter
- 将抽象与实现分离开来,使它们可以独立变化。
-
Abstraction:定义抽象类的接口
-
Implementor:定义实现类接口
![img](http://qin.yydcyy.top/Typora/2019-12-07-041745.png)
-
RemoteControl 表示遥控器,指代 Abstraction。
-
TV 表示电视,指代 Implementor。
-
桥接模式将遥控器和电视分离开来,从而可以独立改变遥控器或者电视的实现。
![img](http://qin.yydcyy.top/Typora/2019-12-07-41756.png)
- AWT (It provides an abstraction layer which maps onto the native OS the windowing support.)
- JDBC
- 为对象动态添加功能。
-
装饰者(Decorator)和具体组件(ConcreteComponent)都继承自组件(Component),具体组件的方法实现不需要依赖于其它对象,而装饰者组合了一个组件,这样它可以装饰其它装饰者或者具体组件。所谓装饰,就是把这个装饰者套在被装饰者之上,从而动态扩展被装饰者的功能。装饰者的方法有一部分是自己的,这属于它的功能,然后调用被装饰者的方法实现,从而也保留了被装饰者的功能。可以看到,具体组件应当是装饰层次的最低层,因为只有具体组件的方法实现不需要依赖于其它对象。
![img](http://qin.yydcyy.top/Typora/2019-12-07-041801.png)
-
设计不同种类的饮料,饮料可以添加配料,比如可以添加牛奶,并且支持动态添加新配料。每增加一种配料,该饮料的价格就会增加,要求计算一种饮料的价格。
-
下图表示在 DarkRoast 饮料上新增新添加 Mocha 配料,之后又添加了 Whip 配料。DarkRoast 被 Mocha 包裹,Mocha 又被 Whip 包裹。它们都继承自相同父类,都有 cost() 方法,外层类的 cost() 方法调用了内层类的 cost() 方法。
![img](http://qin.yydcyy.top/Typora/2019-12-07-041759.png)
![img](http://qin.yydcyy.top/Typora/2019-12-07-041749.png)
- java.io.BufferedInputStream(InputStream)
- java.io.DataInputStream(InputStream)
- java.io.BufferedOutputStream(OutputStream)
- java.util.zip.ZipOutputStream(OutputStream)
- java.util.Collections#checkedList|Map|Set|SortedSet|SortedMap
2019年12月07日 编辑 ReadMe.md 文件
[0] 完善最后3个设计模式 [1] 完善文档备注