【CODE.0x02】软件工程:设计模式浅析

本文最后更新于:2022年1月5日 凌晨

其实👴不会写代码(

0x00.一切开始之前

先来一段面向 Wikipedia 的废话(

在软件工程中,设计模式(design pattern)是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案。这个术语是由埃里希·伽玛(Erich Gamma)等人在1990年代从建筑设计领域引入到计算机科学的。

设计模式并不直接用来完成代码的编写,而是描述在各种不同情况下,要怎么解决问题的一种方案。面向对象设计模式通常以类别或对象来描述其中的关系和相互作用,但不涉及用来完成应用程序的特定类别或对象。设计模式能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免会引起麻烦的紧耦合,以增强软件设计面对并适应变化的能力。

并非所有的软件模式都是设计模式,设计模式特指软件“设计”层次上的问题。还有其他非设计模式的模式,如架构模式。同时,算法不能算是一种设计模式,因为算法主要是用来解决计算上的问题,而非设计上的问题。

设计模式 (计算机) - 维基百科,自由的百科全书 (wikipedia.org)

笔者本身也不是软工专业的,所以下文中笔者的拙见可能有误,还望读者不吝指正。

在笔者看来,通俗地来说,设计模式(design pattern)就是软件的架构设计问题——我们在如何去设计、组织一个软件的架构

当我们谈论到设计模式这一词语时,我们通常是基于面向对象程序设计(Object Oriented Programming)这一基础出发,而非传统的_面向过程的程序设计_,这意味着设计模式是软件设计的抽象层面——即我们并不关注细节的实现,而是关注于更高一层的组织

方便起见,本篇笔者选用“最面向对象的语言”—— Java 语言来讲解不同的设计模式

设计模式的类别

在《设计模式:可复用面向对象软件的基础》(Design Patterns: Elements of Reusable Object-Oriented Software)一书中,GoF 总结了23种设计模式,分为三大类:

创建型模式(5种)

  • 工厂方法模式
  • 抽象工厂模式
  • 单例模式
  • 建造者模式
  • 原型模式

结构型模式(7种)

  • 适配器模式
  • 装饰器模式
  • 代理模式
  • 外观模式
  • 桥接模式
  • 组合模式
  • 享元模式

行为型模式(11种)

  • 策略模式
  • 模板方法模式
  • 观察者模式
  • 迭代子模式
  • 责任链模式
  • 命令模式
  • 备忘录模式
  • 状态模式
  • 访问者模式
  • 中介者模式
  • 解释器模式

随着现代软件设计理论的发展,更多的设计模式也被开发了出来,除了书上所述的上述三大类都分别新增有设计模式之外,针对于多线程与并发软件设计,还新增了一个并发型模式,以及由 Sun Java Center 鉴定的 J2EE模式等等…

本篇主要还是讲书上的23种设计模式,额外的设计模式如果有时间可能也会讲讲

其实笔者本人也还没买钱这本书…(体谅一哈,👴是学生,建议v👴200

0x01.创建型模式

在软件工程中,创建型模式是用以处理对象创建的设计模式,该类别模式根据实际情况使用合适的方式创建对象,因为基本的对象创建方式可能会导致设计上的问题,或增加设计的复杂度

创建型模式的关注点是如何创建对象,其核心思想是要把对象的创建和使用相分离

例如,当我们在C++中使用 string 类的各种方法时,我们并不需要关注其底层(allocator、扩容…)是如何实现的

一、工厂方法模式(Factory method pattern)

工厂方法模式通常又简称工厂模式,该设计模式引入了一种名为“工厂”的概念——_由一个“工厂”来“生产”出我们所需要的对象_

在工厂模式中,我们将对象的创建与对象的使用进行分开,当我们需要创建一个对象时,我们首先需要创建一个“工厂类”对象实例,后续的对象创建都从该“工厂”中获得

下图是笔者偷的一张描述工厂模式的图

假设我们现在要帮 Apple 设计一个生产工厂来生产最新的 iPhone13 和 iPad5,步骤如下:

I.物品接口

我们首先需要定义我们“生产”的对象的类型,例如,我们这个生产工厂可以生产 iPhone13 和 iPad5,那么我们这里便定义一个_设备类的接口类_作为不同产品的接口,同时声明一些这些设备共有的方法,这里随便定义两个方法

1
2
3
4
5
public interface Device
{
void showScreen();
void reboot();
}

II.工厂接口

要实现不同类型的工厂,我们通常需要先定义一个工厂类的接口类,我们要“生产”的是电子设备,那就叫_DeviceFactor_ 好了

1
2
3
4
public interface DeviceFactory
{
Device createDevice();
}

III.物品实体类

有了物品接口,我们可以开始定义相应的设备了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class iPhone13 implements Device
{
@Override
public void showScreen()
{
System.out.println("This is screen of iPhone13.");
}

@Override
public void reboot()
{
System.out.println("The iPhone13 is going to reboot sooooooon...");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class iPad5 implements Device
{
@Override
public void showScreen()
{
System.out.println("This is screen of iPad5.");
}

@Override
public void reboot()
{
System.out.println("The iPad5 is going to reboot sooooon...");
}
}

IV.工厂类

有了工厂接口与物品接口,我们可以开始实现我们的工厂类了,对于 iPhone 而言我们需要一个 iPhone13Factory,而对于 iPad5 我们则需要一个独立的 iPad5 工厂,相应地这两个工厂都要实现 DeviceFactory 接口的 createDevice() 方法

1
2
3
4
5
6
7
8
9
public class iPhone13Factory implements DeviceFactory
{
@Override
public iPhone13 createDevice()
{
return new iPhone13();
}
}

1
2
3
4
5
6
7
8
9
public class iPad5Factory implements DeviceFactory
{
@Override
public iPad5 createDevice()
{
return new iPad5();
}
}

O.工厂方法模式的工作方式

以下测试主程序用以说明工厂方法模式如何工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.lang.*;

public class FactoryMethodMode
{
public static void main(String[] args)
{
iPhone13Factory iphone_13_factory = new iPhone13Factory();
iPad5Factory ipad_5_factory = new iPad5Factory();

iPhone13 iphone13 = iphone_13_factory.createDevice();
iPad5 ipad5 = ipad_5_factory.createDevice();

iphone13.showScreen();
ipad5.showScreen();

iphone13.reboot();
ipad5.reboot();
}
}

运行结果如下:

1
2
3
4
This is screen of iPhone13.
This is screen of iPad5.
The iPhone13 is going to reboot sooooooon...
The iPad5 is going to reboot sooooon...

二、抽象工厂模式(Abstract factory pattern)

抽象工厂模式与工厂模式相类似,不同的是,在工厂模式中,我们以_独立的对象种类_来创建工厂(例如 iPhone13和 iPad5),而在抽象工厂模式中,则以同一种类别的对象作为划分的依据——每一类对象都有一个工厂,该工厂生产同一类别的不同种类的对象实例

偷的图

还是拿 Apple 的工厂做例子,这里我们有两条生产线:一条生产全系 iPhone,另一条生产全系 iPad

I.物品接口

我们首先需要定义我们“生产”的对象的类型,例如,我们这个生产工厂可以生产各系 iPhone 和 iPad,那么我们这里便定义一个_设备类的接口类_作为不同产品的接口,同时声明一些这些设备共有的方法,这里随便定义两个方法

1
2
3
4
5
public interface Device
{
void showScreen();
void reboot();
}

基于 Device 类,我们再定义两个抽象类——iPhone 和 iPad

1
2
3
4
5
public abstract class iPhone implements Device
{
public abstract void showScreen();
public abstract void reboot();
}
1
2
3
4
5
public abstract class iPad implements Device
{
public abstract void showScreen();
public abstract void reboot();
}

II.工厂接口

要实现不同类型的工厂,我们通常需要先定义一个工厂类的接口类,我们要“生产”的是电子设备,那就叫_DeviceFactor_ 好了

1
2
3
4
public interface DeviceFactory
{
Device createDevice(int version)
}

III.物品实体类

有了 iPhone 和 iPad 这两个抽象类,我们便可以定义具体的设备了,比如 iPhone13 类和 iPad 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class iPhone13 extends iPhone
{
@Override
public void showScreen()
{
System.out.println("This is screen of iPhone13.");
}

@Override
public void reboot()
{
System.out.println("The iPhone13 is going to reboot sooooooon...");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class iPad5 extends iPad
{
@Override
public void showScreen()
{
System.out.println("This is screen of iPad5.");
}

@Override
public void reboot()
{
System.out.println("The iPad5 is going to reboot sooooon...");
}
}

IV.工厂类

有了工厂接口与物品接口,我们可以开始实现我们的工厂类了,对于 iPhone 而言我们需要一个 iPhoneFactory,而对于 iPad 我们则需要一个独立的 iPad 工厂,相应地这两个工厂都要实现工厂接口的 createDevice() 方法

1
2
3
4
5
6
7
8
9
10
11
public class iPhoneFactory implements DeviceFactory
{
@Override
public Device createDevice(int version)
{
if (version == 13)
return new iPhone13();
else
return null;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class iPadFactory implements DeviceFactory
{
@Override
public Device createDevice(int version)
{
if (version == 5)
return new iPad5();
else
return null;
}
}

O.抽象工厂模式的工作方式

以下测试主程序用以说明工厂方法模式如何工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class AbstractFactoryPattern
{
public static void main(String[] args)
{
iPhoneFactory iphone_factory = new iPhoneFactory();
iPadFactory ipad_factory = new iPadFactory();

iPhone iPhone_13 = iphone_factory.createDevice(13);
iPad iPad_5 = ipad_factory.createDevice(5);

iPhone_13.showScreen();
iPad_5.showScreen();

iPhone_13.reboot();
iPad_5.reboot();
}
}

运行结果如下:

1
2
3
4
This is screen of iPhone13.
This is screen of iPad5.
The iPhone13 is going to reboot sooooooon...
The iPad5 is going to reboot sooooon...

三、单例模式(Singleton Pattern)

单例模式即在一个进程当中,一个类至多仅有一个实例,并提供一个全局访问点,当一个对象只需要一个全局实例时,便可以使用单例模式(例如 glibc 中的 errno?)

单例模式能够避免对象的重复创建,从而节约空间,并能避免由于操作不同实例而导致的逻辑错误

单例模式涉及的对象通常有两种创建方式(名字怪怪的):

懒汉方式

类似于 Linux 下的 lazy-binding 机制,我们并不在一运行程序便创建对象实例,而是等到我们第一次使用该对象时再进行创建

这是一个🌰:

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
51
52
53
import java.lang.*;
import java.util.*;

class SimpleObject
{
private int val = -1;
public SimpleObject()
{
System.out.println("SimpleObject is created!");
}

public int getVal()
{
return this.val;
}

public void setVal(int val)
{
this.val = val;
}
}

class InterfaceOutside
{
private static SimpleObject simpleObject = null;
public synchronized int getVal()
{
if (this.simpleObject == null)
this.simpleObject = new SimpleObject();
return this.simpleObject.getVal();
}
public synchronized void setVal(int val)
{
if (this.simpleObject == null)
this.simpleObject = new SimpleObject();
this.simpleObject.setVal(val);
}
}

public class SingletonPattern
{
public static void main(String[] args)
{
System.out.println("Create new InterfaceOutside instance.");
InterfaceOutside instance1 = new InterfaceOutside();
InterfaceOutside instance2 = new InterfaceOutside();
System.out.println("Done.");

System.out.println("Set val of instance1");
instance1.setVal(114514);
System.out.println("Get val of instance2: " + Integer.toString(instance2.getVal()));
}
}

运行结果如下:

1
2
3
4
5
Create new InterfaceOutside instance.
Done.
Set val of instance1
SimpleObject is created!
Get val of instance2: 114514

需要注意的是,为了保证初始化的线程安全,InterfaceOutside 类的两个方法都上了锁,且每次都需要判断对象是否存在,这会造成不可忽视的性能开销,故通常情况下不会使用懒汉方式来创建单例模式中的对象实例

饿汉方式

相比于懒汉方式,饿汉方式会在一开始就将需要用到的实例进行初始化,这种方式便能够直接使用已经生成的对象,而不需要判断对象是否存在

需要注意的是这种方式无论你在程序中是否需要使用该对象,他都会将该对象进行实例化,这通常会造成额外的开销

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
import java.lang.*;
import java.util.*;

class SimpleObject
{
private int val = -1;
public SimpleObject()
{
System.out.println("SimpleObject is created!");
}

public int getVal()
{
return this.val;
}

public void setVal(int val)
{
this.val = val;
}
}

class InterfaceOutside
{
private static SimpleObject simpleObject = new SimpleObject();
public synchronized int getVal()
{
return this.simpleObject.getVal();
}
public synchronized void setVal(int val)
{
this.simpleObject.setVal(val);
}
}

public class SingletonPattern
{
public static void main(String[] args)
{
System.out.println("Create new InterfaceOutside instance.");
InterfaceOutside instance1 = new InterfaceOutside();
InterfaceOutside instance2 = new InterfaceOutside();
System.out.println("Done.");

System.out.println("Set val of instance1");
instance1.setVal(114514);
System.out.println("Get val of instance2: " + Integer.toString(instance2.getVal()));
}
}

输出如下:

1
2
3
4
5
Create new InterfaceOutside instance.
SimpleObject is created!
Done.
Set val of instance1
Get val of instance2: 114514

四、建造者模式(Builder Pattern)

五、原型模式

0x02.结构型模式

在软件工程中,结构型模式主要关注于各个元件间的关系,以此简化设计

一、适配器模式

二、装饰器模式

三、代理模式

四、外观模式

五、桥接模式

六、组合模式

七、享元模式

0x03.行为型模式

创建型模式关注对象如何被创建,结构型模式关注对象间的组织结构,那么我们还缺少什么?从OOP的角度来看,我们似乎还少了一个类的“成员函数”——即对象之间的行为

在软件工程当中,行为型模式主要关注于对象之间的交流模式并进行实现

一、策略模式

二、模板方法

三、模式观察者模式

四、迭代子模式

五、责任链模式

六、命令模式

七、备忘录模式

八、状态模式

九、访问者模式

十、中介者模式

十一、解释器模式

0x04.并发型模式

并发型模式主要是随着多线程处理器的不断发展壮大而新出现的一种设计模式,在软件工程中,并发型模式是用来处理多线程编程范式的一类设计模式

to be 🕊🕊🕊