完善设计模式

pull/2/head
吴尤 4 years ago
parent fc997da84b
commit b0308f752d

@ -0,0 +1,189 @@
## 解释器模式概述
> 加法/减法解释器示意图
![image-20211215081232895](设计模式/image-20211215081232895.png)
> 分析
- Java语言**无法直接解释**类似“**1 + 2 + 3 4 + 1**”这样的字符串
- **定义一套文法规则**来实现对这些语句的解释,即**设计一个自定义语言**
- **基于现有的编程语言 => 面向对象编程语言 => 解释器模式**
> 解释器模式的定义
**解释器模式**:给定一个语言,**定义它的文法的一种表示**,并定义一个解释器,这个解释器使用该表示来**解释语言中的句子。**
**Interpreter Pattern**: Given a language, **define a representation for its grammar** along with an interpreter that uses the representation to **interpret sentences in the language.**
- **类行为型**模式
- 在解释器模式的定义中所指的“语言”是使用规定格式和语法的代码
- 是一种**使用频率相对较低但学习难度相对较大**的设计模式,用于描述**如何使用面向对象语言构成一个简单的语言解释器**
- 能够**加深对面向对象思想的理解**,并且**理解编程语言中文法规则的解释过程**
## 文法规则和抽象语法树
> 文法规则
- 1 + 2 + 3 4 + 1
```
expression ::= value | operation
operation ::= expression '+' expression | expression '-' expression
value ::= an integer //一个整数值
```
- “::=”表示“定义为”
- “|”表示“或”
- “{”和“}”表示“组合”
- “*”表示“出现0次或多次”
- **抽象语法树(Abstract Syntax Tree, AST)**
- 描述了如何构成一个复杂的句子,通过对抽象语法树的分析,可以识别出语言中的终结符类和非终结符类
![image-20211215082002958](设计模式/image-20211215082002958.png)
## 解释器模式的结构与实现
> 解释器模式的结构
![image-20211215082023780](设计模式/image-20211215082023780.png)
> 解释器模式包含以下4个角色
- AbstractExpression抽象表达式
- TerminalExpression终结符表达式
- NonterminalExpression非终结符表达式
- Context环境类
> 解释器模式的实现
- 典型的**抽象表达式类**代码:
```java
public abstract class AbstractExpression {
public abstract void interpret(Context ctx);
}
```
- 典型的**终结符表达式**类代码:
```java
public class TerminalExpression extends AbstractExpression {
public void interpret(Context ctx) {
//终结符表达式的解释操作
}
}
```
- 典型的**非终结符表达式类**代码:
```java
public class NonterminalExpression extends AbstractExpression {
private AbstractExpression left;
private AbstractExpression right;
public NonterminalExpression(AbstractExpression left,AbstractExpression right) {
this.left=left;
this.right=right;
}
public void interpret(Context ctx) {
//递归调用每一个组成部分的interpret()方法
//在递归调用时指定组成部分的连接方式,即非终结符的功能
}
}
```
- 环境类Context
- 用于**存储一些全局信息**一般包含一个HashMap或ArrayList等类型的**集合对象也可以直接由HashMap等集合类充当环境类**,存储一系列公共信息,例如变量名与值的映射关系(key/value)等,**用于在执行具体的解释操作时从中获取相关信息**
- 可以在环境类中**增加一些所有表达式解释器都共有的功能,以减轻解释器的职责**
- 当系统无须提供全局公共信息时可以**省略环境类,根据实际情况决定是否需要环境类**
- 典型的**环境类**代码:
```java
public class Context {
private HashMap<String, String> map = new HashMap<String, String>();
public void assign(String key, String value) {
//往环境类中设值
map.put(key, value);
}
public String lookup(String key) {
//获取存储在环境类中的值
return map.get(key);
}
}
```
## 解释器模式的应用实例
> 实例说明
某软件公司要开发一套机器人控制程序,在该机器人控制程序中包含一些简单的英文控制指令,每一个指令对应一个表达式(expression),该表达式可以是简单表达式也可以是复合表达式。每一个简单表达式由移动方向(direction),移动方式(action)和移动距离(distance)三部分组成,其中,移动方向包括向上(up)、向下(down)、向左(left)、向右(right);移动方式包括移动(move)和快速移动(run);移动距离为一个正整数。两个表达式之间可以通过与(and)连接,形成复合(composite)表达式。
用户通过对图形化的设置界面进行操作可以创建一个机器人控制指令机器人在收到指令后将按照指令的设置进行移动例如输入控制指令“up move 5”将“向上移动5个单位”输入控制指令“down run 10 and left move 20”将“向下快速移动10个单位再向左移动20个单位”。
现使用解释器模式来设计该程序并模拟实现。
- 文法规则
```
expression ::= direction action distance | composite //表达式
composite ::= expression 'and' expression //复合表达式
direction ::= 'up' | 'down' | 'left' | 'right' //移动方向
action ::= 'move' | 'run' //移动方式
distance ::= an integer //移动距离
```
- 终结符表达式direction、action和distance对应**DirectionNode类**、**ActionNode类**和**DistanceNode类**
- 非终结符表达式expression和composite对应**SentenceNode类**和**AndNode类**
> 抽象语法树
- down run 10 and left move 20
![image-20211215083816382](设计模式/image-20211215083816382.png)
> 实例分析及类图
![image-20211215083838911](设计模式/image-20211215083838911.png)
> 实例代码
- AbstractNode抽象结点类充当抽象表达式角色
- AndNodeAnd结点类充当非终结符表达式角色
- SentenceNode简单句子结点类充当非终结符表达式角色
- DirectionNode方向结点类充当终结符表达式角色
- ActionNode动作结点类充当终结符表达式角色
- DistanceNode距离结点类充当终结符表达式角色
- InstructionHandler指令处理类工具类
- Client客户端测试类
> 结果及分析
![image-20211215083942094](设计模式/image-20211215083942094.png)
## 解释器模式的优缺点与适用环境
> 模式优点
- **易于改变和扩展文法**
- 可以**方便地实现一个简单的语言**
- **实现文法较为容易**(有自动生成工具)
- **增加**新的**解释表达式较为方便**
> 模式缺点
- 对于**复杂文法难以维护**
- **执行效率较低**
> 模式适用环境
- 可以将一个需要解释执行的语言中的句子表示为一棵**抽象语法树**
- 一些重复出现的问题**可以用一种简单的语言来进行表达**
- 一个语言的**文法较为简单**
- **执行效率不是关键问题**

@ -0,0 +1,249 @@
## 迭代器模式概述
> 电视机遥控器与电视机示意图
![image-20211213080719240](设计模式/image-20211213080719240.png)
> 分析
- 电视机 => 存储电视频道的集合 => 聚合类(Aggregate Classes)
- 电视机遥控器 => 操作电视频道 => 迭代器(Iterator)
- **访问一个聚合对象中的元素但又不需要暴露它的内部结构**
- 聚合对象的两个职责:
- **存储数据**,聚合对象的基本职责
- **遍历数据**,既是可变化的,又是可分离的
- **将遍历数据的行为从聚合对象中分离出来**,封装在迭代器对象中
- 由迭代器来提供遍历聚合对象内部数据的行为,**简化聚合对象的设计,更符合单一职责原则**
> 迭代器模式的定义
**迭代器模式**:提供一种方法**顺序访问一个聚合对象中各个元素,且不用暴露该对象的内部表示。**
**Iterator Pattern**: Provide a way to **access the elements of an aggregate object** sequentially **without exposing its underlying representation.**
- **对象行为型**模式
- 又名**游标(Cursor)模式**
- 通过引入迭代器,**客户端无须了解聚合对象的内部结构即可实现对聚合对象中成员的遍历,还可以根据需要很方便地增加新的遍历方式**
## 迭代器模式的结构与实现
> 迭代器模式的结构
![image-20211213081310296](设计模式/image-20211213081310296.png)
> 迭代器模式包含以下4个角色
- Iterator抽象迭代器
- ConcreteIterator具体迭代器
- Aggregate抽象聚合类
- ConcreteAggregate具体聚合类
> 迭代器模式的实现
- 典型的**抽象迭代器**代码:
```java
public interface Iterator {
public void first(); //将游标指向第一个元素
public void next(); //将游标指向下一个元素
public boolean hasNext(); //判断是否存在下一个元素
public Object currentItem(); //获取游标指向的当前元素
}
```
- 典型的**具体迭代器**代码:
```java
public class ConcreteIterator implements Iterator {
private ConcreteAggregate objects; //维持一个对具体聚合对象的引用,以便于访问存储在聚合对象中的数据
private int cursor; //定义一个游标,用于记录当前访问位置
public ConcreteIterator(ConcreteAggregate objects) {
this.objects=objects;
}
public void first() { ...... }
public void next() { ...... }
public boolean hasNext() { ...... }
public Object currentItem() { ...... }
}
```
- 典型的**具体聚合类**代码:
```java
public class ConcreteAggregate implements Aggregate {
......
public Iterator createIterator() {
return new ConcreteIterator(this);
}
......
}
```
## 迭代器模式的应用实例
> 实例说明
某软件公司为某商场开发了一套销售管理系统在对该系统进行分析和设计时开发人员发现经常需要对系统中的商品数据、客户数据等进行遍历为了复用这些遍历代码开发人员设计了一个抽象的数据集合类AbstractObjectList将存储商品和客户等数据的类作为其子类AbstractObjectList类结构如下图所示
![image-20211213081926870](设计模式/image-20211213081926870.png)
> AbstractObjectList类的方法与说明
| **方法名** | **方法说明** |
| -------------------- | ------------------------------- |
| AbstractObjectList() | 构造方法用于给objects对象赋值 |
| addObject() | 增加元素 |
| removeObject() | 删除元素 |
| getObjects() | 获取所有元素 |
| next() | 移至下一个元素 |
| isLast() | 判断当前元素是否是最后一个元素 |
| previous() | 移至上一个元素 |
| isFirst() | 判断当前元素是否是第一个元素 |
| getNextItem() | 获取下一个元素 |
| getPreviousItem() | 获取上一个元素 |
AbstractObjectList类的子类ProductList和CustomerList分别用于存储商品数据和客户数据。
通过分析发现AbstractObjectList类的职责非常重它既负责存储和管理数据又负责遍历数据违背了单一职责原则实现代码将非常复杂。因此开发人员决定使用迭代器模式对AbstractObjectList类进行重构将负责遍历数据的方法提取出来封装到专门的类中实现数据存储和数据遍历分离还可以给不同的具体数据集合类提供不同的遍历方式。
现给出使用迭代器模式重构后的解决方案。
> 实例类图
![image-20211213082121066](设计模式/image-20211213082121066.png)
> 实例代码
- AbstractObjectList抽象聚合类
- ProductList商品数据类充当具体聚合类
- AbstractIterator抽象迭代器
- ProductIterator商品迭代器充当具体迭代器
- Client客户端测试类
> 结果及分析
- 如果需要**增加一个新的具体聚合类**,只需增加一个新的聚合子类和一个新的具体迭代器类即可,原有类库代码无须修改,**符合开闭原则**
- 如果需要**更换一个迭代器**,只需要**增加一个新的具体迭代器类**作为抽象迭代器类的子类,重新实现遍历方法即可,原有迭代器代码无须修改,也**符合开闭原则**
- 如果要**在迭代器中增加新的方法**,则需要修改抽象迭代器的源代码,这将**违背开闭原则**
## 使用内部类实现迭代器
> 实现
- JDK的AbstractList
```java
package java.util;
……
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
......
private class Itr implements Iterator<E> {
int cursor = 0;
......
}
……
}
```
```java
//使用内部类实现的商品数据类
public class ProductList extends AbstractObjectList {
public ProductList(List products) {
super(products);
}
public AbstractIterator createIterator() {
return new ProductIterator();
}
//商品迭代器:具体迭代器,内部类实现
private class ProductIterator implements AbstractIterator {
private int cursor1;
private int cursor2;
//省略其他代码
}
}
```
## Java内置迭代器
> 结构
![image-20211213082718441](设计模式/image-20211213082718441.png)
> 实现
- java.util.Collection
```java
package java.util;
 
public interface Collection<E> extends Iterable<E> {
……
boolean add(Object c);
boolean addAll(Collection c);
boolean remove(Object o);
boolean removeAll(Collection c);
boolean remainAll(Collection c);
Iterator iterator();
……
}
```
- java.util.Iterator
```java
package java.util;
 
public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}
```
> 应用
```java
import java.util.*;
public class IteratorDemo {
public static void process(Collection c) {
Iterator i = c.iterator(); //创建迭代器对象
//通过迭代器遍历聚合对象
while(i.hasNext()) {
System.out.println(i.next().toString());
}
}
public static void main(String args[]) {
Collection persons;
persons = new ArrayList(); //创建一个ArrayList类型的聚合对象
persons.add("张无忌");
persons.add("小龙女");
persons.add("令狐冲");
persons.add("韦小宝");
persons.add("袁紫衣");
persons.add("小龙女");
process(persons);
}
}
```
## 迭代器模式的优缺点与适用环境
> 优点
- 支持**以不同的方式遍历一个聚合对象**,在同一个聚合对象上**可以定义多种遍历方式**
- **简化了聚合类**
- 由于引入了抽象层,**增加新的聚合类和迭代器类都很方便**,无须修改原有代码,**符合开闭原则**
> 模式缺点
- 在增加新的聚合类时需要对应地增加新的迭代器类,**类的个数成对增加**,这在一定程度上**增加了系统的复杂性**
- **抽象迭代器的设计难度较大,需要充分考虑到系统将来的扩展。**在自定义迭代器时,**创建一个考虑全面的抽象迭代器并不是一件很容易的事情**

@ -0,0 +1,197 @@
## 中介者模式概述
> 介绍
- 联合国
![image-20211213083847190](设计模式/image-20211213083847190.png)
- QQ聊天示意图
![image-20211213083945888](设计模式/image-20211213083945888.png)
> 分析
- QQ聊天的两种方式
- **用户与用户直接聊天**,用户与用户之间存在**多对多的联系**,这将导致系统中用户之间的**关系非常复杂**,一个用户如果要将相同的信息或文件发送给其他所有用户,必须一个一个地发送
- **通过QQ群聊天**,用户只需要将信息或文件发送到群中或上传为群共享文件即可,群的作用就是将发送者所发送的信息和文件转发给每一个接收者,将**极大地减少系统中用户之间的两两通信**
- 软件开发:
- **网状结构****多对多联系**将导致系统非常复杂,几乎每个对象都需要与其他对象发生相互作用,而这种相互作用表现为一个对象与另外一个对象的直接耦合,这将**导致一个过度耦合的系统**
![image-20211213091153727](设计模式/image-20211213091153727.png)
- 星型结构:中介者模式将系统的网状结构变成以中介者为中心的星型结构,同事对象不再直接与另一个对象联系,它通过中介者对象与另一个对象发生相互作用。系统的结构不会因为新对象的引入带来大量的修改工作
![image-20211213091355927](设计模式/image-20211213091355927.png)
> 中介者模式的定义
**中介者模式****定义一个对象来封装一系列对象的交互。**中介者模式使各对象之间不需要显式地相互引用,**从而使其耦合松散,而且让你可以独立地改变它们之间的交互。**
**Mediator Pattern**: **Define an object that encapsulates how a set of objects interact.** Mediator promotes **loose coupling** by keeping objects from referring to each other explicitly, and **it lets you vary their interaction independently.**
- **对象行为型**模式
- 又称为**调停者模式**
- 在中介者模式中,通过**引入中介者来简化对象之间的复杂交互**
- 中介者模式是**迪米特法则**的一个典型应用
- 对象之间**多对多**的复杂关系转化为相对简单的**一对多**关系
## 中介者模式的结构与实现
> 中介者模式的结构
![image-20211213091651155](设计模式/image-20211213091651155.png)
> 中介者模式包含以下4个角色
- Mediator抽象中介者
- ConcreteMediator具体中介者
- Colleague抽象同事类
- ConcreteColleague具体同事类
> 中介者类的职责
- **中转作用(结构性)**:各个同事对象不再需要显式地引用其他同事,当需要和其他同事进行通信时,可**通过中介者来实现间接调用**
- **协调作用(行为性)**:中介者可以更进一步的对同事之间的关系进行封装,同事可以一致地和中介者进行交互,而不需要指明中介者需要具体怎么做,**中介者根据封装在自身内部的协调逻辑对同事的请求进行进一步处理,将同事成员之间的关系行为进行分离和封装**
> 实现
- 典型的**抽象中介者类**代码:
```java
public abstract class Mediator {
protected ArrayList<Colleague> colleagues = new ArrayList<Colleague>(); //用于存储同事对象
//注册方法,用于增加同事对象
public void register(Colleague colleague) {
colleagues.add(colleague);
}
//声明抽象的业务方法
public abstract void operation();
}
```
- 典型的**具体中介者类**代码:
```java
public class ConcreteMediator extends Mediator {
//实现业务方法,封装同事之间的调用
public void operation() {
......
((Colleague)(colleagues.get(0))).method1(); //通过中介者调用同事类的方法
......
}
}
```
- 典型的**抽象同事类**代码:
```java
public abstract class Colleague {
protected Mediator mediator; //维持一个抽象中介者的引用
public Colleague(Mediator mediator) {
this.mediator=mediator;
}
public abstract void method1(); //声明自身方法,处理自己的行为
//定义依赖方法,与中介者进行通信
public void method2() {
mediator.operation();
}
}
```
- 典型的具体同事类代码:
```java
public class ConcreteColleague extends Colleague {
public ConcreteColleague(Mediator mediator) {
super(mediator);
}
//实现自身方法
public void method1() {
......
}
}
```
## 中介者模式的应用实例
> 实例说明
某软件公司要开发一套CRM系统其中包含一个客户信息管理模块所设计的“客户信息管理窗口”界面效果图如下图所示
![image-20211213092332031](设计模式/image-20211213092332031.png)
通过分析发现,在上图中,界面组件之间存在较为复杂的交互关系:如果删除一个客户,将从客户列表(List)中删掉对应的项,客户选择组合框(ComboBox)中的客户名称也将减少一个;如果增加一个客户信息,则客户列表中将增加一个客户,且组合框中也将增加一项。
为了更好地处理界面组件之间的交互,现使用中介者模式设计该系统。
> 实例分析及类图
![image-20211213092436223](设计模式/image-20211213092436223.png)
![image-20211213092448559](设计模式/image-20211213092448559.png)
> 实例代码
- Mediator抽象中介者类
- ConcreteMediator具体中介者类
- Component抽象组件类充当抽象同事类
- Button按钮类充当具体同事类
- List列表框类充当具体同事类
- ComboBox组合框类充当具体同事类
- TextBox文本框类充当具体同事类
- Client客户端测试类
> 结果及分析
- 当某个**组件类**的**changed()**方法被调用时,**中介者**的componentChanged()方法将被调用在中介者的componentChanged()方法中再逐个调用与该组件有交互的其他组件的相关方法
- **如果某个组件类需要与新的组件进行交互**,无须修改已有组件类的源代码,**只需修改中介者或者对现有中介者进行扩展即可**,系统具有更好的灵活性和可扩展性
## 扩展中介者与同事类
> 目的
对 “客户信息管理窗口”进行改进,在窗口的下端能够及时显示当前系统中客户信息的总数
![image-20211213093025087](设计模式/image-20211213093025087.png)
> 解决方案
- 方案一增加一个界面组件类Label**修改原有的具体中介者类ConcreteMediator**增加一个对Label对象的引用
- 方案二增加一个界面组件类Label**增加一个ConcreteMediator的子类SubConcreteMediator**来实现对Label对象的引用
- 方案(2)更符合开闭原则
> 结构
![image-20211213093217139](设计模式/image-20211213093217139.png)
![image-20211213093223597](设计模式/image-20211213093223597.png)
## 中介者模式的优缺点与适用环境
> 模式优点
- **简化了对象之间的交互**,它用中介者和同事的一对多交互代替了原来同事之间的**多对多**交互,将原本难以理解的**网状结构**转换成相对简单的**星型结构**
- **可将各同事对象解耦**
- **可以减少子类生成**,中介者模式将原本分布于多个对象间的行为集中在一起,改变这些行为只需生成新的中介者子类即可,这使得各个同事类可被重用,无须直接对同事类进行扩展
> 模式缺点
- **在具体中介者类中包含了大量的同事之间的交互细节**,可能会导致**具体中介者类非常复杂**,使得系统难以维护
> 模式适用环境
- 系统中**对象之间存在复杂的引用关系**,系统结构混乱且难以理解
- 一个对象由于引用了其他很多对象并且直接和这些对象通信,导致**难以复用**该**对象**
- 想通过一个**中间类**来**封装多个类中的行为**,又**不想生成太多的子类**

@ -0,0 +1,194 @@
## 备忘录模式概述
- 备忘录模式——软件中的“**后悔药**”——**撤销(Undo)**
![image-20211215084815364](设计模式/image-20211215084815364.png)
> 分析
- 通过使用备忘录模式可以**让系统恢复到某一特定的历史状态**
- 首先保存软件系统的历史状态,当用户需要取消错误操作并且返回到某个历史状态时,可以**取出事先保存的历史状态来覆盖当前状态**
> 备忘录模式的定义
**备忘录模式****在不破坏封装的前提下**,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样**就可以在以后将对象恢复到原先保存的状态。**
**Memento Pattern**: **Without violating encapsulation**, capture and externalize an object's internal state so that **the object can be restored to this state later.**
- **对象行为型**模式
- 别名为**标记(Token)**模式
- 提供了一种**状态恢复**的实现机制,使得用户可以方便地**回到一个特定的历史步骤**
- 当前在很多软件所提供的**撤销(Undo)操作**中就使用了备忘录模式
## 备忘录模式的结构与实现
> 备忘录模式的结构
![image-20211215085325539](设计模式/image-20211215085325539.png)
> 备忘录模式包含以下3个角色
- Originator原发器
- Memento备忘录)
- Caretaker负责人
> 备忘录模式的实现
- 典型的**原发器类**代码:
```java
package designpatterns.memento;
public class Originator {
private String state;
public Originator(){}
//创建一个备忘录对象
public Memento createMemento() {
return new Memento(this);
}
//根据备忘录对象恢复原发器状态
public void restoreMemento(Memento m) {
state = m.state;
}
public void setState(String state) {
this.state=state;
}
public String getState() {
return this.state;
}
}
```
- 典型的**备忘录类**代码:
```java
package designpatterns.memento;
//备忘录类,默认可见性,包内可见
class Memento {
private String state;
Memento(Originator o) {
state = o.getState();
}
void setState(String state) {
this.state=state;
}
String getState() {
return this.state;
}
}
```
- 除了Originator类**不允许其他类来调用备忘录类Memento的构造函数与相关方法**
- **如果允许其他类调用setState()等方法,将导致在备忘录中保存的历史状态发生改变,通过撤销操作所恢复的状态就不再是真实的历史状态**,备忘录模式也就失去了本身的意义
- 理想的情况是**只允许生成该备忘录的原发器访问备忘录的内部状态**
- Java语言实现
- 将Memento类与Originator类**定义在同一个包(package)**中来实现封装使用默认可见性定义Memento类即**保证其在包内可见**
- 将**备忘录类作为原发器类的内部类**,使得只有原发器才可以访问备忘录中的数据,其他对象都无法使用备忘录中的数据
- 典型的**负责人类**代码:
```java
package designpatterns.memento;
public class Caretaker {
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento=memento;
}
}
```
## 备忘录模式的应用实例
某软件公司要使用Java语言开发一款可以运行在Android平台的触摸式中国象棋软件由于考虑到有些用户是“菜鸟”经常不小心走错棋还有些用户因为不习惯使用手指在手机屏幕上拖动棋子常常出现操作失误因此该中国象棋软件要提供“悔棋”功能在用户走错棋或操作失误后可恢复到前一个步骤。如下图所示
![image-20211215085907517](设计模式/image-20211215085907517.png)
为了实现“悔棋”功能,现使用备忘录模式来设计该中国象棋软件。
> 实例类图
![image-20211215085943429](设计模式/image-20211215085943429.png)
实例代码
- Chessman象棋棋子类充当原发器
- ChessmanMemento象棋棋子备忘录类充当备忘录
- MementoCaretaker象棋棋子备忘录管理类充当负责人
- Client客户端测试类
> 结果及分析
- 通过创建备忘录对象可以**将象棋棋子的历史状态信息记录下来**,在**“悔棋”**时取出存储在备忘录中的历史状态信息,**用历史状态来覆盖当前状态**,从而实现状态的撤销
```
棋子车当前位置为第1行第1列。
棋子车当前位置为第1行第4列。
棋子车当前位置为第5行第4列。
******悔棋******
棋子车当前位置为第1行第4列。
```
## 实现多次撤销
> 动机
- 有时候用户需要**撤销多步操作**
- **实现方案**:在负责人类中**定义一个集合**来存储多个备忘录,每个备忘录负责保存一个历史状态,**在撤销时可以对备忘录集合进行逆向遍历,回到一个指定的历史状态**,还可以**对备忘录集合进行正向遍历,实现重做(Redo)或恢复操作**,即取消撤销,让对象状态得到恢复
> 结构
![image-20211215090225110](设计模式/image-20211215090225110.png)
> 实现
```java
import java.util.*;
public class MementoCaretaker {
//定义一个集合来存储多个备忘录
private ArrayList<ChessmanMemento> mementolist = new ArrayList <ChessmanMemento>();
public ChessmanMemento getMemento(int i) {
return (ChessmanMemento)mementolist.get(i);
}
public void setMemento(ChessmanMemento memento) {
mementolist.add(memento);
}
}
```
## 备忘录模式的优缺点与适用环境
> 模式优点
- **提供了一种状态恢复的实现机制**,使得用户可以方便地回到一个特定的历史步骤
- **实现了对信息的封装**,一个备忘录对象是一种原发器对象状态的表示,不会被其他代码所改动
> 模式缺点
- **资源消耗过大**,如果需要保存的原发器类的成员变量太多,就不可避免地需要占用大量的存储空间,**每保存一次对象的状态都需要消耗一定的系统资源**
> 模式适用环境
- **保存一个对象在某一个时刻的全部状态或部分状态**,这样以后**需要时能够恢复到先前的状态,实现撤销操作**
- **防止外界对象破坏一个对象历史状态的封装性**,避免将对象历史状态的实现细节暴露给外界对象
## 思考题
> 如何使用**栈(Stack)**实现多步撤销(Undo)和重做(Redo)操作?

@ -0,0 +1,204 @@
## 观察者模式概述
> 交通信号灯与汽车示意图
![image-20211215090816291](设计模式/image-20211215090816291.png)
> 分析
- 交通信号灯 => 观察目标
- 汽车(汽车驾驶员) => 观察者
![image-20211215090933177](设计模式/image-20211215090933177.png)
- **软件系统****一个对象的状态或行为的变化将导致其他对象的状态或行为也发生改变**,它们之间将产生**联动**
- **观察者模式:**
- 定义了对象之间一种**一对多**的依赖关系,让一个对象的改变能够影响其他对象
- 发生改变的对象称为**观察目标**,被通知的对象称为**观察者**
- **一个观察目标**可以对应**多个观察者**
> 观察者模式的定义
**观察者模式**:定义对象之间的一种**一对多依赖关系**,使得每当**一个对象状态发生改变**时,其相关依赖对象**都得到通知并被自动更新。**
**Observer Pattern**: Define a **one-to-many dependency** between objects so that when **one object changes state**, all its dependents are **notified and updated automatically.**
- **对象行为型**模式
- 别名
- **发布-订阅(Publish/Subscribe)**模式
- **模型-视图(Model/View)**模式
- **源-监听器(Source/Listener)**模式
- **从属者(Dependents)**模式
## 观察者模式的结构与实现
> 观察者模式的结构
![image-20211215091618343](设计模式/image-20211215091618343.png)
> 观察者模式包含以下4个角色
- Subject目标
- ConcreteSubject具体目标
- Observer观察者
- ConcreteObserver具体观察者
> 观察者模式的实现
- 典型的**抽象目标类**代码:
```java
import java.util.*;
public abstract class Subject {
//定义一个观察者集合用于存储所有观察者对象
protected ArrayList observers<Observer> = new ArrayList();
//注册方法,用于向观察者集合中增加一个观察者
public void attach(Observer observer) {
observers.add(observer);
}
//注销方法,用于在观察者集合中删除一个观察者
public void detach(Observer observer) {
observers.remove(observer);
}
//声明抽象通知方法
public abstract void notify();
}
```
- 典型的**具体目标类**代码:
```java
public class ConcreteSubject extends Subject {
//实现通知方法
public void notify() {
//遍历观察者集合,调用每一个观察者的响应方法
for(Object obs:observers) {
((Observer)obs).update();
}
}
```
- 典型的**抽象观察者**代码:
```java
public interface Observer {
//声明响应方法
public void update();
}
```
- 典型的**具体观察者**代码:
```java
public class ConcreteObserver implements Observer {
//实现响应方法
public void update() {
//具体响应代码
}
}
```
- 说明:
- 有时候**在具体观察者类ConcreteObserver中需要使用到具体目标类ConcreteSubject中的状态属性**,会存在**关联或依赖关系**
- 如果在具体层之间具有关联关系,系统的扩展性将受到一定的影响,**增加新的具体目标类有时候需要修改原有观察者的代码**,在一定程度上违背了开闭原则,但是如果原有观察者类无须关联新增的具体目标,则系统扩展性不受影响
- 典型的**客户端**代码片段:
```java
// ……
Subject subject = new ConcreteSubject();
Observer observer = new ConcreteObserver();
subject.attach(observer); //注册观察者
subject.notify();
// ……
```
## 观察者模式的应用实例
> 实例说明
在某多人联机对战游戏中,多个玩家可以加入同一战队组成联盟,当战队中的某一成员受到敌人攻击时将给所有其他盟友发送通知,盟友收到通知后将做出响应。
现使用观察者模式设计并实现该过程,以实现战队成员之间的联动。
## JDK对观察者模式的支持
> 实例分析及类图
- 战队成员之间的联动过程:
- 联盟成员受到攻击 => 发送通知给盟友 => 盟友做出响应
![image-20211215092356162](设计模式/image-20211215092356162.png)
![image-20211215092412883](设计模式/image-20211215092412883.png)
> 实例代码
- AllyControlCenter指挥部战队控制中心充当抽象目标类
- ConcreteAllyControlCenter具体指挥部类充当具体目标类
- Observer抽象观察者类
- Player战队成员类充当具体观察者类
- Client客户端测试类
> 结果及分析
- 两次对象之间的联动,触发链:**Player.beAttacked() => AllyControlCenter.notifyObserver() => Player.help()**
> JDK对观察者模式的支持
- java.util.Observer
- java.util.Observable![image-20211215092646191](设计模式/image-20211215092646191.png)
## 观察者模式与Java事件处理
> 分析
- **事件源对象充当观察目标角色,事件监听器充当抽象观察者角色,事件处理对象充当具体观察者角色**
- 如果事件源对象的某个事件触发,则调用事件处理对象中的事件处理程序来对事件进行处理
- **事件源(Event Source) Subject**
- 例如:**JButtonaddActionListener()**:注册方法,**fireXXX()** 通知方法
- 事件监听器(Event Listener)**Observer**
- 例如:**ActionListener****actionPerformed()**:响应方法
- 事件处理类(Event Handling Class)**ConcreteObserver**
- 例如:**LoginHandling**实现ActionListener接口
> 结构
![image-20211215092911235](设计模式/image-20211215092911235.png)
## 观察者模式与MVC
> MVC(Model-View-Controller)架构
- **模型(Model)****视图(View)**和**控制器(Controller)**
- **模型**可对应于观察者模式中的**观察目标**,而**视图**对应于**观察者****控制器**可充当两者之间的**中介者**
- 当**模型层**的数据发生改变时,**视图层**将自动改变其显示内容
![image-20211215093053070](设计模式/image-20211215093053070.png)
## 观察者模式的优缺点与适用环境
> 模式优点
- 可以**实现表示层和数据逻辑层的分离**
- 在观察目标和观察者之间**建立一个抽象的耦合**
- 支持**广播通信****简化了一对多系统设计的难度**
- **符合开闭原则**,增加新的具体观察者无须修改原有系统代码,在具体观察者与观察目标之间不存在关联关系的情况下,增加新的观察目标也很方便
> 模式缺点
- 将所有的观察者都通知到会**花费很多时间**
- 如果存在**循环依赖**时**可能导致系统崩溃**
- **没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的**,而只是知道观察目标发生了变化
> 模式适用环境
- 一个抽象模型有两个方面,其中**一个方面依赖于另一个方面**,将这两个方面封装在独立的对象中使它们**可以各自独立地改变和复用**
- **一个对象的改变将导致一个或多个其他对象发生改变**,且并**不知道具体有多少对象将发生改变**,也**不知道这些对象是谁**
- 需要在系统中**创建一个触发链**

@ -0,0 +1,257 @@
## 状态模式概述
> H2O的三种状态未考虑临界点
![image-20211215143701987](设计模式/image-20211215143701987.png)
> 分析
- 在软件系统中:
- 有些对象具有**多种状态**
- 这些状态在某些情况下**能够相互转换**
- 对象在不同的状态下将**具有不同的行为**
- 复杂的**条件判断语句**来进行状态的判断和转换操作 => **导致代码的可维护性和灵活性下降** => 出现新的状态时,代码的扩展性很差,客户端代码也需要进行相应的修改,**违背了开闭原则**
```java
public class TestXYZ {
int behaviour;
//省略Getter和Setter方法
......
public void handleAll() {
if (behaviour == 0) {
//do something }
else if (behaviour == 1) {
//do something }
else if (behaviour == 2) {
//do something }
else if (behaviour == 3) {
//do something }
... some more else if ...
}
}
```
> 状态模式的定义
状态模式:允许一个对象在其**内部状态改变时改变它的行为。对象看起来似乎修改了它的类。**
**State Pattern**: Allow an object to **alter its behavior when its internal state changes. The object will appear to change its class.**
- **对象行为型**模式
- 又名**状态对象(Objects for States)**
- 用于解决系统中**复杂对象的状态转换以及不同状态下行为的封装问题**
- **将**一个对象的**状态从该对象中分离**出来,封装到专门的**状态类**中,使得对象状态可以灵活变化
- 对于**客户端**而言,**无须关心对象状态的转换以及对象所处的当前状态**,无论对于何种状态的对象,客户端都可以一致处理
## 状态模式的结构与实现
> 状态模式的结构
![image-20211215144207433](设计模式/image-20211215144207433.png)
> 状态模式包含以下3个角色
- Context环境类
- State抽象状态类
- ConcreteState具体状态类
> 状态模式的实现
- 典型的**抽象状态类**代码:
```java
public abstract class State {
//声明抽象业务方法,不同的具体状态类可以有不同的实现
public abstract void handle();
}
```
- 典型的**具体状态类**代码:
```java
public class ConcreteState extends State {
public void handle() {
//方法具体实现代码
}
}
```
- 典型的**环境类**代码:
```java
public class Context {
private State state; //维持一个对抽象状态对象的引用
private int value; //其他属性值,该属性值的变化可能会导致对象的状态发生变化
public void setState(State state) {
this.state = state;
}
public void request() {
//其他代码
state.handle(); //调用状态对象的业务方法
//其他代码
}
}
```
- 状态转换的实现:
- 统一由**环境类**来负责状态之间的转换,环境类充当了**状态管理器(State Manager)**角色
```java
// ……
public void changeState()
{
//判断属性值,根据属性值进行状态转换
if (value == 0)
{
this.setState(new ConcreteStateA());
}
else if (value == 1)
{
this.setState(new ConcreteStateB());
}
......
}
// ……
```
- 由**具体状态类**来负责状态之间的转换,可以**在具体状态类的业务方法中判断环境类的某些属性值**,再根据情况为环境类设置新的状态对象,实现状态转换
```java
// ……
public void changeState(Context ctx) {
//根据环境对象中的属性值进行状态转换
if (ctx.getValue() == 1) {
ctx.setState(new ConcreteStateB());
}
else if (ctx.getValue() == 2) {
ctx.setState(new ConcreteStateC());
}
......
}
// ……
```
## 状态模式的应用实例
> 实例说明
某软件公司要为一银行开发一套信用卡业务系统,银行账户(Account)是该系统的核心类之一通过分析该软件公司开发人员发现在系统中账户存在3种状态且在不同状态下账户存在不同的行为具体说明如下
(1) 如果账户中余额大于等于0则账户的状态为正常状态(Normal State),此时用户既可以向该账户存款也可以从该账户取款;
(2) 如果账户中余额小于0并且大于-2000则账户的状态为透支状态(Overdraft State),此时用户既可以向该账户存款也可以从该账户取款,但需要按天计算利息;
(3) 如果账户中余额等于-2000那么账户的状态为受限状态(Restricted State),此时用户只能向该账户存款,不能再从中取款,同时也将按天计算利息;
(4) 根据余额的不同以上3种状态可发生相互转换。
现使用状态模式设计并实现银行账户状态的转换。
> 实例分析和类图
![image-20211215145047718](设计模式/image-20211215145047718.png)
![image-20211215145100679](设计模式/image-20211215145100679.png)
> 实例代码
- Account银行账户充当环境类
- AccountState账户状态类充当抽象状态类
- NormalState正常状态类充当具体状态类
- OverdraftState透支状态类充当具体状态类
- RestrictedState受限状态类充当具体状态类
- Client客户端测试类
> 结果与分析
```
段誉开户初始金额为0.0
---------------------------------------------
段誉存款1000.0
现在余额为1000.0
现在帐户状态为designpatterns.state.NormalState
---------------------------------------------
段誉取款2000.0
现在余额为-1000.0
现在帐户状态为designpatterns.state.OverdraftState
---------------------------------------------
段誉存款3000.0
现在余额为2000.0
现在帐户状态为designpatterns.state.NormalState
---------------------------------------------
段誉取款4000.0
现在余额为-2000.0
现在帐户状态为designpatterns.state.RestrictedState
---------------------------------------------
段誉取款1000.0
帐号受限,取款失败
现在余额为-2000.0
现在帐户状态为designpatterns.state.RestrictedState
---------------------------------------------
计算利息!
```
## 共享状态
> 动机
- 在有些情况下,**多个环境对象可能需要共享同一个状态**
- 如果希望在系统中实现多个环境对象共享一个或多个状态对象,那么需要**将这些状态对象定义为环境类的静态成员对象**
> 结构
![image-20211215145338906](设计模式/image-20211215145338906.png)
> 实现
- 开关类Switch环境类
- 开关状态类SwitchState (抽象状态类)
- 打开状态类OnState具体状态类
- 关闭状态类OffState (具体状态类)
- 客户端测试类Client
## 使用环境类实现状态转换
> 动机
- 对于**客户端**而言,**无须关心状态类**,可以为环境类设置默认的状态类,将状态的转换工作交给环境类(或具体状态类)来完成,**具体的转换细节对于客户端而言是透明的**
- 可以通过**环境类**来**实现状态转换**,环境类作为一个**状态管理器**,统一实现各种状态之间的转换操作
> 实例
现欲开发一个屏幕放大镜工具,其具体功能描述如下:
用户单击“放大镜”按钮之后屏幕将放大一倍,再单击一次“放大镜”按钮屏幕再放大一倍,第三次单击该按钮后屏幕将还原到默认大小。
现使用状态模式来设计该屏幕放大镜工具。
> 结构
![image-20211215145800522](设计模式/image-20211215145800522.png)
> 实现
- 屏幕类Screen (环境类)
- 抽象状态类State
- 正常状态类NormalState (具体状态类)
- 二倍状态类LargerState (具体状态类)
- 四倍状态类LargestState (具体状态类)
- 客户端测试类Client
## 状态模式的优缺点与适用环境
> 模式优点
- **封装了状态的转换规则**,可以对状态转换代码进行**集中管理**,而不是分散在一个个业务方法中
- **将所有与某个状态有关的行为放到一个类中**,只需要注入一个不同的状态对象即可使环境对象拥有不同的行为
- **允许状态转换逻辑与状态对象合成一体,而不是提供一个巨大的条件语句块**,可以避免使用庞大的条件语句来将业务方法和状态转换代码交织在一起
- 可以让多个环境对象**共享一个状态对象**,从而**减少系统中对象的个数**
> 模式缺点
- 会**增加系统中类和对象的个数,导致系统运行开销增大**
- 结构与实现都较为复杂,**如果使用不当将导致程序结构和代码混乱,增加系统设计的难度**
- **对开闭原则的支持并不太好**,增加新的状态类需要修改负责状态转换的源代码,否则无法转换到新增状态;而且修改某个状态类的行为也需要修改对应类的源代码
> 模式适用环境
- 对象的**行为依赖于**它的**状态**(例如某些属性值),**状态的改变将导致行为的变化**
- **在代码中包含大量与对象状态有关的条件语句**,这些条件语句的出现会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,并且导致客户类与类库之间的耦合增强

@ -0,0 +1,152 @@
## 策略模式概述
> 旅游出行方式示意图
![image-20211215150303155](设计模式/image-20211215150303155.png)
> 分析
- 实现某个目标的途径不止一条,可根据实际情况选择一条合适的途径
- 软件开发:
- 多种算法,例如排序、查找、打折等
- 使用**硬编码(Hard Coding)**实现将导致系统违背开闭原则,扩展性差,且维护困难
- 可以定义一些独立的类来封装不同的算法,**每一个类封装一种具体的算法** => **策略类**
> 策略模式的定义
**策略模式**:定义**一系列算法,将每一个算法封装起来**,并让它们可以相互替换。策略模式让算法可以独立于使用它的客户变化。
**Strategy Pattern**: **Define a family of algorithms, encapsulate each one**, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
- **对象行为型**模式
- 又称为**政策(Policy)模式**
- 每一个封装算法的类称之为**策略(Strategy)类**
- 策略模式提供了一种**可插入式(Pluggable)算法**的实现方案
## 策略模式的结构与实现
> 策略模式的结构
![image-20211215150848304](设计模式/image-20211215150848304.png)
> 策略模式包含以下3个角色
- Context环境类
- Strategy抽象策略类
- ConcreteStrategy具体策略类
> 策略模式的实现
- 典型的**抽象策略类**代码:
```java
public abstract class Strategy {
public abstract void algorithm(); //声明抽象算法
}
```
- 典型的**具体策略类**代码:
```java
public class ConcreteStrategyA extends Strategy {
//算法的具体实现
public void algorithm() {
//算法A
}
}
```
- 典型的**环境类**代码:
```java
public class Context {
private Strategy strategy; //维持一个对抽象策略类的引用
//注入策略对象
public void setStrategy(Strategy strategy) {
this.strategy= strategy;
}
//调用策略类中的算法
public void algorithm() {
strategy.algorithm();
}
}
```
- 典型的**客户端**代码片段
```java
// ……
Context context = new Context();
Strategy strategy;
strategy = new ConcreteStrategyA(); //可在运行时指定类型,通过配置文件和反射机制实现
context.setStrategy(strategy);
context.algorithm();
// ……
```
## 策略模式的应用实例
> 实例说明
某软件公司为某电影院开发了一套影院售票系统,在该系统中需要为不同类型的用户提供不同的电影票打折方式,具体打折方案如下:
(1) 学生凭学生证可享受票价8折优惠。
(2) 年龄在10周岁及以下的儿童可享受每张票减免10元的优惠原始票价需大于等于20元
(3) 影院VIP用户除享受票价半价优惠外还可进行积分积分累计到一定额度可换取电影院赠送的奖品。
该系统在将来可能还要根据需要引入新的打折方式。现使用策略模式设计该影院售票系统的打折方案。
> 实例类图
![image-20211215151549027](设计模式/image-20211215151549027.png)
> 实例代码
- MovieTicket电影票类充当环境类
- Discount折扣类充当抽象策略类
- StudentDiscount学生票折扣类充当具体策略类
- ChildrenDiscount儿童票折扣类充当具体策略类
- VIPDiscountVIP会员票折扣类充当具体策略类
- Client客户端测试类
> 结果及分析
- 如果需要更换具体策略类,无须修改源代码,只需修改**配置文件**即可,完全**符合开闭原则**
```java
<?xml version="1.0"?>
<config>
<className>designpatterns.strategy.StudentDiscount</className>
</config>
```
## Java SE中的布局管理
![image-20211215152043337](设计模式/image-20211215152043337.png)
## 策略模式的优缺点与适用环境
> 模式优点
- 提供了**对开闭原则的完美支持**,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为
- 提供了**管理相关的算法族的办法**
- 提供了一种**可以替换继承关系的办法**
- 可以**避免多重条件选择语句**
- 提供了一种**算法的复用机制**,不同的环境类可以方便地复用策略类
> 模式缺点
- 客户端**必须知道所有的策略类**,并自行决定使用哪一个策略类
- 将造成系统**产生很多具体策略类**
- **无法同时在客户端使用多个策略类**
> 模式适用环境
- 一个系统需要**动态地在几种算法中选择一种**
- **避免使用难以维护的多重条件选择语句**
- 不希望客户端知道复杂的、与算法相关的数据结构,**提高算法的保密性与安全性**
## 思考题
> 在策略模式中一个环境类Context能否对应多个不同的策略等级结构如何设计

@ -0,0 +1,230 @@
## 模板方法模式概述
> 请客吃饭示意图
![image-20211215152542700](设计模式/image-20211215152542700.png)
> 分析
- 请客吃饭:(1) 点单 => (2) **吃东西** => (3) 买单
- 软件开发:某个方法的实现需要**多个步骤**(类似“请客”),其中**有些步骤是固定的**(类似“点单”和“买单”),而**有些步骤并不固定**,存在可变性(类似“吃东西”)
- **模板方法模式****基本方法**(“点单”、“吃东西”和“买单”) <==> **模板方法** “请客”)
> 模板方法模式的定义
**模板方法模式**:定义一个操作中**算法的框架**,而将**一些步骤延迟到子类中**。模板方法模式使得子类不改变一个算法的结构即可重定义该算法的**某些特定步骤**。
**Template Method Pattern**: Define **the skeleton of an algorithm** in an operation, **deferring some steps to subclasses**. Template Method lets subclasses **redefine certain steps** of an algorithm without changing the algorithm's structure.
- **类行为型**模式
- 是一种**基于继承的代码复用技术**
- 将一些复杂流程的**实现步骤**封装在一系列**基本方法**中
- 在抽象父类中提供一个称之为**模板方法**的方法来**定义这些基本方法的执行次序**,而通过其子类来覆盖某些步骤,从而使得**相同的算法框架**可以**有不同的执行结果**
## 模板方法模式的结构与实现
> 模板方法模式的结构
![image-20211215153213666](设计模式/image-20211215153213666.png)
> 模板方法模式包含以下两个角色:
- AbstractClass抽象类
- ConcreteClass具体子类
> 模板方法模式的实现
- 模板方法 (Template Method)
- 基本方法 (Primitive Method)
- 抽象方法(Abstract Method)
- 具体方法(Concrete Method)
- 钩子方法(Hook Method)
- “挂钩”方法: isXXX()或hasXXX()返回类型为boolean类型
- 空方法
```java
// ……
//模板方法
public void template() {
open();
display();
//通过钩子方法来确定某一步骤是否执行
if(isPrint()) {
print();
}
}
 
//钩子方法
public boolean isPrint() {
return true;
}
// ……
```
- **抽象类**典型代码:
```java
public abstract class AbstractClass {
//模板方法
public void templateMethod() {
primitiveOperation1();
primitiveOperation2();
primitiveOperation3();
}
//基本方法—具体方法
public void primitiveOperation1() {
//实现代码
}
//基本方法—抽象方法
public abstract void primitiveOperation2();
//基本方法—钩子方法
public void primitiveOperation3()
{ }
}
```
- **具体子类**典型代码:
```java
public class ConcreteClass extends AbstractClass {
public void primitiveOperation2() {
//实现代码
}
public void primitiveOperation3() {
//实现代码
}
}
```
## 模板方法模式的应用实例
> 实例说明
某软件公司要为某银行的业务支撑系统开发一个利息计算模块,利息的计算流程如下:
(1) 系统根据账号和密码验证用户信息,如果用户信息错误,则系统显示出错提示。
(2) 如果用户信息正确,则根据用户类型的不同使用不同的利息计算公式计算利息(如活期账户和定期账户具有不同的利息计算公式)。
(3) 系统显示利息。
现使用模板方法模式设计该利息计算模块。
> 实例类图
![image-20211215153857412](设计模式/image-20211215153857412.png)
> 实例代码
- Account账户类充当抽象类
- CurrentAccount活期账户类充当具体子类
- SavingAccount定期账户类充当具体子类
- Client客户端测试类
> 结果及分析
- 如果需要更换或增加**具体子类**,无须修改源代码,只需修改配置文件即可,**符合开闭原则**
```java
<?xml version="1.0"?>
<config>
<className>designpatterns.templatemethod.CurrentAccount</className>
</config>
```
## 钩子方法的使用
> 实例
某软件公司要为销售管理系统提供一个数据图表显示功能,该功能的实现包括以下几个步骤:
(1) 从数据源获取数据。
(2) 将数据转换为XML格式。
(3) 以某种图表方式显示XML格式的数据。
该功能支持多种数据源和多种图表显示方式但所有的图表显示操作都基于XML格式的数据因此**可能需要对数据进行转换如果从数据源获取的数据已经是XML数据则无须转换。**
> 结构
![image-20211215154100731](设计模式/image-20211215154100731.png)
> 实现
```java
//designpatterns.templatemethod.hookmethod.DataViewer.java
package designpatterns.templatemethod.hookmethod;
public abstract class DataViewer {
//抽象方法:获取数据
public abstract void getData();
//具体方法:转换数据
public void convertData() {
System.out.println("将数据转换为XML格式。");
}
//抽象方法:显示数据
public abstract void displayData();
//钩子方法判断是否为XML格式的数据
public boolean isNotXMLData() {
return true;
}
//模板方法
public void process() {
getData();
//如果不是XML格式的数据则进行数据转换
if (isNotXMLData()) {
convertData();
}
displayData();
}
}
```
```java
//designpatterns.templatemethod.hookmethod.XMLDataViewer.java
package designpatterns.templatemethod.hookmethod;
public class XMLDataViewer extends DataViewer {
//实现父类方法:获取数据
public void getData() {
System.out.println("从XML文件中获取数据。");
}
//实现父类方法:显示数据,默认以柱状图方式显示,可结合桥接模式来改进
public void displayData() {
System.out.println("以柱状图显示数据。");
}
//覆盖父类的钩子方法
public boolean isNotXMLData() {
return false;
}
}
```
## 模板方法模式的优缺点与适用环境
> 模式优点
- 在父类中形式化地定义一个算法,而由它的子类来实现细节的处理,**在子类实现详细的处理算法时并不会改变算法中步骤的执行次序**
- 提取了类库中的公共行为,**将公共行为放在父类中**,而通过其子类来实现不同的行为
- **可实现一种反向控制结构**,通过子类覆盖父类的钩子方法来决定某一特定步骤是否需要执行
- 更换和增加新的子类很方便,**符合单一职责原则和开闭原则**
> 模式缺点
- 需要为每一个基本方法的不同实现提供一个子类,**如果父类中可变的基本方法太多,将会导致类的个数增加,**系统会更加庞大,设计也更加抽象(可结合**桥接模式**
> 模式适用环境
- **一次性实现一个算法的不变部分**,并将**可变的行为留给子类来实现**
- 各子类中**公共的行为**应被提取出来,并集中到一个**公共父类**中,以**避免代码重复**
- 需要**通过子类来决定父类算法中某个步骤是否执行**,实现**子类对父类的反向控制**
## 思考题
> 在模板方法模式中,钩子方法如何实现子类控制父类的行为?

@ -0,0 +1,205 @@
## 访问者模式概述
> 医院处方单处理示意图
![image-20211215154552231](设计模式/image-20211215154552231.png)
> 分析
- 处方单:
- 药品信息的集合,**包含一种或多种不同类型的药品信息**
- **不同类型的工作人员**(例如划价人员和药房工作人员)在操作同一个药品信息集合时将**提供不同的处理方式**
- 可能会**增加新类型的工作人员**来操作处方单
- 软件开发:
- 处方单 <==> 对象结构
- 药品信息 <==> 元素
- 工作人员 <==> 访问者
- 对象结构中存储了**多种不同类型**的对象信息
- 对同一对象结构中的元素的**操作方式并不唯一**,可能
- 需要**提供多种不同的处理方式**
- 还有可能需要**增加新的处理方式**
![image-20211215154830203](设计模式/image-20211215154830203.png)
> 访问者模式的定义
**访问者模式**:表示一个作用于某**对象结构**中的各个元素的操作。访问者模式让你**可以在不改变各元素的类的前提下定义作用于这些元素的新操作。**
**Visitor Pattern**: Represent an operation to be performed on the elements of an **object structure**. Visitor lets you **define a new operation without changing the classes of the elements** on which it operates.
- **对象行为型**模式
- 它为**操作存储不同类型元素的对象结构**提供了一种解决方案
- 用户**可以对不同类型的元素施加不同的操作**
## 访问者模式的结构与实现
> 访问者模式的结构
![image-20211215155056952](设计模式/image-20211215155056952.png)
> 访问者模式包含以下5个角色
- Visitor抽象访问者
- ConcreteVisitor具体访问者
- Element抽象元素
- ConcreteElement具体元素
- ObjectStructure对象结构
> 访问者模式的实现
- 典型的**抽象访问者类**代码:
```java
public abstract class Visitor {
public abstract void visit(ConcreteElementA elementA);
public abstract void visit(ConcreteElementB elementB);
public void visit(ConcreteElementC elementC) {
//元素ConcreteElementC操作代码
}
}
```
- 典型的**具体访问者类**代码:
```java
public class ConcreteVisitor extends Visitor {
public void visit(ConcreteElementA elementA) {
//元素ConcreteElementA操作代码
}
public void visit(ConcreteElementB elementB) {
//元素ConcreteElementB操作代码
}
}
```
- 典型的**抽象元素类**代码:
```java
public interface Element {
public void accept(Visitor visitor);
}
```
- 典型的**具体元素类**代码:
```java
public class ConcreteElementA implements Element {
public void accept(Visitor visitor) {
visitor.visit(this);
}
public void operationA() {
//业务方法
}
}
```
- 双重委派机制
![image-20211215155648995](设计模式/image-20211215155648995.png)
- 调用具体元素类的accept(Visitor visitor)方法并将Visitor子类对象作为其参数
- 在具体元素类accept(Visitor visitor)方法内部调用传入的Visitor对象的visit()方法例如visit(ConcreteElementA elementA),将当前具体元素类对象(this)作为参数例如visitor.visit(this)
- 执行Visitor对象的visit()方法,在其中还可以调用具体元素对象的业务方法
- 典型的**对象结构**代码:
```java
import java.util.*;
public class ObjectStructure
{
private ArrayList<Element> list = new ArrayList<Element>(); //定义一个集合用于存储元素对象
//接受访问者的访问操作
public void accept(Visitor visitor) {
Iterator i=list.iterator();
while(i.hasNext()) {
((Element)i.next()).accept(visitor); //遍历访问集合中的每一个元素
}
}
public void addElement(Element element) {
list.add(element);
}
public void removeElement(Element element) {
list.remove(element);
}
}
```
## 访问者模式的应用实例
> 实例说明
某公司OA系统中包含一个员工信息管理子系统该公司员工包括正式员工和临时工每周人力资源部和财务部等部门需要对员工数据进行汇总汇总数据包括员工工作时间、员工工资等。该公司基本制度如下
(1) 正式员工每周工作时间为40小时不同级别、不同部门的员工每周基本工资不同如果超过40小时超出部分按照100元/小时作为加班费如果少于40小时所缺时间按照请假处理请假所扣工资以80元/小时计算,直到基本工资扣除到零为止。除了记录实际工作时间外,人力资源部需记录加班时长或请假时长,作为员工平时表现的一项依据。
(2) 临时工每周工作时间不固定,基本工资按小时计算,不同岗位的临时工小时工资不同。人力资源部只需记录实际工作时间。
人力资源部和财务部工作人员可以根据各自的需要对员工数据进行汇总处理,人力资源部负责汇总每周员工工作时间,而财务部负责计算每周员工工资。
现使用访问者模式设计该系统绘制类图并使用Java语言编码实现。
> 实例类图
![image-20211215155947189](设计模式/image-20211215155947189.png)
> 实例代码
- Employee员工类充当抽象元素类
- FulltimeEmployee全职员工类充当具体元素类
- ParttimeEmployee兼职员工类充当具体元素类
- Department部门类充当抽象访问者类
- FADepartment财务部类充当具体访问者类
- HRDepartment人力资源部类充当具体访问者类
- EmployeeList员工列表类充当对象结构
- Client客户端测试类
> 结果及分析
- 如果需要增加或更换**具体访问者类**,无须修改源代码,只需修改**配置文件**,从**增加新的访问者**的角度来看,完全**符合开闭原则**
- 如果要在系统中增加一种新的**具体元素**,必须对原有系统进行修改,在原有的抽象访问者类和具体访问者类中增加相应的访问方法,从**增加新的元素**的角度来看,访问者模式**违背了开闭原则**
- 开**闭原则的倾斜性**
```xml
<?xml version="1.0"?>
<config>
<className>designpatterns.visitor.FADepartment</className>
</config>
```
```
正式员工张无忌实际工资为3700.0元。
正式员工杨过实际工资为2000.0元。
正式员工段誉实际工资为2240.0元。
临时工洪七公实际工资为1600.0元。
临时工郭靖实际工资为1080.0元。
```
## 访问者模式与组合模式联用
![image-20211215160314495](设计模式/image-20211215160314495.png)
## 访问者模式的优缺点与适用环境
> 模式优点
- **增加新的访问操作很方便**
- 将有关元素对象的访问行为集中到一个访问者对象中,而不是分散在一个个的元素类中,**类的职责更加清晰**
- 让用户能够在**不修改现有元素类层次结构的情况下,定义作用于该层次结构的操作**
> 模式缺点
- **增加新的元素类很困难**
- **破坏了对象的封装性**
> 模式适用环境
- 一个对象结构包含多个类型的对象,**希望对这些对象实施一些依赖其具体类型的操作**
- 需要**对一个对象结构中的对象进行很多不同的且不相关的操作**,并需要**避免**让这些操作**“污染”**这些对象的类,也不希望在增加新操作时修改这些类
- **对象结构中对象对应的类很少改变**,但经常需要在此对象结构上定义新的操作
## 思考题
> 什么是双重分派机制?如何用代码实现?

@ -0,0 +1,319 @@
## UML概述
![image-20211215165429033](设计模式/image-20211215165429033.png)
![image-20211215165523696](设计模式/image-20211215165523696.png)
- UML是一个通用的**可视化建模语言**,不同于编程语言,它**通过一些标准的图形符号和文字来对系统进行建模**
- 用于对软件进行描述、可视化处理、构造和建立软件系统制品的文档
- 是一套总结了以往建模技术的经验并吸收了当今最优秀成果的标准建模方法
> UML的结构
- 视图(View)
![image-20211215165626072](设计模式/image-20211215165626072.png)
- 图(Diagram)13种(UML 2.X)
- 用例图(Use Case Diagram)
- 类图(Class Diagram)
- 对象图(Object Diagram)
- 包图(Package Diagram)
- 组合结构图(Composite Structure Diagram)
- 状态图(State Diagram)
- 活动图(Activity Diagram)
- 顺序图(Sequence Diagram)
- 通信图(Communication Diagram)
- 定时图(Timing Diagram)
- 交互概览图(Interaction Overview Diagram)
- 组件图(Component Diagram)
- 部署图(Deployment Diagram)
- 模型元素(Model Element)
- UML图中所使用的一些概念对应于普通的面向对象概念
- 同一个模型元素可以在多个不同的UML图中使用但是无论在哪个图中**同一个模型元素都必须保持相同的意义并具有相同的符号**
- 通用机制(General Mechanism)
- UML提供的通用机制**为模型元素提供额外的注释、语义和其他信息**,包括**扩展机制**允许用户对UML进行扩展
## 类与类的UML表示
> 类
- **类(Class)封装了数据和行为**,是面向对象的重要组成部分,**它是具有相同属性、操作、关系的对象集合的总称**
- 在系统中,**每个类都具有一定的职责**,职责指的是类要完成什么样的功能,要承担什么样的义务。**一个类可以有多种职责,设计得好的类通常有且仅有一种职责。**在定义类的时候,将类的职责分解成为类的属性和操作(即方法)
- **类的属性即类的数据职责,类的操作即类的行为职责**
- 类**实例化成对象(Object)****对象**对应于某个具体的事物,是**类的实例(Instance)**
- **类图(Class Diagram)**使用出现在系统中的不同类来**描述系统的静态结构**,它**用来描述不同的类以及它们之间的关系**
> 类的UML图示
- 在UML类图中类一般由三部分组成
- 第一部分是**类名**:每个类都必须有一个名字,类名是一个字符串。
- 按照Java语言的命名规范类名中每一个单词的首字母均大写。
![image-20211215170100117](设计模式/image-20211215170100117.png)
- 第二部分是**类的属性(Attributes)**:属性是指类的性质,即类的成员变量。一个类可以有任意多个属性,也可以没有属性。
- 按照Java语言的命名规范属性名中的第一个单词全小写之后每个单词首字母大写。**(驼峰命名法)**
![image-20211215170137347](设计模式/image-20211215170137347.png)
- 第三部分是**类的操作(Operations)**:操作是类的任意一个实例对象都拥有的行为,是类的成员方法。
- 按照Java语言的命名规范方法名中的第一个单词全小写之后每个单词首字母大写。
![image-20211215170205829](设计模式/image-20211215170205829.png)
## 类之间的关系
### 关联关系
- **关联(Association)关系**是类与类之间最常用的一种关系,它是一种结构化关系,**用于表示一类对象与另一类对象之间有联系**
- 在UML类图中**用实线连接有关联关系的对象所对应的类**在使用Java、C++和C#等编程语言实现关联关系时,通常将**一个类的对象作为另一个类的成员变量**
- 在使用类图表示关联关系时**可以在关联线上标注角色名**
![image-20211215170348389](设计模式/image-20211215170348389.png)
```java
public class LoginForm {
private JButton loginButton; //定义为成员变量
// ……
}
public class JButton {
// ……
}
```
- 双向关联:默认情况下,关联是**双向的**
![image-20211215170438715](设计模式/image-20211215170438715.png)
```java
public class Customer {
private Product[] products;
// ……
}
public class Product{
private Customer customer;
// ……
}
```
- 单向关联:类的关联关系也可以是**单向的**,单向关联**用带箭头的实线表示**
![image-20211215170533413](设计模式/image-20211215170533413.png)
```java
public class Customer {
private Address address;
// ……
}
public class Address {
// ……
}
```
- 自关联:在系统中可能会存在**一些类的属性对象类型为该类本身**,这种特殊的关联关系称为自关联
![image-20211215170702545](设计模式/image-20211215170702545.png)
```java
public class Node {
private Node subNode;
// ……
}
```
- 多重性关联 :多重性关联关系又称为**重数性(Multiplicity)关联关系**,表示**两个关联对象在数量上的对应关系**。在UML中**对象之间的多重性可以直接在关联直线上用一个数字或一个数字范围表示**
| **表示方式** | **多重性说明** |
| ------------ | ---------------------------------------------------------- |
| 1..1 | 表示另一个类的一个对象只与该类的一个对象有关系 |
| 0..* | 表示另一个类的一个对象与该类的零个或多个对象有关系 |
| 1..* | 表示另一个类的一个对象与该类的一个或多个对象有关系 |
| 0..1 | 表示另一个类的一个对象没有或只与该类的一个对象有关系 |
| m..n | 表示另一个类的一个对象与该类最少m最多n个对象有关系 (m≤n) |
![image-20211215170816188](设计模式/image-20211215170816188.png)
```java
public class Form {
private Button[] buttons; //定义一个集合对象
……
}
public class Button {
}
```
- 聚合关系
- **聚合(Aggregation)关系**表示整体与部分的关系
- 在聚合关系中,成员对象是整体对象的一部分,但是成员对象可以脱离整体对象独立存在
- **在UML中聚合关系用带空心菱形的直线表示**
![image-20211215170935831](设计模式/image-20211215170935831.png)
```java
public class Car {
private Engine engine;
public Car(Engine engine) { //构造注入
this.engine = engine;
}
public void setEngine(Engine engine) { //设值注入
this.engine = engine;
}
// ……
}
public class Engine {
// ……
}
```
- 组合关系
- **组合(Composition)关系**也表示类之间整体和部分的关系,但是在组合关系中**整体对象可以控制成员对象的生命周期**,一旦整体对象不存在,成员对象也将不存在
- 成员对象与整体对象之间具有**同生共死**的关系
- **在UML中组合关系用带实心菱形的直线表示**
![image-20211215171040026](设计模式/image-20211215171040026.png)
```java
public class Head {
private Mouth mouth;
public Head() {
mouth = new Mouth(); //实例化成员类
}
// ……
}
public class Mouth {
// ……
}
```
### 依赖关系
- **依赖(Dependency)关系**是一种使用关系,特定事物的改变有可能会影响到使用该事物的其他事物,**在需要表示一个事物使用另一个事物时使用依赖关系**
- 大多数情况下,**依赖关系体现在某个类的方法使用另一个类的对象作为参数**
- **在UML中依赖关系用带箭头的虚线表示**,由依赖的一方指向被依赖的一方。
![image-20211215171213457](设计模式/image-20211215171213457.png)
```java
public class Driver {
public void drive(Car car)
{
car.move();
}
// ……
}
public class Car {
public void move() {
......
}
// ……
}
```
- 在系统实现阶段,依赖关系通常通过三种方式来实现:
- 将一个类的对象作为另一个类中**方法的参数**
- 在一个类的方法中将另一个类的对象作为其**局部变量**
- 在一个类的方法中**调用**另一个类的**静态方法**
### 泛化关系
- **泛化(Generalization)关系**也就是继承关系,用于描述**父类与子类之间的关系**,父类又称为基类或超类,子类又称为派生类
- **在UML中泛化关系用带空心三角形的直线来表示**
- 在代码实现时使用面向对象的继承机制来实现泛化关系在Java语言中使用**extends**关键字实现
![image-20211215171412634](设计模式/image-20211215171412634.png)
```java
//父类
public class Person {
protected String name;
protected int age;
public void move() {
// ……
}
public void say() {
// ……
}
}
//子类
public class Student extends Person {
private String studentNo;
public void study() {
// ……
}
}
```
### 接口与实现关系
- 接口之间也可以有与类之间关系类似的继承关系和依赖关系,但是接口和类之间还存在一种**实现(Realization)关系**,在这种关系中,类实现了接口,类中的操作实现了接口中所声明的操作
- **在UML中类与接口之间的实现关系用带空心三角形的虚线来表示**
![image-20211215171525563](设计模式/image-20211215171525563.png)
### 接口与实现关系
![image-20211215171545713](设计模式/image-20211215171545713.png)
```java
public interface Vehicle {
public void move();
}
public class Ship implements Vehicle {
public void move() {
// ……
}
}
public class Car implements Vehicle {
public void move() {
// ……
}
}
```
## 补充知识
> 讨论
接口(Interface) VS.抽象类(Abstract Class)
> 注释
![image-20211215171704178](设计模式/image-20211215171704178.png)
> 实例
![image-20211215171713248](设计模式/image-20211215171713248.png)
> 正向工程
模型(Model) => 代码(Code)
> 逆向工程
代码(Code) => 模型(Model)
![image-20211215171811560](设计模式/image-20211215171811560.png)

@ -30,4 +30,14 @@
* [✍ 享元模式](src/university/214 "享元模式")
* [✍ 代理模式](src/university/215 "代理模式")
* [✍ 职责链模式](src/university/216 "职责链模式")
* [✍ 命令模式](src/university/217 "命令模式")
* [✍ 命令模式](src/university/217 "命令模式")
* [✍ 解释器模式](src/university/218 "解释器模式")
* [✍ 迭代器模式](src/university/219 "迭代器模式")
* [✍ 中介者模式](src/university/220 "中介者模式")
* [✍ 备忘录模式](src/university/221 "备忘录模式")
* [✍ 观察者模式](src/university/222 "观察者模式")
* [✍ 状态模式](src/university/223 "状态模式")
* [✍ 策略模式](src/university/224 "策略模式")
* [✍ 模板方法模式](src/university/225 "模板方法模式")
* [✍ 访问者模式](src/university/226 "访问者模式")
* [✍ UML类图](src/university/227 "UML类图")

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Loading…
Cancel
Save