模板方法模式

概述

定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式。

其实本质上就是类的继承,首先父类将所要实现的功能放到模板方法里面 TemplateMethod() 外界只会调用这个方法来完成某一个任务。而完成模板方法可以变动的细节,再抽取成抽象方法,让子类用不同的方式实现这些方法。也就是说,父类的 TemplateMethod() 会通过调用子类实现的抽象方法来最终完成整个任务,不同的子类意味着实现方式的不同。

主要是对于同一个目标的不同实现方法分别放在不同的子类中,根据实际分析出最好的方法,然后用对应的子类完成。

下面就是模板方法的 UML 图,父类的 TemplateMethod() 会调用自己定义的抽象方法完成任务,而抽象方法的具体实现交给不同的子类。

模板方法模式的主要思想:父类定义骨架,子类实现某些细节。这里的骨架就是获取最短路径的步骤,建表-迭代-输出。而将拥有不同实现方法的细节——迭代,延迟到子类去实现(Floyd 算法、Dijkstra 算法)

代码实现

我们用求最短路来表示一下模板方法模式,这里父类给出了要解决最短路问题的主要框架:建表-迭代-输出,然后迭代细节交给不同的子类用不同的算法实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
abstract class Shortest {
private Edge[] edges;

Shortest(Edge[] edges) {
this.edges=edges;
}

public Edge[] GetShortest() { // 模板方法,调用此方法完成某一项功能
BuildEdge(this.edges); // 固定的算法骨架-建表
ToShort(this.edges); // 调用子类实现的细节-迭代
return Output(this.edges); // 固定的算法骨架-输出
}

private void BuildEdge(Edge[] edges) {
System.out.println("正在建立邻接表")
}

private Edge[] Output(Edge[] edges) {
System.out.println("正在整合输出")
}

abstract protected Edge[] ToShort(Edge[] edges); // 部分等待实现的细节,不同子类用不同方式实现。
}

用 Floyd 和 Dijkstra 算法实现的最短路子类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Floyd extends Shortest {
Floyd(Edge[] edges) {
super(edges);
}

@Override
protected Edge[] ToShort(Edge[] edges) {
System.out.println("使用 Floyd 算法迭代出来了最短路");
return edges;
}
}

class Dijkstra extends Shortest {
Dijkstra(Edge[] edges) {
super(edges);
}

@Override
protected Edge[] ToShort(Edge[] edges) {
System.out.println("使用 Dijkstra 算法迭代出来了最短路");
return edges;
}
}

使用,实例化对应某一方法的子类,调用其模板方法即可。

1
2
3
4
5
6
7
8
public class GetShort {
public static void main(String[] args) {
Edge[] edges=new Edge[100];
Shortest toshort=new Floyd(edges);
edges=toshort.GetShortest();
}
}

策略模式

概述

上面的模板方法模式的主要思想是:父类定义骨架,子类实现某些细节。这说明父类是针对与某一个问题的总体实现,框架是固定的,只不过是部分的实现细节不同(不同细节对不同资源的消耗是不同的),但最后得到的结果都是相同的。

而策略模式针对的不是一种解决方式的不同细节实现,而是多个整体解决方式。这些整体解决方式可以被替换,因为都是实现了同一个接口,所以替换不会影响到客户端的调用。一般来说是不同的策略得到的结果是不一样的。

比如 Arrays.sort(T[] a, Comparator<? super T> c) 这个排序方法,其中的 Comparator 接口就是排序策略的统一接口,不同的 compare() 实现的是不同的比较策略,对于不同的比较策略,最后排序的结果也会不同。

综上:策略模式的核心思想是在一个计算方法中把容易变化的算法抽出来接口作为 “策略” 参数传进去,从而使得新增策略不必修改原有逻辑。

策略模式的实现结构,所谓的环境类就是利用不同策略的类(比如接收 Comparator 接口的排序类):

代码实现

我们实现一个根据 VIP 等级决定不同打折策略的购物系统。

策略接口和实现的不同策略类:

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
interface Discount {
int getPrice(int price);
}

class VIP implements Discount {
@Override
public int getPrice(int price) {
System.out.println("VIP会员,统一打八折");
double result=price*0.8;
return (int)result;
}
}

class Coupon implements Discount {
private int cutdown;

Coupon(int price) {
this.cutdown=price;
}

@Override
public int getPrice(int price) {
System.out.println("拥有优惠卷,便宜"+cutdown+"元");
return price-cutdown;
}
}

class Common implements Discount {
@Override
public int getPrice(int price) {
System.out.println("普通会员,不优惠");
return price;
}
}

环境类:超市购物系统,接收打折策略并完成结算过程。

1
2
3
4
5
6
7
8
9
10
11
class SuperMarket {
private Discount discount;

SuperMarket(Discount discount) {
this.discount=discount;
}

public int checkOut(int price) {
return discount.getPrice(price);
}
}

客户来超市实际的购物过程,不同的优惠策略对应不同的价格。

1
2
3
4
5
6
7
8
9
10
11
12
public class Shopping {
public static void main(String[] args) {
SuperMarket sm1=new SuperMarket(new VIP());
System.out.println(sm1.checkOut(100));

SuperMarket sm2=new SuperMarket(new Coupon(20));
System.out.println(sm2.checkOut(100));

SuperMarket sm3=new SuperMarket(new Common());
System.out.println(sm3.checkOut(100));
}
}

命令模式

概述

对于一般我们控制一样东西,都是客户端直接持有需要控制的对象,然后和其进行交互。

但是当控制越来越复杂,就需要引入中间件来降低系统的耦合度了,这个中间件就是命令接口,实现其接口的不同子类拥有不同的命令,我们只需要持有命令接口,就可以用简单的代码完成复杂命令的发出。

使用小爱同学(中间件)来控制智能家居就是命令模式,小爱可以帮你传达控制智能家居的命令(红外 / wifi),还可以封装一些基本控制的组合为一个新的整体命令(小爱捷径),还可以在小爱执行列表里看到命令执行情况(中间件添加的日志系统)。

代码实现

看看上面的 UML 图就行了,这个设计模式逻辑过于简单,就不写代码了。

责任链模式

责任链模式:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

比如异常的处理机制,从出问题的地方抛出来,然后经过每一个 catch() 进行判断是否可以捕捉,如果可以捕捉,则捕捉异常并处理,如果不能,则传递给下一个 catch(),直到有人能处理异常或者直接抛到程序外面,终止程序的运行。

责任链的实现也很简单,先建立一个抽象类规定处理者的公有属性和方法,(属性:下一个处理者)(方法:处理问题方法)。

构建责任链有两种方式,一种是直接创造好所有责任链所拥有的实例,然后用内聚的方法在调用处构造好责任链,然后传入待处理对象处理。另外一种是在写具体处理者的时候,就定义好责任链的下一个处理者,下一个处理者的实例化交给当前处理者决定(如果自己能处理,就自己处理,如果不能,实例化下一个处理者,并交付给它。)

下图是第一种责任链的构造方式:

然后一步一步的去处理:

代码实现

我们来写写第二种责任链的代码实现,实现一个责任链给人穿衣服。

定义被处理者 Person ,和处理者 Wearer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Person {
private String name;

Person(String name) {
this.name=name;
}

public String getName() {
return this.name;
}
}

class Wearer {
protected Person person;

Wearer(Person person) {
this.person=person;
}

public String wear() {
Wearer next=new WearPants(person);
return next.wear();
}
}

定义一下责任链里面的每一个单独处理者:

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
48
49
50
class WearPants extends Wearer {
WearPants(Person person) {
super(person);
}

@Override
public String wear() {
System.out.println("正在给"+person.getName()+"穿内裤");
Wearer next=new WearJacket(person);
return next.wear();
}
}

class WearJacket extends Wearer {
WearJacket(Person person) {
super(person);
}

@Override
public String wear() {
System.out.println("正在给"+person.getName()+"穿上衣");
Wearer next=new WearTrousers(person);
return next.wear();
}
}

class WearTrousers extends Wearer {
WearTrousers(Person person) {
super(person);
}

@Override
public String wear() {
System.out.println("正在给"+person.getName()+"穿裤子");
Wearer next=new WearShoes(person);
return next.wear();
}
}

class WearShoes extends Wearer {
WearShoes(Person person) {
super(person);
}

@Override
public String wear() {
System.out.println("正在给"+person.getName()+"穿鞋子");
return "完成穿衣,准备出门!";
}
}

然后让 Xorex 小朋友被责任链一个接着一个穿衣服:

1
2
3
4
5
6
7
public class Chain {
public static void main(String[] args) {
Person Xorex=new Person("Xorex");
Wearer wearer=new Wearer(Xorex);
System.out.println(wearer.wear());
}
}

获取最终的输出结果:

1
2
3
4
5
正在给Xorex穿内裤
正在给Xorex穿上衣
正在给Xorex穿裤子
正在给Xorex穿鞋子
完成穿衣,准备出门!

状态模式

概述

状态关系的定义:对有状态的对象,把复杂的不同状态代码提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。

我们会遇到在不同情况下,一个类需要有不同的应对处理状态。那么这个时候最基础的解决方法就是在类里面用 if 或者 switch 语句来判断当前所处于的状态,然后里面写对应状态的处理代码。但是这样做的缺陷会很大,代码会很复杂,很难维护,所以我们就更具定义,法复杂的状态代码提取到不同的状态对象中。定义一个状态接口,然后实现不同状态的类的代码。一旦系统处于某个状态,那么我们就持有对应这个状态的实例来处理事物,从而进行代码解耦。

对于持有应对外界状态的实例变化,有两种方式,一种是让状态实例自己判断并改变:实现原理就是让状态实例处理的时候,传入环境实例,等状态实例处理完成之后再自行判断并修改环境实例中的状态标志,从而改变状态。比如下面的 UML 类图就是这种模式:

另外一种则是交给外界控制并持有状态类实例的环境类来判断改变。其实现原理更加简单了,环境类执行状态实例的处理方法之后,根据处理方法返回的信息,决定环境类持有的下一个状态实例。没有 UML 图,但是有代码:

代码实现

我个人是觉得第二种更加好一点,这样可以单方面解耦状态类和环境类之间的联系,更加纯粹一点。

现在有一个人,可以进行交互,状态有开心和生气两种,改变状态触发条件时 Play() 和 Wrok() 。

状态接口以及两种状态类。

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
interface States {
String Talk();
String Play();
String Work();
}

class HappyState implements States {
@Override
public String Talk() {
return "你好啊,一起去玩吧!";
}

@Override
public String Play() {
return "好耶,去玩!";
}

@Override
public String Work() {
return "不想工作啊……";
}
}

class AngryState implements States {
@Override
public String Talk() {
return "滚,别和我说话。";
}

@Override
public String Work() {
return "让我工作,想要找死吗?";
}

@Override
public String Play() {
return "真是受够了,终于能玩了";
}
}

环境类,用于控制状态的转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Context {
private States now;

Context(States now) {
this.now=now;
}

public String Talk() {
return now.Talk();
}

public String Work() {
String ans=now.Work();
now=new AngryState();
return ans;
}

public String Play() {
String ans=now.Play();
now=new HappyState();
return ans;
}
}

模拟和这个人交互:

1
2
3
4
5
6
7
8
9
10
11
public class Human {
public static void main(String[] args) {
Context Xorex=new Context(new AngryState());
System.out.println(Xorex.Talk());
System.out.println(Xorex.Work());
System.out.println(Xorex.Play());
System.out.println(Xorex.Play());
System.out.println(Xorex.Talk());
System.out.println(Xorex.Work());
}
}

最后表现,可以看到中间 Play() 之后状态由 angry 转化为了 happy。

1
2
3
4
5
6
滚,别和我说话。
让我工作,想要找死吗?
真是受够了,终于能玩了
好耶,去玩!
你好啊,一起去玩吧!
不想工作啊……

观察者模式

概述

观察者模式定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。

观察者模式是一种通知机制,让发送通知的一方(被观察方)和接收通知的一方(观察者)能彼此分离,互不影响。而具体的实现方式就是建立一个中介,里面保存着所有的需要被通知的观察者,一旦被观察者通过自己被修改了,那么它会逐一通知所有保存着的观察者。

核心就是将观察者和被观察者解耦,建立一个中间类,中间类负责代理观察者监视被观察者,并根据被观察者的改变发送给观察者信息,包括管理订阅消息的观察者。

代码实现

观察者需要实现的接口,以及具体的观察者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Observable {
public void getInfo(String info);
}

class Yukino implements Observable {
@Override
public void getInfo(String info) {
System.out.println("[Yukino received] "+info);
}
}

class Asuna implements Observable {
@Override
public void getInfo(String info) {
System.out.println("[Yukino received] "+info);
}
}

负责管理被观察者信息改变,并传递给观察者消息的信息管理者,以及被观察者的实现。

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
class MessageManger {
Xorex xorex=new Xorex();
List<Observable> obser=new ArrayList<>();

public void setOberver(Observable observer) {
obser.add(observer);
}

public void change(int num) {
xorex.setHeart(num);
notification(num);
inform(num);
}

private void inform(int num) {
obser.forEach(o->o.getInfo("Xorex's heart number is changed to "+num));
}

private void notification(int num) {
System.out.println();
System.out.println("[Message Manger] Information changed to "+num);
}
}
class Xorex { //被观察者
private int heart;

public void setHeart(int num) {
this.heart=num;
}
}

修改被观察者的信息,发现观察者已经接收到通知:

1
2
3
4
5
6
7
8
9
10
public class Observers {
public static void main(String[] args) {
MessageManger mm=new MessageManger();
mm.setOberver(new Asuna());
mm.setOberver(new Yukino());
mm.change(100);
mm.change(10);
mm.change(1);
}
}

接收到的通知:

1
2
3
4
5
6
7
8
9
10
11
[Message Manger] Information changed to 100
[Yukino received] Xorex's heart number is changed to 100
[Yukino received] Xorex's heart number is changed to 100

[Message Manger] Information changed to 10
[Yukino received] Xorex's heart number is changed to 10
[Yukino received] Xorex's heart number is changed to 10

[Message Manger] Information changed to 1
[Yukino received] Xorex's heart number is changed to 1
[Yukino received] Xorex's heart number is changed to 1

中介者模式

概述

中介者模式定义:定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是最小认知原则的典型应用。

其实就是一堆应用应为相互依赖过于混乱,导致了改一个其他都可能会出问题,那么就搞一个中介,让所有的人都通过中介和其他人交互。大概就是从网状网络的去中心化更改为了星形网络的中心化,这样对于一切更改,我们只需要针对中介这一个类就可以了。

举个例子,对于同事们之间的信息交互,我们可以搞一个中介类来负责数据的发送和接收,UML 类图大概是这样:(其实抽象中介者一般来说都可以省略)

异同点

代理模式:一般来说是代理类代替被代理类作为目标类来使用,用来完成被代理类无法完成或者不方便完成的任务。

中介者模式:作为一群类的处理交互人,这些类的相互交互全在中介类一个人身上。

外观模式:目标类(被其它类交互的类)整理出来一个统一的接口,来规范化简洁化和自己交互的过程。

代码实现

因为中介者模式比较简单,所以不写了。

迭代器模式

概述

迭代器模式前面学习集合的时候肯定已经非常熟悉了,就是对于一种聚合数据的类(无论是集合还是映射),都有遍历里面所有元素的需求,而这些需求都是聚合数据类内部实现的。遍历的方法是获取迭代器 Iterator,这个迭代器往往通过聚合数据类的 iterator() 获取。

而 Iteratior 接口就是这种标准的迭代接口,在 Collection 接口种就规定了一个方法用来返回用于此集合遍历的迭代器 Iterator iterator();。而 Iteratior 接口规定了实现迭代统一而必不可少的方法:

1
2
3
4
public interface Iterator<E> {
boolean hasNext(); // 返回是否还有下一个元素
E next(); // 返回下一个元素并移动指针指向
}

没错,只有这两个方法就够了,判断还有下一个元素没有了,如果有就取出来,如果没有那就迭代完成!我们不需要关心集合是啥,只要 hasNext 和 next ,闭着眼都能遍历它。

而我们需要实现对聚合数据类进行迭代的时候,只需要对此类标注实现接口 Iterable 表示实现了方法 iterator() 来返回此数据聚合类的迭代器。然后重点就是让 iterator() 方法返回一个实现了 Iterator 接口的迭代器的实例。

主要是用内部类实现 Iterator 并重写 hasNext() 和 next() 方法,对应实现迭代器的 UML 图如下:

代码实现

实现了 Iterable 接口的聚合数据类,表示可以被迭代,拥有 iterator() 方法。

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
class MyCollection<T> implements Iterable<T> {
private int index=0;
private T[] array;

MyCollection() {
this.array=(T[]) new Object[1000];
}

public void add(T t) {
array[index++]=t;
}

@Override //返回本实例的迭代器实例
public Iterator<T> iterator() {
return new Iter();
}

private class Iter implements Iterator<T> { //通过内部类来实现迭代器的代码
private int curser=0;

@Override
public boolean hasNext() {
return curser<index;
}

@Override
public T next() {
return array[curser++];
}
}
}

代码验证迭代器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ForEach {
public static void main(String[] args) {
MyCollection<String> mycol=new MyCollection<>();
mycol.add("Xorex");
mycol.add("Tempest");
mycol.add("Yukino");
mycol.add("Asuna");
mycol.add("Katou");
mycol.add("Megumi");
for(String i:mycol) {
System.out.println(i);
}
}
}

最后迭代成功:

1
2
3
4
5
6
Xorex
Tempest
Yukino
Asuna
Katou
Megumi

访问者模式

概述

定义:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离。

说人话版本,将对于数据结构里面数据的遍历和数据的操作分离。举个例子:遍历一个树结构然后将每一个节点的值 +1,这里遍历一个树结构是数据的遍历,将每一个节点的值 +1 是对数据的操作。这里我们可以将对数据的操作抽取出来,作为一个访问者,不同访问者的实现类实现了对数据的不同操作。这样在遍历数据的时候,我们就可以传入不同的访问者实例来决定对数据进行不同操作。

首先需要设置数据元素的接口,然后实现不同的元素类。

其次需要设置访问者接口,然后实现不同操作的访问者,访问者需要接收上面的符合元素接口实例,然后操作传入的实例。

最后就是实现数据结构类,负责对数据的增删查改和遍历,可以传入不同的访问者来实现对数据遍历的时候不同的操作。

代码实现

我们写一个通过访问者修改数据的字符串内容为全部大写的需求。

首先代码数据的接口及其具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface Element {
String getName();
void setName(String name);
}

class Person implements Element {
private String Name;

Person(String name) {
this.Name=name;
}

@Override
public String getName() {
return this.Name;
}

@Override
public void setName(String name) {
this.Name=name;
}
}

然后代码访问者的接口及代码:

1
2
3
4
5
6
7
8
9
10
11
interface Visitor {
void Handle(Element element);
}

class Upper implements Visitor {
@Override
public void Handle(Element element) {
System.out.println("[Changed element:] "+element.getName()+" to "+element.getName().toUpperCase());
element.setName(element.getName().toUpperCase());
}
}

实现数据聚合的控制类和主函数去调用对应的方法。

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
public class Main {
public static void main(String[] args) {
ElementController ec=new ElementController();
ec.add(new Person("Xorex"));
ec.add(new Person("Yukino"));
ec.add(new Person("Megumi"));
ec.add(new Person("Asuna"));
ec.add(new Person("Origami"));
ec.forEach(new Upper());
}
}

class ElementController {
private List<Element> list=new ArrayList<>();

void forEach(Visitor visitor) {
for(Element i:list) {
visitor.Handle(i);
}
}

void add(Element element) {
list.add(element);
}
}

通过访问者成功修改:

1
2
3
4
5
[Changed element:] Xorex to XOREX
[Changed element:] Yukino to YUKINO
[Changed element:] Megumi to MEGUMI
[Changed element:] Asuna to ASUNA
[Changed element:] Origami to ORIGAMI

备忘录模式

概述

就是对某一个类的某个状态进行抽取成某个格式,并保存到备忘录类中,方便实现历史状态回溯。

首先需要一个存档类,也就是某个类去存储状态的时候,里面属性的存储格式。这里可以是被存储类的部分属性组成的类,也可以直接 clone() 被存储类(存档格式就是被存储类本身)

然后就是一个存档容器类,里面用集合存储存档实例,内部由存储,提取,删除存档等功能。

如果存档类是要被存储类本身的话,获取存档只需要调用 clone() 就可以返回一个复制的存档用来存储。如果不是的话,还需要一个存档类生成器,用于传入被存储类,然后返回存档类。

实现的 UML 图大概如下,其中发起人 Originator 就是需要被存储的类,它可以自己创建并返回自己当前状态的存档类 Memonto ,也可以接收 Memonto 实例来恢复对应存档当时的状态。获取自己当前的状态类 Memonto 之后,交为管理者 Caretaker 存储管理。

客户端主要和管理者和被存储类交互,从被存储类获取它当前状态的存档并保存到管理者这里,或者从管理者这里获取过去状态存档然后传给被存储类进行状态恢复。

代码实现

觉得这个模式比较好理解,所以就不写代码了 QAQ

解释者模式

概述

解释器模式的定义:给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文。

举个例子就是各种编程语言,SQL 语言,正则表达式,都是通过定义此语言的文法表示,然后用解析器解析成所需要执行的步骤。这里对应的就是各语言的 编译器/解释器 。

所以因为过于复杂,一般来说除非想写编译器,否则根本用不上。

代码实现

请翻阅各种语言的编译器/解释器源代码 QAQ