Design-Patterns-1

Design Patterns - Behavioural

首先,什么是设计模式?设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

设计模式可以教我们如何去架构一个类、以及类与类之间如何联系起来。在90年代由GoF(四人帮)出版的《设计模式-可复用的面向对象软件元素》的数介绍了23种设计模式

这些设计模式会被分成三类:Creational(创建型)、 Structure(结构型)、Behavioural(行为型)

序号 包括
1 创建型模式 这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。 工厂模式(Factory Pattern)
抽象工厂模式(Abstract Factory Pattern)
单例模式(Singleton Pattern)
建造者模式(Builder Pattern)
原型模式(Prototype Pattern)
2 结构型模式 这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。 适配器模式(Adapter Pattern)
桥接模式(Bridge Pattern)
过滤器模式(Filter、Criteria Pattern)
组合模式(Composite Pattern)
装饰器模式(Decorator Pattern)
外观模式(Facade Pattern)
享元模式(Flyweight Pattern)
代理模式(Proxy Pattern)
3 行为型模式 这些设计模式特别关注对象之间的通信。 责任链模式(Chain of Responsibility Pattern)
命令模式(Command Pattern)
解释器模式(Interpreter Pattern)
迭代器模式(Iterator Pattern)
中介者模式(Mediator Pattern)
备忘录模式(Memento Pattern)
观察者模式(Observer Pattern)
状态模式(State Pattern)
空对象模式(Null Object Pattern)
策略模式(Strategy Pattern)
模板模式(Template Pattern)
访问者模式(Visitor Pattern)

The Essentials

首先我们要复习一些Java OOP的基本知识

Coupling

Interfaces

接下来介绍OOP的四大特性

  • 封装性:对象属性是隐藏的,对象属性修改需要通过对象方法。
  • 继承性:子类可以把父类的属性和方法都继承过来,无需重新定义。
  • 多态性:多态分为静态和动态,静态是指同一个对象可以有不同的表现形式,动态指一个父类型可以指向其子类型的实例,使子类型对同一方法作出不同的回应。
  • 抽象性:抽象指把一类东西的共同属性和行为提取出来存在一个类里面,而不关注具体行为如何实现。

Encapsulation

见博客:Encapsulation

Abstraction

Inheritance

我们知道抽象类和接口都可以用于实现抽象,但是他们应用的场景有所区别。

接口和抽象类的区别在于,接口一般只提供函数定义,并不会进行声明;抽象类允许包含某些方法的实现。

共性 都是不断抽取出来的抽象概念
区别1 抽象类体现继承关系,一个类只能单继承
接口体现实现关系,一个类可以多实现
区别2 抽象类是继承,是 “is a”关系
接口是实现,是”like a”关系
区别3 抽象类中可以定义非抽象方法,供子类直接使用
接口的方法都是抽象,接口中的成员都有固定修饰符

Polymorphism

Polymorphism

UML

UML 是 Unified Modeling Language的缩写,即一种统一建模语言

类图

  • 由用例图抽象出来的静态结构图,描述类的内部结构
  • “-”代表成员变量
  • “+”代表方法

比如,对于一个 shape 类来说,它的类图如下设计:

那么,其代码结构如下:

1
2
3
4
5
public class Shape{
private int positionX;

public void render(){}
}

类之间的关系

依赖
  • 类型:General Relationship
  • 描述:使用另一个类(uses a),某个对象的功能依赖于另外的某个对象,而被依赖的对象知识作为一种工具在使用,并不持有对它的引用
  • 表现:局部变量、方法参数、静态方法的调用
  • 表示:带箭头的虚线,由使用者指向被使用者

比如下图,展示了Shape类中使用了Document类:

代码如下:

1
2
3
4
5
public class Shape{
public void render(Document doc){

}
}
继承(extends)
  • 类型:Class Level Relationship
  • 描述:指定了子类如何继承父类的所有特征和行为
  • 表示:带三角形箭头的实线,由子类指向父类

1
2
3
public class Rectangle extends Shape{

}
组合(Composition)
  • 描述:组合是关系当中的最强版本,它直接要求包含对象对被包含对象的拥有(is a part of)以及包含对象与被包含对象⽣命期的关系。被包含的对象还可以再被别的对象关联,所以被包含对象是可以共享的,然⽽绝不存在两个包含对象对同⼀个被包含对象的共享。
  • 表现:成员变量
  • 表示:箭尾有一实心菱形的“关联”

1
2
3
public class Shape{
private Size size;
}

Memento Pattern

第一种我们要学习的是备忘录模式(Memento Pattern)。备忘录模式保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。

The Problem

在不破坏封装性的前提下,如何捕获并保存一个对象的内部状态,并在该对象之外保存这个状态。这样可以在以后将对象回复到原先保存的状态。

比如说,我有一个Editor类:

1
2
3
4
5
6
7
8
9
10
11
12
public class Editor {
private String content;

public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}

}

现在,我在main函数中多次设置了这个Editor,现在我想回退(undo)一个状态,请问该怎么实现?

1
2
3
4
5
6
7
8
public class Main {
public static void main(String[] args) {
Editor editor = new Editor();
editor.setContent("a");
editor.setContent("b");
editor.undo();
}
}

如何解决:通过一个备忘录类专门存储对象状态。

关键代码:客户不与备忘录类耦合,与备忘录管理类耦合。

应用实例: 1、后悔药。 2、打游戏时的存档。 3、Windows 里的 ctrl + z。 4、IE 中的后退。 5、数据库的事务管理。

优点: 1、给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。 2、实现了信息的封装,使得用户不需要关心状态的保存细节。

缺点:消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。

使用场景: 1、需要保存/恢复数据的相关状态场景。 2、提供一个可回滚的操作。

注意事项: 1、为了符合迪米特原则,还要增加一个管理备忘录的类。 2、为了节约内存,可使用原型模式+备忘录模式。

Solution

根据 Single Responsibility Principle ,每个类只需要负责一项任务.

因此,Editor类中,只需要保存当前状态的内容,以及两个方法:

  • createState(): 将当前Editor中的内容创建一个 EditorState对象, 并返回这个对象。然后,History类会调用push方法将该状态添加到状态列表中
  • restore(): 从一个EditorState对象中恢复得到content内容

在 EditorState类:这是一个保存状态的类,里面除了content之外还有 getter(UML图中省略了)

在History类:这个类中保存了一个状态列表,用来保存之前的状态。此外,还有push和pop方法,用来添加和删除状态

事实上,将这三个类抽象出来,是三种不同的类: MementoOriginatorCareTaker

  • Originator(Editor) 创建并在 Memento 对象中存储状态

  • Memento (EditorState) 包含了要被恢复的对象的状态。

  • Caretaker (History) 对象负责从 Memento 中恢复对象的状态。

优点

  1. 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。
  2. 实现了信息的封装,使得用户不需要关心状态的保存细节。

缺点:

  1. 消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。

Implementation

备忘录模式实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class Editor {
private String content;

public EditorState createState() { // 创建一个新的EditorState
return new EditorState(content);
}

public void restore(EditorState state) { // 从一个state中恢复数据
content = state.getContent();
}

public String getContent() { //getter
return content;
}

public void setContent(String content) {//setter
this.content = content;
}
}

public class EditorState {
private final String content;

public EditorState(String content) { //构造函数
this.content = content;
}

public String getContent() {
return content;
}
}


public class History {
private List<EditorState> states = new ArrayList<>();// State列表

public void push (EditorState state) { // 加入列表
states.add(state);
}
public EditorState pop (){ //选择最近的状态,pop
int lastIndex = states.size()-1;
EditorState lastState = states.get(lastIndex);
states.remove(lastState);

return lastState;
}
}

测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Main {
public static void main(String[] args) {
Editor editor = new Editor();
History history = new History();

editor.setContent("a");
history.push(editor.createState());

editor.setContent("b");
history.push(editor.createState());

editor.setContent("c");//此时状态为c
editor.restore(history.pop());//但是因为调用了restore,因此回退了一个版本

System.out.println(editor.getContent());//此时editor版本为b
}
}

Exercise

In the Exercises project, look at the code in the memento/Document class. This class represents a document in a word processor like MS Word or Apple Pages.

Our Document class has three attributes:

  • content
  • fontName
  • fontSize

We should allow the user to undo the changes to any of these attributes. In the future, we may add additional attributes in this class and these attributes should also be undoable.

Implement the undo feature using the memento pattern

State Pattern

在状态模式(State Pattern)中,类的行为是基于它的状态改变的。在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。

The Problem

意图:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。

主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。

何时使用:代码中包含大量与对象状态有关的条件语句。

比如说,我现在要设计一个和PS类似的软件,那么,当我点击不同的侧栏工具的时候,鼠标在画板上拖动的效果也是不一样的。比如有选择工具、画笔工具、橡皮擦工具等

因此,一个原始的想法就是用很多if else语句或者switch case,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public enum ToolType {
SELECTION,
BRUSH,
ERASER
}

public class Canvas {
private ToolType currentTool;

public void mouseDown(){//点击icon后鼠标的形状
if(currentTool == ToolType.SELECTION)
System.out.println("Selection icon");
else if(currentTool == ToolType.BRUSH)
System.out.println("Brush icon");
else if (currentTool==ToolType.ERASER)
System.out.println("Eraser icon");
}

public void mouseUp(){//在画布上拖动的效果
if(currentTool == ToolType.SELECTION)
System.out.println("Draw dashed rectangle");
else if(currentTool == ToolType.BRUSH)
System.out.println("Draw a line");
else if (currentTool==ToolType.ERASER)
System.out.println("Erase something");
}
}

那么有没有更好的办法呢?

Solution

我们可以创建一个Tool接口,里面定义两个函数 mouseDown()和mouseUp(),然后用两个具体的状态来实现这个接口,如下所示:

将其抽象出来,如下所示:

优点:

  1. 封装了转换规则。
  2. 枚举可能的状态,在枚举状态之前需要确定状态种类。
  3. 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
  4. 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
  5. 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。

缺点:

  1. 状态模式的使用必然会增加系统类和对象的个数。
  2. 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
  3. 状态模式对”开闭原则”的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。

Implementation

首先,我可以创建一个接口:

1
2
3
4
public interface Tool {
public void mouseDown();
public void mouseUp();
}

然后,我们创建两个状态类,用来实现这个接口,每个状态类中mouseDown和mouseUp的行为都是不同的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class BrushTool implements Tool{
@Override
public void mouseDown() {
System.out.println("Brush icon");
}

@Override
public void mouseUp() {
System.out.println("Draw a line");
}
}

public class SelectionTool implements Tool{

@Override
public void mouseDown() {
System.out.println("Selection icon");
}

@Override
public void mouseUp() {
System.out.println("Draw a Dashed Rectangle");
}
}

于是,在Canvas中就不用写繁琐的switch case或者if-else case了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Canvas {
private Tool currentTool;

// 调用当前接口的mouseDown或者mouseUp方法就可以实现不同结果
public void mouseDown(){
currentTool.mouseDown();
}

public void mouseUp(){
currentTool.mouseUp();
}

public Tool getCurrentTool() {
return currentTool;
}

public void setCurrentTool(Tool currentTool) {
this.currentTool = currentTool;
}
}

现在,只要给Canvas设置不同的状态,就可以展现不同的行为了:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Main {
public static void main(String[] args) {
Canvas canvas = new Canvas();

canvas.setCurrentTool(new SelectionTool());
canvas.mouseDown(); // Selection icon
canvas.mouseUp(); // Draw a Dashed Rectangle

canvas.setCurrentTool(new BrushTool());
canvas.mouseDown(); // Brush icon
canvas.mouseUp(); // Draw a line
}
}

Abusing the State Pattern

之前说了,State Pattern 必然会增加系统类和对象的个数。 因此,如果不正确使用它的话,很可能造成滥用。比如说下面这个情况:

比如说我想设计一个时钟类,这个类很不需要很多状态,只有runningstop,也只需要一个方法:click。因此,对这种不会有“发展”的、简单的类,用if-else case就可以解决了,不需要再使用state pattern

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class StopWatch {
private boolean isRunning;
public void click(){
if(isRunning){
isRunning = false;
System.out.println("Stopped");
}else{
isRunning = true;
System.out.println("Running");
}
}
}

public class Main {
public static void main(String[] args) {
StopWatch stop_watch = new StopWatch();
stop_watch.click(); // Running
stop_watch.click(); // Stopped
stop_watch.click(); // Running
}
}

但是,如果对State Pattern了解不够的话,在这种情况下很可能会滥用,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class StopWatch {
private State currentState = new StopState(this); //初始化是停止状态

public State getCurrentState() {
return currentState;
}

public void setCurrentState(State currentState) {
this.currentState = currentState;
}

public void click(){ //调用click,就会开始/停止
currentState.click();
}
}

public interface State {//创建接口函数
void click();
}

public class RunningState implements State{ //分别实现运行时状态和停止状态
private StopWatch stopWatch;
public RunningState(StopWatch stopWatch) {//构造函数
this.stopWatch = stopWatch;
}
@Override
public void click(){//每次点击时,先设置相反的状态,然后打印
stopWatch.setCurrentState(new StopState(stopWatch));
System.out.println("Stopped");
}
}

public class StopState implements State{
private StopWatch stopWatch;
public StopState(StopWatch stopWatch) {
this.stopWatch = stopWatch;
}

@Override
public void click() {
stopWatch.setCurrentState(new RunningState(stopWatch));
System.out.println("Running");
}
}

这样虽然结果是一样的,但就会显得非常繁琐,多出来很多类,浪费很多空间。

Exercise

In the Exercises project, look at the code in the state/ DirectionService class. This is the class that powers our mapping app. It provides two methods for calculating the estimated time of arrival (ETA) and the direction between two points.

Identify the problems in this implementation. Then, refactor the code to use the state pattern. What are the benefits of the new implementation?

Iterator Pattern

The Problem

Solution

Implementation

Benefits

Exercise

A Quick Note

Strategy Pattern

The Problem

Solution

Implementation

Exercise

Template Method Pattern

The Problem

Solution

Implementation

Exercise

Command Pattern

The Problem

Solution

Implementation

Composite Commands

Undoable Commands

Implementing Undo Mechanism

Exercise

Observer Pattern

The Problem

Solution

Implementation

Communication Styles

The Push Style

The Pull Style

Exercise

Mediator Pattern

The Problem

Solution

Implementation

Using the Observer Pattern

Implementation Using the Observer Pattern

Exercise

Chain of Responsibility

The Problem

Solution

Implementation

Exercise

Visitor Pattern

The Problem

Solution

Implementation

Exercise

-------------本文结束,感谢您的阅读-------------