0%

设计模式之创建型模式

行为型模式主要涉及算法和对象间的职责分配。通过使用对象组合,行为型模式可以描述一组对象应该如何协作来完成一个整体任务。

观察者模式

观察者模式可以当一个对象出现改变的时候,会执行对应的方法。一般情况下,被依赖的对象称为被观察者,依赖的对象称为观察者。在kotlin中可以很方便的使用可观察属性Observable来使用观察者模式,具体代码如下:

1
2
3
4
5
6
7
8
9
//kotlin 1.4.0
import kotlin.properties.Delegates
var observer :String by Delegates.observable(""){ _, old, new ->
println("$old -> $new")
}
fun main(){
observer="a"
observer="b"
}

观察者模式可以将发送通知的一方与接收通知的一方进行分离,使其互不影响。

模板模式

模板模式在一个方法中定义一个算法骨架,将某些步骤推迟到子类中实现。模板方法可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。

模板模式实现

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
//java 11
abstract class AbstractClass {
public static void main(String[] args) {
AbstractClass demo = new ConcreteClass1();
demo.templateMethod();
}
public final void templateMethod() {
//...
method1();
//...
method2();
//...
}
abstract void method1();
abstract void method2();
}

class ConcreteClass1 extends AbstractClass {
@Override protected void method1() {
//...
}

@Override protected void method2() {
//...
}
}

为了避免子类重写,templateMethod()函数需要定义为final。method1()和method2()定义为abstract,强迫子类去实现。templateMethod就是算法的骨架即模板方法。

模板模式使用场景

模板模式的的作用在于方便复用和扩展。在例子中可以将算法中不变的流程抽象到父类的模板方法templateMethod()中,将可变的部分留给子类来实现,这方便了代码的复用。同时允许子类在实现的方法中处理自己的逻辑,这增加了框架的扩展性。

策略模式

策略模式的作用在于可以用于避免冗长的if-else或switch分支判断,同时可以为框架提供扩展点。

策略模式实现

策略模式的定义比较简单,所有的策略类都实现相同的接口,通过基于接口编程,可以灵活的替换不同的策略。在使用策略的时候,一般需要通过类型来判断创建哪个模式来使用。可以把根据 type 创建策略的逻辑抽离出来,放到工厂类中。示例代码如下所示:

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
//java 11
//定义策略
interface Strategy {
void algorithmInterface();
}

class ConcreteStrategyA implements Strategy {
@Override public void algorithmInterface() {/*具体的算法...*/}
}

class ConcreteStrategyB implements Strategy {
@Override public void algorithmInterface() {/*具体的算法...*/}
}
//创建策略
public class StrategyFactory {
public static Strategy getStrategy(String type) {
if (type != null) {
if (type.equals("A")) {
return new ConcreteStrategyA();
} else if (type.equals("B")) {
return new ConcreteStrategyB();
}
}
throw new IllegalArgumentException("错误的类型"+type);
}
public static void main(String[]args){
//使用
StrategyFactory.getStrategy("A").algorithmInterface();
}
}

可以看出策略模式应该由三个部分组成(定义,创建,使用)。策略类的定义比较简单,包含一个策略接口以及实现这一接口的子类。策略模式的创建则由一个工厂类来进行完成,封装了策略创建的细节。在策略模式的使用时,动态的传入类型选择需要使用哪个策略。

策略模式使用场景

责任链模式

责任链模式将请求的发送和接收解藕,让多个接收对象都有机会处理这个请求。将这些对象串成一条链,并沿着这条链传递这个请求,直到链上出现能够处理的对象为止。

责任链模式实现

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
//java 11
interface IHandler {
boolean handle();
}

class HandlerA implements IHandler {
@Override public boolean handle() {
System.out.println("handleA");
return Math.random() < 0.5;
}
}

class HandlerB implements IHandler {
@Override public boolean handle() {
System.out.println("handleB");
return Math.random() < 0.5;
}
}

class HandlerChain {
private final List<IHandler> handlers = new ArrayList<>();

public void addHandler(IHandler handler) {
this.handlers.add(handler);
}

public void handle() {
for (IHandler handler : handlers) {
boolean handled = handler.handle();
if (handled) {
break;
}
}
}

public static void main(String[] args) {
HandlerChain chain = new HandlerChain();
chain.addHandler(new HandlerA());
chain.addHandler(new HandlerB());
chain.handle();
}
}

在HandlerChain类中使用一个数组来保存所有的处理器,然后在handle()方法中依次调用handle()函数,同时判断是否需要将责任链请求继续传递下去。

责任链模式使用场景

通常使用责任链模式来做一些工作例如日志记录,鉴权等等。Java中Servlet规范定义的filter以及Spring的拦截器Interceptor都是使用责任链模式来进行实现的。

状态模式

状态模式经常用在带有状态的对象中,用于实现状态机。
//TODO

迭代器模式

提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。一个完整的迭代器模式需要容器以及容器迭代器两部分的内容。在很多语言中其实已经提供了遍历容器的迭代器类,在平时使用的情况下直接使用即可。在java中使用迭代器的代码如下:

1
2
3
4
5
6
7
8
9
10
11
// java 11
List<Integer> list = List.of(1, 2, 3, 4);
// 使用迭代器遍历
for (var it = list.iterator(); it.hasNext(); ) {
System.out.println(it.next());
}
// 简写形式
for (Integer n : list) {
System.out.println(n);
}

实现迭代器模式

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
interface Iterator<E> {
boolean hasNext();
void next();
E current();
}

class ArrayIterator<E> implements Iterator<E> {
private int index = 0;
private final E[] array;

@SafeVarargs ArrayIterator(E... array) {
this.array = array;
}

@Override public boolean hasNext() {
return index != array.length;
}

@Override public void next() {
index++;
}

@Override public E current() {
return array[index];
}
}

class Main {
public static void main(String[] args) {
ArrayIterator<Integer> iterator = new ArrayIterator<>(1, 2, 3);
while (iterator.hasNext()) {
System.out.println(iterator.current());
iterator.next();
}
}
}

为了实现迭代器模式,需要先定义一个Iterator接口,在其中定义迭代器需要实现的方法hasNext,next以及current。ArrayIterator的实现非常简单,将需要遍历的容器对象传递给迭代器类,在通常的实现中,可以在容器类之中定义一个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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import java.io.File;

public class Main {
public static void main(String[] args) {
FileStructure fs = new FileStructure(new File("."));
fs.handle(new PrintFileNameVisitor());
}
}

interface FileVisitor {
void handleDirectory(File dir);
void handleFile(File file);
}

class PrintFileNameVisitor implements FileVisitor {
@Override public void handleDirectory(File dir) {
System.out.println("目录名: " + dir);
}

@Override public void handleFile(File file) {
System.out.println("文件名 " + file);
}
}

class FileStructure {
private File path;

public FileStructure(File path) {
this.path = path;
}

public void handle(FileVisitor visitor) {
handle(this.path, visitor);
}

private void handle(File file, FileVisitor visitor) {
if (file.isDirectory()) {
visitor.handleDirectory(file);
for (File sub : file.listFiles()) {
handle(sub, visitor);
}
} else if (file.isFile()) {
visitor.handleFile(file);
}
}
}

在这段代码中,先定义了一个访问者的接口FileVisitor,然后实现handleDirectory和handleFile这两个方法分别用于处理不同的文件类型的情况。最后给FileStructure这个类添加了handle方法,传入一个访问者,这样子就把访问者的行为抽象了出来。

访问者模式优势

访问者模式可以用于解藕操作和对象本身,保持类的职责单一,满足开闭原则。

备忘录模式

备忘录模式可以在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。

备忘录模式实现

例如在一个文本编辑器中需要实现撤销的操作,可以用如下代码进行实现:

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 Text {
private final StringBuilder text = new StringBuilder();

public void add(String s) {
text.append(s);
}

public void delete() {
if (text.length() > 0) {
text.deleteCharAt(text.length() - 1);
}
}

public Snapshot createSnapshot() {
return new Snapshot(text.toString());
}

public void loadFromSnapshot(Snapshot snapshot) {
this.text.replace(0, text.length(), snapshot.getText());
}
}

class Snapshot {
private String text;

public Snapshot(String text) {
this.text = text;
}

public String getText() {
return text;
}
}

class SnapshotHolder {
//最大记录数
private static final int MAX_SIZE = 20;
private Deque<Snapshot> snapshots = new ArrayDeque<>(MAX_SIZE);

public Snapshot pop() {
return snapshots.removeLast();
}

public void push(Snapshot snapshot) {
if (snapshots.size() > MAX_SIZE) {
snapshots.removeFirst();
}
snapshots.addLast(snapshot);
}
}

备忘录模式使用场景

备忘录模式主要用来防止丢失,实现撤销以及恢复功能。

命令模式

TODO

解释器模式

TODO

中介模式

TODO