Gof23

设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理地运用设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是设计模式能被广泛应用的原因。

1. 分类

总体来说设计模式总共分为三大类:创建型模式,结构型模式,行为型模式

模式类别 数量 设计模式
创建型模式 5种 单例模式,工厂模式,抽象工厂模式,建造者模式和原型模式
结构型模式 7种 适配器模式,桥接模式,装饰模式,组合模式,外观模式,享元模式和代理模式
行为型模式 11种 模板方法模式,命令模式,迭代器模式,观察者模式,中介者模式,备忘录模式,解释器模式,状态模式,策略模式,职责链模式和访问者模式

1.1 创建型模式

用于描述怎么创建对象,它的主要特点是将对象的创建和使用分离

它包含共五种模式,分别是单例模式,工厂模式,抽象工厂模式,建造者模式和原型模式

1.2 结构型模式

用于描述如何将类或对象按照某种布局组成更大的结构

它包含共七种模式,分别是适配器模式,桥接模式,装饰模式,组合模式,外观模式,享元模式和代理模式

1.3 行为型模式

用于描述类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务,以及怎样分配职责

它包含共十一种模式,分别是模板方法模式,命令模式,迭代器模式,观察者模式,中介者模式,备忘录模式,解释器模式,状态模式,策略模式,职责链模式和访问者模式

2. OOP七大原则

OOP:**面向对象程序设计(Object Oriented Programming)**作为一种新方法,其本质是以建立模型体现出来的抽象思维过程和面向对象的方法。模型是用来反映现实世界中事物特征的。任何一个模型都不可能反映客观事物的一切具体特征,只能对事物特征和变化规律的一种抽象,且在它所涉及的范围内更普遍、更集中、更深刻地描述客体的特征。通过建立模型而达到的抽象是人们对客体认识的深化。

  • 开闭原则:对扩展开放,对修改关闭
  • 里氏替换原则:继承必须确保父类所拥有的性质在子类中依然成立
  • 依赖倒置原则:要面向接口编程,而不是面向实现编程
  • 单一职责原则:控制类的粒度大小,对其类与类之间进行解耦,提高内聚性
  • 接口隔离原则:要为每个类建立它们需要的专用接口
  • 迪米特法则:只与直接朋友交谈,而不与陌生人交谈
  • 合成复用原则:尽量先使用组合或者聚合等关联关系来实现,其次再考虑使用继承关系来实现

3. 单例模式(Singleton)

3.1 作用

保证一个类只有一个实例,并且提供一个访问该实例的全局访问点,即获取该单例的一个静态方法

3.2 使用场景

  • Windows的任务管理器
  • Windows的回收站
  • 数据库连接池的设计
  • Spring中的每一个Bean默认都是单例的
  • Servlet编程中,每个Servlet也是单例的

3.2 饿汉式

饿汉式单例模式是指,在JVM进行静态变量初始化的时候,就立即对该类进行实例化。且该类的构造器为私有化,仅提供一个获取该静态实例的getInstance()方法来保证实例的唯一性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 饿汉式单例模式
*
* @author zchengb
* @since 2021-01-29
*/
public class Hungry {
// 饿汉式单例模式可能会造成空间浪费
private byte[] data1 = new byte[1024 * 1024];
private byte[] data2 = new byte[1024 * 1024];
private byte[] data3 = new byte[1024 * 1024];
private byte[] data4 = new byte[1024 * 1024];

private final static Hungry HUNGRY = new Hungry();

private Hungry() {
}

public static Hungry getInstance() {
return HUNGRY;
}
}

3.3 DCL懒汉式

DCL -> Double Check Lock

a. 普通懒汉式

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
public class LazyMan {
private static LazyMan lazyMan;

private LazyMan() {
// 日志语句
System.out.println(Thread.currentThread().getName() + ": ok");
}

public static LazyMan getInstance() {
if(lazyMan == null) {
lazyMan = new LazyMan();
}
return lazyMan;
}

public static void main(String[] args) {
for(int i = 0; i < 10; ++i) {
// 多线程情况下
new Thread(LazyMan::getInstance).start();
}
}
}

// 运行结果(有几率)
// Thread-1: ok
// Thread-0: ok

由上述普通懒汉式单例的运行结果可看出,其在某些情况下也会有几率地出现多次构造实例,主要是因为没有加锁的情况下,同一行代码可能会被两条线程同时执行,而当lazyMan变量为空时,则会同时进行实例化,导致出现多次实例化的情况。

b. DCL懒汉式

由于上述的普通单例模式无法满足线程安全的要求,因此就衍生了DCL懒汉式单例,具体代码如下所示。

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
/**
* 懒汉式单例
*
* @author zchengb
* @since 2021-01-29
*/
public class LazyMan {
private static LazyMan lazyMan;

private LazyMan() {
}

public static LazyMan getInstance() {
// 外层if判断是为了防止每次都走到里边的synchronized锁进行长时间的等待
if(lazyMan == null) {
// 上锁 且进行判断 并根据判断结果进行实例化
synchronized (LazyMan.class) {
if(lazyMan == null) {
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}

public static void main(String[] args) {
for(int i = 0; i < 10; ++i) {
// 多线程情况下
new Thread(LazyMan::getInstance).start();
}
}
}

但需要知道的是,上述的getInstance()方法仍然是不安全的。假设线程一执行到lazyMan = new LazyMan()这句,这里看起来是一个语句,但实际上在其被编译完JVM执行的对应汇编代码可以发现,这条语句被分解为八条汇编指令,共大致做了三件事情:lazyMan实例分配内存 -> 初始化lazyMan构造器 -> 将lazyMan对象指向分配的内存空间,到最后一步则表示lazyMan已经不为null了。

但JVM为了优化指令,提高程序的运行效率,允许指令重排序。所以,可能会出现:lazyMan实例分配内存 -> 将lazyMan对象指向分配的内存空间 -> 初始化lazyMan构造器的指令执行顺序。这种情况下,当存在线程A与线程B,线程A执行至lazyMan = new LazyMan();时,由于指令重排指向了分配的内存空间但还未对其进行初始化的时候,线程切换至线程B进行实例的获取,很容易造成错误,也就是在使用时会出现对象尚未初始化的错误

解决上述问题的关键实际上在于禁止指令重排序优化,即使用volatile变量。

c. 完善后的DCL懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 懒汉式单例
*
* @author zchengb
* @since 2021-01-29
*/
public class LazyMan {
private static volatile LazyMan lazyMan = null;

private LazyMan() {
}

public static LazyMan getInstance() {
if(lazyMan == null) {
synchronized (LazyMan.class) {
if(lazyMan == null) {
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
}

但上述懒汉式的单例模式虽然线程安全了,但并不是无懈可击的,必须可以通过Java的反射来破坏这个单例模式,具体代码如下所示。

1
2
3
4
5
6
7
8
9
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
LazyMan lazyMan = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan newLazyMan = declaredConstructor.newInstance();
System.out.println(lazyMan);
System.out.println(newLazyMan);
System.out.println("反射获取的LazyMan与单例模式获取的LazyMan是否相等:" + (lazyMan == newLazyMan));
}

运行结果如下所示。

1
2
3
4
5
main: ok
main: ok
singleton.LazyMan@7ea987ac
singleton.LazyMan@12a3a380
反射获取的LazyMan与单例模式获取的LazyMan是否相等:false

在这种实现中,通过setAccessible()方法可以将私有构造参数的访问级别设置为public,然后调用构造函数从而实例化对象,如果要防止这种攻击,则需要在构造函数中添加防止多次实例化的代码。

d. 静态内部类

当Singleton类加载时,静态内部类SingletonHolder没有被加载进内存,只有当调用Singleton.getInstance()方法时,SingletonHolder才会被加载,此时初始化实例,且JVM能确保该实例只被实例化一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 静态内部类方式实现单例模式
*
* @author zchengb
* @since 2021-01-30
*/
public class Singleton {
private Singleton(){}

public static Singleton getInstance() {
return SingletonHolder.SINGLETON;
}

private static class SingletonHolder {
private static final Singleton SINGLETON = new Singleton();
}
}

e. 相对安全的单例模式

枚举类本身也是一个Class类,只不过继承了Enum.class

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 相对安全的单例模式
*
* @author zchengb
* @since 2021-01-30
*/
public enum EnumSingleton {
SINGLETON;

public EnumSingleton getInstance() {
return SINGLETON;
}
}

使用该模式的单例方法,可以有效地防止反射破坏,再者,由于Java在其反射获取构造器中的getInstance()方法中也指明了不允许反射方式创建枚举的实例,如下的getInstance()方法源码所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@CallerSensitive
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}

3.4 优缺点

  • 优点

    • 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)
    • 避免对资源的多重占用(比如写文件操作)
  • 缺点

    • 没有接口,不能继承
    • 与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化

4. 工厂模式(Factory)

4.1 简单工厂(Simple Factory)

在创建一个对象时不向用户暴露内部细节,并提供一个创建对象的通用接口。

简单工厂把实例化的操作单独放到一个类中,这个类就成为了简单工厂类,让简单工厂类来决定应该用哪一个具体子类来实例化。这样做的好处就是能帮助用户类与具体子类之间进行解耦,用户类不需要知道有哪些子类以及应当实例化哪个子类

Untitled Diagram

具体代码实现如下所示。

  • Car

1
2
3
4
5
6
7
8
9
/**
* 车接口
*
* @author zchengb
* @since 2021-01-30
*/
public interface Car {
void name();
}
  • Audi

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 奥迪
*
* @author zchengb
* @since 2021-01-30
*/
public class Audi implements Car {
@Override
public void name() {
System.out.println("Audi");
}
}
  • Tesla

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 特斯拉
*
* @author zchengb
* @since 2021-01-30
*/
public class Tesla implements Car {
@Override
public void name() {
System.out.println("Tesla");
}
}
  • SimpleFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 简单工厂实现方式
*
* @author zchengb
* @since 2021-01-30
*/
public class SimpleFactory {
public Car getCar(String car) {
if("Audi".equals(car)) {
return new Audi();
}
else if("Tesla".equals(car)) {
return new Tesla();
}else {
return null;
}
}
}

4.2 工厂方法(Factory Method)

定义了一个创建对象的接口,但由子类来决定要实例化哪个类,工厂方法把实例化操作推迟到子类。

工厂方法

具体代码(部分代码基于上述实现,本处省略)如下所示。

  • CarFactory

1
2
3
4
5
6
7
8
/**
* 车工厂
* @author zchengb
* @since 2021-01-30
*/
public interface CarFactory {
Car getCar();
}
  • TeslaFactory

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 特斯拉车工厂
*
* @author zchengb
* @since 2021-01-30
*/
public class TeslaFactory implements CarFactory{
@Override
public Car getCar() {
return new Tesla();
}
}
  • AudiFactory

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 奥迪车工厂
*
* @author zchengb
* @since 2021-01-30
*/
public class AudiCarFactory implements CarFactory {
@Override
public Car getCar() {
return new Audi();
}
}

4.3 优缺点

优点显而易见,可以隐藏原始类,方便之后的代码迁移。调用者只需要记住类的代名词即可。但由于多了层封装,会造成类的数目过多,系统复杂度增加

5. 抽象工厂模式(Abstract Factory)

5.1 作用

提供一个接口,用于创建相关的对象家族。同时围绕一个超级工厂创建其他工厂,该超级工厂又称为其他工厂的工厂。抽象工厂创建的是对象家族,也就是很多对象而不是一个对象,并且这些对象是相关的,而工厂方法模式只是用于创建一个对象,这和抽象工厂模式有很大不同。

从高层次来看,抽象工厂使用了组合,即Client组合了抽象工厂,而工厂方法模式使用了继承

抽象工厂

5.2 实现代码

  • Client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Client {
public static void main(String[] args) {
System.out.println("===============小米系列产品===============");
IProductFactory xiaoMiFactory = new XiaoMiFactory();
IPhone xiaoMiPhone = xiaoMiFactory.phoneProduct();
xiaoMiPhone.callUp();
xiaoMiPhone.sendSMS();
IRouter xiaoMiRouter = xiaoMiFactory.routerProduct();
xiaoMiRouter.openWifi();
System.out.println("===============小米系列产品===============");
IProductFactory huaWeiFactory = new HuaWeiFactory();
IPhone huaweiPhone = huaWeiFactory.phoneProduct();
huaweiPhone.callUp();
huaweiPhone.sendSMS();
IRouter huaweiRouter = huaWeiFactory.routerProduct();
huaweiRouter.openWifi();
}
}
  • IPhone

此处的IPhone实际为Interface Phone

1
2
3
4
5
6
7
8
9
/**
* 手机产品接口
*/
public interface IPhone {
void start();
void shutdown();
void callUp();
void sendSMS();
}
  • IProductFactory

1
2
3
4
5
6
7
8
9
10
/**
* 抽象产品工厂
*/
public interface IProductFactory {
// 生产手机
IPhone phoneProduct();

// 生产路由器
IRouter routerProduct();
}
  • IRouter

1
2
3
4
5
6
7
8
9
/**
* 路由器产品接口
*/
public interface IRouter {
void start();
void shutdown();
void openWifi();
void setting();
}
  • HuaWeiFactory

1
2
3
4
5
6
7
8
9
10
11
public class HuaWeiFactory implements IProductFactory{
@Override
public IPhone phoneProduct() {
return new HuaWeiPhone();
}

@Override
public IRouter routerProduct() {
return new HuaWeiRouter();
}
}
  • HuaWeiPhone

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class HuaWeiPhone implements IPhone{
@Override
public void start() {
System.out.println("打开华为手机");
}

@Override
public void shutdown() {
System.out.println("关闭华为手机");
}

@Override
public void callUp() {
System.out.println("使用华为手机打电话");
}

@Override
public void sendSMS() {
System.out.println("使用华为手机发短信");
}
}
  • HuaWeiRouter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class HuaWeiRouter implements IRouter{
@Override
public void start() {
System.out.println("启动华为路由器");
}

@Override
public void shutdown() {
System.out.println("关闭华为路由器");
}

@Override
public void openWifi() {
System.out.println("打开WiFi");
}

@Override
public void setting() {
System.out.println("华为设置");
}
}
  • XiaoMiFactory

1
2
3
4
5
6
7
8
9
10
11
public class XiaoMiFactory implements IProductFactory{
@Override
public IPhone phoneProduct() {
return new XiaoMiPhone();
}

@Override
public IRouter routerProduct() {
return new XiaoMiRouter();
}
}
  • XiaoMiPhone

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class XiaoMiPhone implements IPhone{
@Override
public void start() {
System.out.println("打开小米手机");
}

@Override
public void shutdown() {
System.out.println("关闭小米手机");
}

@Override
public void callUp() {
System.out.println("小米手机打电话");
}

@Override
public void sendSMS() {
System.out.println("小米手机发短信");
}
}
  • XiaoMiRouter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class XiaoMiRouter implements IRouter{
@Override
public void start() {
System.out.println("启动小米路由器");
}

@Override
public void shutdown() {
System.out.println("关闭小米路由器");
}

@Override
public void openWifi() {
System.out.println("打开WiFi");
}

@Override
public void setting() {
System.out.println("小米设置");
}
}

5.3 优缺点

  • 优点

当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。

  • 缺点

产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Factory里加代码,又要在具体的产品里面加代码。

6. 建造者模式(Builder)

封装一个对象的构造过程,并允许按步骤构造。

6.1 作用

将一个复杂对象的构建和它的表示分离,使得同样的构建过程可以创建不同的表示。也就是在用户不知道对象的建造过程和细节的情况下就可以直接创建复杂的对象。用户只需要给出指定复杂对象的类型和内容,建造者模式负责按顺序创建复杂对象(把内部的建造过程和细节隐藏起来)。

Untitled Diagram

6.2 实现代码

具体实现代码如下所示。

a. Builder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 抽象的建造者
*
* @author zchengb
* @since 2021-01-31
*/
public abstract class Builder {
// 步骤A
abstract Builder buildA();
// 步骤B
abstract Builder buildB();
// 步骤C
abstract Builder buildC();
// 步骤D
abstract Builder buildD();

// 完工: 得到产品
abstract Product getProduct();
}

b. Worker

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
/**
* 具体的建造者:工人
*
* @author zchengb
* @since 2021-01-31
*/
public class Worker extends Builder{
private Product product;

public Worker() {
this.product = new Product();
}

@Override
Builder buildA() {
product.setBuildA("A");
return this;
}

@Override
Builder buildB() {
product.setBuildB("B");
return this;
}

@Override
Builder buildC() {
product.setBuildC("C");
return this;
}

@Override
Builder buildD() {
product.setBuildD("D");
return this;
}

@Override
Product getProduct() {
return product;
}
}

c. Product

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
/**
* 产品
* @author zchengb
* @since 2021-01-31
*/
public class Product {
private String buildA;
private String buildB;
private String buildC;
private String buildD;

@Override
public String toString() {
return "Product{" +
"buildA='" + buildA + '\'' +
", buildB='" + buildB + '\'' +
", buildC='" + buildC + '\'' +
", buildD='" + buildD + '\'' +
'}';
}

public String getBuildA() {
return buildA;
}

public void setBuildA(String buildA) {
this.buildA = buildA;
}

public String getBuildB() {
return buildB;
}

public void setBuildB(String buildB) {
this.buildB = buildB;
}

public String getBuildC() {
return buildC;
}

public void setBuildC(String buildC) {
this.buildC = buildC;
}

public String getBuildD() {
return buildD;
}

public void setBuildD(String buildD) {
this.buildD = buildD;
}
}

d. Director

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 指挥:负责指挥构建一个工程,工程如何构建
*
* @author zchengb
* @since 2021-01-31
*/
public class Director {
public Product build(Builder builder) {
builder.buildA();
builder.buildB();
builder.buildC();
builder.buildD();
return builder.getProduct();
}
}

e. Test

1
2
3
4
5
6
7
8
public class Test {
public static void main(String[] args) {
Product product = new Worker().buildA().buildD().getProduct();
System.out.println("自定义构建过程:" + product);
product = new Director().build(new Worker());
System.out.println("封装的构造过程:" + product);
}
}

运行结果如下所示。

1
2
自定义构建过程:Product{buildA='A', buildB='null', buildC='null', buildD='D'}
封装的构造过程:Product{buildA='A', buildB='B', buildC='C', buildD='D'}

由上述测试结果则可以得知,建造者模式可以由指挥者进行直接构建,也可以由调用者自主控制构建过程。

6.3 优缺点

  • 优点

    • 产品的建造和表示分离,实现了解耦,使用建造者模式可以使客户端不必知道产品内部组成的细节
    • 将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰
    • 具体的建造者类之间是相互独立的,这有利于系统的扩展,增加新的具体建造者无需修改原有类库的代码,符合开闭原则
  • 缺点

    • 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,但如果产品之间差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制
    • 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大

7. 原型模式(Prototype)

7.1 作用

使用原型实例指定要创建对象的类型,通过复制这个原型来创建新对象

image-20210201115648116

原型模式的通用类图如上图所示,原型模式的核心是一个clone方法,通过该方法进行对象的拷贝,Java提供了一个Cloneable接口来标识这个类是可拷贝的,Cloneable仅仅是用作标识的,因为其源码中一个方法都没有,只起到了标识的作用,随后重写该类的clone()方法。

7.2 实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Video implements Cloneable{
private String name;
private Date createTime;

public Video() {}

public Video(String name, Date createTime) {
this.name = name;
this.createTime = createTime;
}

@Override
protected Object clone() throws CloneNotSupportedException {
Object object = super.clone();
Video v = (Video)object;
v.createTime = (Date) this.createTime.clone();
return object;
}
}

其中clone()方法则展示了原型模式的核心。

7.3 优缺点

  • 优点

    • 原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是在一个循环体内产生大量的对象时,原型模式可以更好地体现这个优点
  • 缺点

    • 直接在内存中拷贝,其构造函数是不会执行的,优点就是减少了约束,需要在应用中结合实际情况考虑

8. 适配器模式(Adapter)

Convert the interface of a class into a another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.

8.1 作用

将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作

适配器模式

8.2 实现代码

鸭子(Duck)和火鸡(Turkey)拥有不同的叫声,Duck的叫声则调用quack()方法,而Turkey调用gobble()方法,现在要求给Turkey🦃的嘴装上一个适配器「Adapter」,即将Turkey的gobble()方法适配成Duck的quack()的方法,从而让Turkey冒充Duck,具体代码实现如下所示。

适配器模式

a. IDuck

1
2
3
public interface IDuck {
void quack();
}

b. ITurkey

1
2
3
public interface ITurkey {
void gobble();
}

c. Turkey

1
2
3
4
5
6
public class Turkey implements ITurkey{
@Override
public void gobble() {
System.out.println("gobble!");
}
}

d. TurkeyAdapter

1
2
3
4
5
6
7
8
9
10
11
12
public class TurkeyAdapter implements IDuck{
Turkey turkey;

public TurkeyAdapter(Turkey turkey) {
this.turkey = turkey;
}

@Override
public void quack() {
turkey.gobble();
}
}

e. Client

1
2
3
4
5
public static void main(String[] args) {
Turkey turkey = new Turkey();
IDuck duck = new TurkeyAdapter(turkey);
duck.quack();
}

8.3 优缺点

  • 优点

    • 可以让任何两个没有关联的类一起运行
    • 提高了类的复用
    • 增加了类的透明度
    • 灵活性好
  • 缺点

    • 过多地使用适配器,会让系统非常零乱,不易整体进行把握
      • 比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
    • 由于Java至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类

9. 桥接模式(Bridge)

Decouple an abstraction from its implementation so that the two can very independently.

9.1 作用

桥接模式是将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体「Handle and Body」模式或接口「Intergface」模式,桥接模式的重点在于解耦。

9.2 实现代码

image-20210206113230861

a. Brand

1
2
3
public interface Brand {
void info();
}

b. Computer

1
2
3
4
5
6
7
8
9
10
11
public abstract class Computer {
protected Brand brand;

public Computer(Brand brand) {
this.brand = brand;
}

public void info() {
brand.info();
}
}

c. Desktop

1
2
3
4
5
6
7
8
9
10
11
public class Desktop extends Computer{
public Desktop(Brand brand) {
super(brand);
}

@Override
public void info() {
super.info();
System.out.println("台式机");
}
}

d. Laptop

1
2
3
4
5
6
7
8
9
10
11
public class Laptop extends Computer{
public Laptop(Brand brand) {
super(brand);
}

@Override
public void info() {
super.info();
System.out.println("笔记本");
}
}

e. Apple

1
2
3
4
5
6
public class Apple implements Brand{
@Override
public void info() {
System.out.println("苹果");
}
}

f. Lenovo

1
2
3
4
5
6
public class Lenovo implements Brand{
@Override
public void info() {
System.out.println("联想");
}
}

9.3 优缺点

  • 优点

    • 抽象和实现分离
    • 优秀的扩充能力
    • 实现细节对客户透明
  • 缺点

    • 桥接模式的引入会增加系统的理解和设计难度
    • 要求正确识别出系统两个独立变化的维度,因此其使用范围有一定的局限性

10. 装饰模式(Decorator)

Attach additional responsibilities to an object dynamically keeping the smae interface. Decorators provide a flexible alternative to subclassing for extending functionality.

动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式相比生成子类更为灵活。

10.1 作用

装饰者和具体组件都继承自组件,具体组件的方法实现不需要依赖于其他对象,而装饰者组合了一个组件,这样它可以装饰其他装饰者或者具体组件。所谓装饰,就是把这个装饰者套在被装饰者之上,从而动态扩展被装饰者的功能,装饰着的方法有一部分是自己的,这属于它自己的功能,然后调用被装饰者的方法实现,从而也保留了被装饰者的功能,其最核心的作用就是为对象动态添加功能

10.2 实现代码

设计不同种类的电脑,电脑可以添加配件,比如可以添加显示器,并且支持动态添加新配件,比如可以添加DDR4内存条,固态硬盘等,每添加一种配件,该电脑的价格就会增加。

装饰模式

a. Computer

1
2
3
public interface Computer {
double cost();
}

b. Desktop

1
2
3
4
5
6
public class Desktop implements Computer{
@Override
public double cost() {
return 2;
}
}

c. Laptop

1
2
3
4
5
6
public class Laptop implements Computer{
@Override
public double cost() {
return 1;
}
}

d. AccessoryDecorator

1
2
3
public abstract class AccessoryDecorator implements Computer{
protected Computer computer;
}

e. RAM

1
2
3
4
5
6
7
8
9
10
public class RAM extends AccessoryDecorator {
public RAM(Computer computer) {
this.computer = computer;
}

@Override
public double cost() {
return 1 + computer.cost();
}
}

f. Disk

1
2
3
4
5
6
7
8
9
10
public class Disk extends AccessoryDecorator{
public Disk(Computer computer) {
this.computer = computer;
}

@Override
public double cost() {
return 1 + computer.cost();
}
}

g. Test

1
2
3
4
5
6
public static void main(String[] args) {
Computer desktop = new Desktop();
desktop = new Disk(desktop);
desktop = new RAM(desktop);
System.out.println("当前价格:" + desktop.cost());
}

运行结果如下所示。

1
2
运行结果如下所示:
当前价格:4.0

以上就是相关代码,其类应该对扩展开放,对修改关闭,也就是当添加新功能时不需要修改代码,饮料可以动态添加新的配件,而不需要修改电脑的代码。

10.3 优缺点

  • 优点

    • 装饰类和被装饰类可以独立发展,而不会互相耦合
    • 装饰模式是继承关系的一个替代方案,不管装饰多少层,返回的对象还是Component,实现的还是is-a的关系
    • 装饰模式可以动态地扩展一个实现类的功能
  • 缺点

    • 多层的装饰会使系统的复杂度增加,在其中一层出现问题时,工作量大

11. 外观模式(Facade)

Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.

要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行,外观模式提供一个高层次的接口,使得子系统更易于使用。

11.1 作用

提供了一个统一的接口,用来访问子系统中的一群接口,从而使子系统更容易使用。

外观模式「Facade Pattern」也叫做门面模式,是一种比较常用的封装模式,它注重的是“统一的对象”,也就是提供一个访问子系统的接口,除了这个接口不允许有任何访问子系统的行为发生。

外观模式

11.2 实现代码

观看电影需要很多步骤,下述代码将使用外观模式实现一键看电影的功能。

a. SubSystem

1
2
3
4
5
6
7
8
9
10
11
12
13
public class SubSystem {
public void turnOnTV() {
System.out.println("turnOnTV()");
}

public void setCD(String cd) {
System.out.println("set cd:" + cd);
}

public void startWatching() {
System.out.println("startWatching()");
}
}

b. Facade

1
2
3
4
5
6
7
8
public class Facade {
private SubSystem subSystem = new SubSystem();
public void watchMovie() {
subSystem.turnOnTV();
subSystem.setCD("a movie");
subSystem.startWatching();
}
}

c. Test

1
2
3
4
public static void main(String[] args) {
Facade facade = new Facade();
facade.watchMovie();
}

运行结果如下所示。

1
2
3
turnOnTV()
set cd:a movie
startWatching()

11.3 优缺点

  • 优点

    • 减少系统的相互依赖
    • 提升了灵活性
    • 提高了安全性
  • 缺点

    • 不符合开闭原则,当内部集成出现错误时,需对其内部进行修改

12. 组合模式(Composite)

Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individuall objects and compositions of objects uniformly.

将对象组合成树形结构以表示“部分 - 整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。

12.1 作用

将对象组合成树形结构来表示“整体 / 部分”层次关系,允许用户以相同的方式处理单独对象和组合对象。组合对象「Composite Pattern」也叫合成模式,有时也叫部分-整体模式「Part-Whole」,主要用来描述部分和整体的关系。

组合模式的通用类图如下所示。

组合模式

12.2 实现代码

a. Component

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public abstract class Component {
protected String name;

public Component(String name) {
this.name = name;
}

public void print() {
print(0);
}

abstract void print(int level);

abstract public void add(Component component);

abstract public void remove(Component component);
}

b. Composite

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
public class Composite extends Component{
private List<Component> child;

public Composite(String name) {
super(name);
this.child = new ArrayList<>();
}

@Override
void print(int level) {
for(int i = 0; i < level; ++i) {
System.out.print("--");
}
System.out.println("Composite:" + name);
for(Component component : child) {
component.print(level + 1);
}
}

@Override
public void add(Component component) {
child.add(component);
}

@Override
public void remove(Component component) {
child.remove(component);
}
}

c. Leaf

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 Leaf extends Component{
public Leaf(String name) {
super(name);
}

@Override
void print(int level) {
for(int i = 0; i < level; ++i) {
System.out.print("--");
}
System.out.println("leaf:" + name);
}

@Override
public void add(Component component) {
// 牺牲透明性换取单一职责原则,这样就无需考虑是叶子节点还是组合节点
throw new UnsupportedOperationException();
}

@Override
public void remove(Component component) {
throw new UnsupportedOperationException();
}
}

d. Test

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
Composite root = new Composite("root");
Component node1 = new Leaf("1");
Component node2 = new Composite("2");
Component node3 = new Leaf("3");
root.add(node1);
root.add(node2);
root.add(node3);
node2.add(new Leaf("4"));
root.print();
}

运行结果如下所示。

1
2
3
4
5
Composite:root
--leaf:1
--Composite:2
----leaf:4
--leaf:3

13. 享元模式(Flyweight)

Use sharing to support large numbers of fine-grained objects effciently.

使用共享对象可有效地支持大量的细粒度的对象

13.1 作用

利用共享的方式来支持大量细粒度的对象,这些对象一部分内部状态是相同的。享元模式「Flyweight Pattern」是池技术的重要实现方式,其定义提出了两个要求:细粒度的对象和共享对象。

享元模式的通用类图如下所示。

享元模式

13.2 实现代码

下述代码实际上可以运用在项目中,利用缓存池来加速大量小对象的访问时间。

a. Flyweight

简单地说就是一个产品的抽象类,同时定义出对象的外部状态和内部状态的接口或实现。

1
2
3
public interface Flyweight {
void doOperation(String extrinsicState);
}

b. FlyweightFactory

职责简单,就是构造一个池容器,同时提供从池中获取该对象的方法。

1
2
3
4
5
6
7
8
9
10
11
public class FlyWeightFactory {
private HashMap<String, Flyweight> pool = new HashMap<>();

Flyweight getFlyweight(String intrinsicState) {
if(!pool.containsKey(intrinsicState)) {
Flyweight flyweight = new ConcreteFlyweight(intrinsicState);
pool.put(intrinsicState, flyweight);
}
return pool.get(intrinsicState);
}
}

c. ConcreteFlyweight

具体一个产品类,实现抽象角色定义的业务,该角色中需要注意的是内部状态处理应该与环境无关,不应该出现一个操作改变了内部状态,同时修改了外部状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ConcreteFlyweight implements Flyweight{
private String intrinsicState;

public ConcreteFlyweight(String intrinsicState) {
this.intrinsicState = intrinsicState;
}

@Override
public void doOperation(String extrinsicState) {
System.out.println("Object address:" + System.identityHashCode(this));
System.out.println("IntrinsicState:" + intrinsicState);
System.out.println("ExtrinsicState:" + extrinsicState);
}
}

d. Test

1
2
3
4
5
6
7
public static void main(String[] args) {
FlyWeightFactory flyWeightFactory = new FlyWeightFactory();
Flyweight a1 = flyWeightFactory.getFlyweight("a1");
Flyweight a2 = flyWeightFactory.getFlyweight("a1");
a1.doOperation("x");
a2.doOperation("y");
}

运行结果如下所示

1
2
3
4
5
6
Object address:2125039532
IntrinsicState:a1
ExtrinsicState:x
Object address:2125039532
IntrinsicState:a1
ExtrinsicState:y

享元模式的目的在于运用共享技术,使得一些细粒度的对象可以共享,多使用细粒度的对象,便于重用或重构。

13.3 优缺点

  • 优点

    • 可以大大减少应用程序创建的对象,降低程序内存的占用
  • 缺点

    • 提高了系统复杂性
    • 需要分离出外部状态和内部状态,而外部状态具有固化特性。不应该随内部状态改变而改变

14. 代理模式(Proxy)

Provide a surrogate or placeholder for another object to control access to it.

为其他对象提供一种代理以控制对这个对象的访问。

14.1 作用

代理模式(Proxy Pattern)是一个使用率非常高的模式。其通用类图如下所示。

代理模式

其中代理分为以下四类:

  • 远程代理(Remote Proxy):控制对远程对象的访问,它负责将请求及参数进行编码,并向不同地址空间中的对象发送已经编码的请求。
  • 虚拟代理(Virtual Proxy):根据需要创建开销很大的对象,它可以缓存实体的附加信息,以便延迟对它的访问,例如在网站加载一张很大的图片时,无法立马完成,可以用虚拟代理缓存图片的大小信息,然后生成一张临时图片代替原始图片。
  • 保护代理(Protection Proxy):按权限控制对象的访问,它负责检查调用者是否具有实现一个请求所必须的访问权限。
  • 智能代理(Smart Reference):取代了简单的指针,它在访问对象时执行一些附加操作,如:记录对象的引用次数;当第一次引用一个对象时,将它装入内存;在访问一个实际对象前,检查是否已经锁定,以确保其他对象无法对其进行更改。

14.2 实现代码

以下代码将叙述一座塔,塔中最多仅可容纳3人,每次进塔则通过代理人判断塔内人员是否已满,再确定是否允许进入。

a. Person

1
2
3
4
5
6
7
8
9
10
11
12
public class Person {
private final String name;

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

@Override
public String toString() {
return name;
}
}

b. Tower

1
2
3
4
5
6
7
8
9
10
public interface ITower {
void enter(Person person);
}

public class Tower implements ITower{
@Override
public void enter(Person person) {
System.out.println(person + " enter success!");
}
}

c. TowerProxy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class TowerProxy implements ITower{
private static final int NUM_PERSON_ALLOWED = 3;
private final Tower tower;
private int nums;

public TowerProxy(Tower tower) {
this.tower = tower;
}

@Override
public void enter(Person person) {
if(nums < NUM_PERSON_ALLOWED) {
tower.enter(person);
++nums;
}else {
System.out.println("DISALLOWED enter!");
}
}
}

运行以下代码进行测试。

1
2
3
4
5
6
var proxy = new TowerProxy(new Tower());
proxy.enter(new Person("Red people"));
proxy.enter(new Person("Black people"));
proxy.enter(new Person("Green people"));
proxy.enter(new Person("Brown people"));
proxy.enter(new Person("White people"));

得到如下测试结果。

1
2
3
4
5
Red people enter success!
Black people enter success!
Green people enter success!
DISALLOWED enter!
DISALLOWED enter!

15. 模板方法模式(Template Method)

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.

定义一个操作中的算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

15.1 作用

定义算法框架,并将一些步骤的实现延迟到子类中。通过模板方法,子类可以重新定义算法的某些步骤,而不用改变算法的结构。

其通用UML模板如下所示。

模板方法模式

15.2 实现代码

下述代码将通过模板方法模式来展示悍马汽车多款式模型的启动方式。

a. HummerModel

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class HummerModel {
public abstract void start();
public abstract void stop();
public abstract void alarm();
public abstract void engineBoom();
public void run() {
this.start();
this.engineBoom();
this.alarm();
this.stop();
}
}

b. HummerH1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class HummerH1 extends HummerModel{
@Override
public void start() {
System.out.println("H1 starting.");
}

@Override
public void stop() {
System.out.println("H1 stop.");
}

@Override
public void alarm() {
System.out.println("H1 alarm.");
}

@Override
public void engineBoom() {
System.out.println("H1 engine boom.");
}
}

c. HummerH2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class HummerH2 extends HummerModel{
@Override
public void start() {
System.out.println("H2 starting.");
}

@Override
public void stop() {
System.out.println("H2 stop.");
}

@Override
public void alarm() {
System.out.println("H2 alarm.");
}

@Override
public void engineBoom() {
System.out.println("H2 engine boom.");
}
}

运行以下代码即可体现模板方法run()

1
2
new HummerH1().run();
new HummerH2().run();

得到以下运行结果。

1
2
3
4
5
6
7
8
H1 starting.
H1 engine boom.
H1 alarm.
H1 stop.
H2 starting.
H2 engine boom.
H2 alarm.
H2 stop.

15.3 优缺点

往常的设计习惯中,抽象类负责声明最抽象、最一般事物属性和方法,实现类完成具体的事物属性和方法。当时在模板方法模式中却颠倒了,抽象类定义了部分抽象方法,由子类实现,子类执行的结果影响了父类的结果,也就是子类对父类产生了影响,在实际的项目当中可能会带来代码的阅读难度。

16. 命令模式(Command)

Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.

将一个请求封装成一个对象,从而让用户使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。

16.1 作用

命令模式是一个高内聚的模式,将命令封装成对象,同时使对象具有以下功能:

  • 使用命令来参数化其他对象
  • 将命令放入队列中进行排队
  • 将命令的操作记录到日志中
  • 支持可撤销的操作

其通用类图如下所示。

命令模式

16.2 实现代码

以下代码主要实现一个遥控器,能够控制电灯开关。

a. Command

1
2
3
public interface Command {
void execute();
}

b. Light

1
2
3
4
5
6
7
8
9
public class Light {
public void on() {
System.out.println("Light is on.");
}

public void off() {
System.out.println("Light is off.");
}
}

c. LightOffCommand

1
2
3
4
5
6
7
8
9
10
11
12
public class LightOffCommand implements Command{
Light light;

public LightOffCommand(Light light) {
this.light = light;
}

@Override
public void execute() {
this.light.off();
}
}

d. LightOnCommand

1
2
3
4
5
6
7
8
9
10
11
12
public class LightOnCommand implements Command{
Light light;

public LightOnCommand(Light light) {
this.light = light;
}

@Override
public void execute() {
this.light.on();
}
}

e. Invoker

1
2
3
4
5
6
7
8
9
10
11
public class Invoker {
private Command command;

public void setCommand(Command command) {
this.command = command;
}

public void action() {
this.command.execute();
}
}

f. Client

1
2
3
4
5
6
Invoker invoker = new Invoker();
Light light = new Light();
invoker.setCommand(new LightOnCommand(light));
invoker.action();
invoker.setCommand(new LightOffCommand(light));
invoker.action();

程序运行结果如下所示

1
2
Light is on.
Light is off.

16.3 优缺点

  • 优点

    • 类间解耦
      • 调用者角色与接收者角色之间么有任何依赖关系,调用者实现功能时只需调用Command抽象类的execute()方法就可以
    • 可扩展性
      • 可以十分轻易地扩展Command的子类
    • 命令模式结合责任链模式或模板方法模式会更优秀
  • 缺点

    • 随着命令增多,会导致Command子类的膨胀

17. 迭代器模式(Iterator)

Provide a way to acess the elements of an aggregate object sequentially without exposing its underlying representation.

它提供一种方法访问一个容器对象中各个元素,而又不需要暴露对该对象的内部细节。

17.1 作用

迭代器模式提供了一种顺序访问聚合对象元素的方法,并且不暴露聚合对象的内部表示,具体类图如下所示。

迭代器模式

  • Aggregate是聚合类,其中createIterator()方法可以产生一个Iterator
  • Iterator主要定义了hasNext()next()方法
  • Client组合了Aggregate,为了迭代遍历Aggregate,也需要组合Iterator

17.2 实现代码

a. Aggregate

1
2
3
public interface Aggregate {
Iterator<Integer> createIterator();
}

b. ConcreteAggregate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ConcreteAggregate implements Aggregate{
private Integer[] items;

public ConcreteAggregate() {
items = new Integer[10];
for (int i = 0; i < items.length; i++) {
items[i] = i;
}
}

@Override
public Iterator createIterator() {
return new ConcreteIterator<>(items);
}
}

c. ConcreteIterator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ConcreteIterator<Item> implements Iterator<Item> {
private Item[] items;
private int position = 0;

public ConcreteIterator(Item[] items) {
this.items = items;
}

@Override
public boolean hasNext() {
return position < items.length;
}

@Override
public Item next() {
return items[position++];
}
}

d. Client

1
2
3
4
5
Aggregate aggregate = new ConcreteAggregate();
Iterator<Integer> iterator = aggregate.createIterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}

17.3 优缺点

迭代器现在应用越来越广泛,已经称为了一个最基础的工具,所以一般都无需手动写迭代器。

18. 观察者模式(Observer)

18.1 作用

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

定义对象间的一种一对多的依赖关系,使得当每一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新。

其通用类图如下所示。

观察者模式

18.2 普通实现

以下代码主要展示温度变化的情况下,观察者对象实时获得温度变动消息。

a. Observer

1
2
3
public interface Observer {
void update(float temp);
}

b. Subject

1
2
3
4
5
public interface Subject {
void resisterObserver(Observer o);
void removeObserver(Observer o);
void notifyObserver();
}

c. WeatherData

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
public class WeatherData implements Subject{
private List<Observer> observers;
private float temperature = 18.0f;

public WeatherData() {
this.observers = new ArrayList<>();
}

public void setTemperature(float temperature) {
this.temperature = temperature;
notifyObserver();
}

@Override
public void resisterObserver(Observer o) {
observers.add(o);
}

@Override
public void removeObserver(Observer o) {
observers.remove(o);
}

@Override
public void notifyObserver() {
observers.forEach(o -> o.update(temperature));
}
}

d. StatisticsDisplay

1
2
3
4
5
6
7
8
9
10
public class StatisticsDisplay implements Observer{
public StatisticsDisplay(Subject weatherData) {
weatherData.resisterObserver(this);
}

@Override
public void update(float temp) {
System.out.println("StatisticsDisplay up:" + temp + "℃");
}
}

测试方法如下所示。

1
2
3
4
System.out.println("==================NORMAL==================");
WeatherData weatherData = new WeatherData();
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
weatherData.setTemperature(30.0f);

运行结果如下所示。

1
2
==================NORMAL==================
StatisticsDisplay up:30.0

18.3 Java8实现

Java8及之前的版本中提供了方便的Observable超类以及Observer接口等现成的封装供用户快速实现观察者模式。

以下代码将展示消息板块发生消息变动时,自动将消息推送给订阅了消息板块的用户。

a. MessageBoard

1
2
3
4
5
6
7
public class MessageBoard extends Observable {
public void changeMsg(String msg) {
System.out.println("MessageBoard changing msg...");
super.setChanged();
super.notifyObservers(msg);
}
}

b. User

1
2
3
4
5
6
public class User implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("Message board changed: " + arg);
}
}

针对以上版本的实现写出如下测试方法。

1
2
3
4
5
6
System.out.println("==================JAVA8==================");
MessageBoard messageBoard = new MessageBoard();
User user = new User();
messageBoard.addObserver(user);
messageBoard.changeMsg("hello");
messageBoard.changeMsg("working");

运行结果如下所示。

1
2
3
4
5
==================JAVA8==================
MessageBoard changing msg...
Message board changed: hello
MessageBoard changing msg...
Message board changed: working

需要注意的是,Observer接口与Observable超类在Java9开始就被标志为过时方法,如下所示。

This class and the Observer interface have been deprecated. The event model supported by Observer and Observable is quite limited, the order of notifications delivered by Observable is unspecified, and state changes are not in one-for-one correspondence with notifications. For a richer event model, consider using the java.beans package. For reliable and ordered messaging among threads, consider using one of the concurrent data structures in the java.util.concurrent package. For reactive streams style programming, see the java.util.concurrent.Flow API.

取而代之的是官方建议使用的java.util.concurrent.Flow,基于Java9的响应式流「 Reactive Stream」。

而至于废弃Observer接口与Observable超类的原因主要如下所示。

They don’t provide a rich enough event model for applications. For example, they support only the notion that something has changed, but they don’t convey any information about what has changed. There are also some thread-safety and sequencing issues that cannot be fixed compatibly.

主要是因为它们存在的线程安全问题,以及在其发挥作用的同时,仅仅只能转达某某被观察对象发生了变化,而并不能转达其发生了什么变化

18.4 优缺点

  • 优点

    • 观察者与被观察者之间是抽象耦合
    • 建立了一套触发机制

    缺点

    • 一个被观察者,多个观察者的情况下,会导致调试复杂
    • 一个观察者的通知卡壳会波及后续观察者的消息通知

19. 中介者模式(Mediator)

19.1 作用

Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from reffering to each other explicitly, and it lets you vary their interaction independently.

用一个中介对象封装一系列的对象交互,中介者使各对象不需要显式地相互作用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

集中相关对象之间复杂的沟通和控制方式,下图为中介者模式的通用类图。

中介者模式

19.2 实现代码

以下代码实现的场景是,等待闹钟事件触发从而使中介者调用闹钟响铃方法并按顺序执行操作咖啡壶事件。

a. Colleague

1
2
3
public abstract class Colleague {
public abstract void onEvent(Mediator mediator);
}

b. Alarm

1
2
3
4
5
6
7
8
9
10
public class Alarm extends Colleague{
@Override
public void onEvent(Mediator mediator) {
mediator.doEvent("alarm");
}

public void doAlarm() {
System.out.println("doAlarm()");
}
}

c. CoffeePot

1
2
3
4
5
6
7
8
9
10
public class CoffeePot extends Colleague{
@Override
public void onEvent(Mediator mediator) {
mediator.doEvent("coffeePot");
}

public void doCoffeePot() {
System.out.println("doCoffeePot()");
}
}

d. Mediator

1
2
3
public abstract class Mediator {
public abstract void doEvent(String eventType);
}

e. ConcreteMediator

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
public class ConcreteMediator extends Mediator{
private Alarm alarm;
private CoffeePot coffeePot;

public ConcreteMediator(Alarm alarm, CoffeePot coffeePot) {
this.alarm = alarm;
this.coffeePot = coffeePot;
}

@Override
public void doEvent(String eventType) {
switch (eventType) {
case "alarm":
doAlarmEvent();
break;
case "coffeePot":
doCoffeePotEvent();
break;
default:
break;
}
}

private void doAlarmEvent() {
alarm.doAlarm();
coffeePot.doCoffeePot();
}

private void doCoffeePotEvent() {
// ...
}
}

f. Client

1
2
3
4
5
Alarm alarm = new Alarm();
CoffeePot coffeePot = new CoffeePot();
ConcreteMediator concreteMediator = new ConcreteMediator(alarm, coffeePot);
// 等待闹钟事件触发,从而调用中介者通过其操作相关的对象
alarm.onEvent(concreteMediator);

上述代码运行结果如下所示。

1
2
doAlarm()
doCoffeePot()

19.3 优缺点

  • 优点

其中介者模式的优点就是减少了类与类之间的相互依赖,把原有的一对多依赖变成了一对一的依赖,降低了类之间的耦合。

  • 缺点

中介者模式的缺点就是其中介者类会膨胀得较大,且逻辑较为复杂,处理类越多,逻辑就越复杂。

20. 备忘录模式(Memento)

Without violating encapsulation, capture and externalize an object’s internal state so that the object can be restored to this state later.

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

20.1 作用

备忘录模式「Memento Pattern」就是一个对象的备份模式,提供了一种程序数据的备份方法。在不违反封装的情况下获得对象的内部状态,从而在需要时可以将对象恢复到最初的状态。其通用类图如下所示。

备忘录模式

  • Originator 发起人角色

    记录当前时刻的内部状态,负责定义哪些属于备份状态范围的状态,负责创建和恢复备忘录数据。

  • Memento 备忘录角色

    负责存储Originator发起人对象的内部状态,在需要的时候提供发起人需要的内部状态。

  • Caretaker 备忘录管理员角色

    对备忘录进行管理、保存和提供备忘录。

20.2 实现代码

a. Memento

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Memento {
private String state = "";

public Memento(String state) {
this.state = state;
}

public String getState() {
return state;
}

public void setState(String state) {
this.state = state;
}
}

b. Originator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Originator {
private String state = "";

public String getState() {
return state;
}

public void setState(String state) {
this.state = state;
}

public Memento createMemento() {
return new Memento(this.state);
}

public void restoreMemento(Memento memento) {
this.setState(memento.getState());
}
}

c. Caretaker

1
2
3
4
5
6
7
8
9
10
11
public class Caretaker {
private Memento memento;

public Memento getMemento() {
return memento;
}

public void setMemento(Memento memento) {
this.memento = memento;
}
}

d. Client

1
2
3
4
5
6
7
8
// 定义发起人
Originator originator = new Originator();
// 定义备忘录管理员
Caretaker caretaker = new Caretaker();
// 创建一个备忘录
caretaker.setMemento(originator.createMemento());
// 恢复一个备忘录
originator.restoreMemento(caretaker.getMemento());

21. 解释器模式(Interpreter)

Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.

给定一门语言,定义它的文法的一种表示,并定义一个解释器,改解释器使用该表示来解释语言中的句子。

21.1 作用

解释器模式「Interpreter Pattern」为语言创建解释器,通常由语言的语法和语义分析来定义。

具体通用类图如下所示。

解释器模式

21.2 实现代码

以下是一个规则检验器实现,具有and和or规则,通过规则可以构建一颗解析树,用来检验一个文本是否满足解析树定义的规则。

例如一颗解析树为D and (A or (B and C)),文本“D and A”满足该解析树定义的规则。

a. Expression

1
2
3
public abstract class Expression {
public abstract boolean interpret(String str);
}

b. TerminalExpression

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class TerminalExpression extends Expression{
private String literal = null;

public TerminalExpression(String literal) {
this.literal = literal;
}

@Override
public boolean interpret(String str) {
StringTokenizer st = new StringTokenizer(str);
while(st.hasMoreTokens()) {
String test = st.nextToken();
if(test != null && test.equals(literal)) {
return true;
}
}
return false;
}
}

c. AndExpression

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class AndExpression extends Expression{
private Expression expression1;
private Expression expression2;

public AndExpression(Expression expression1, Expression expression2) {
this.expression1 = expression1;
this.expression2 = expression2;
}

@Override
public boolean interpret(String str) {
return expression1.interpret(str) && expression2.interpret(str);
}
}

d. OrExpression

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class OrExpression extends Expression{
private Expression expression1;
private Expression expression2;

public OrExpression(Expression expression1, Expression expression2) {
this.expression1 = expression1;
this.expression2 = expression2;
}

@Override
public boolean interpret(String str) {
return expression1.interpret(str) || expression2.interpret(str);
}
}

e. Client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Client {
public static Expression buildInterpreterTree() {
Expression terminal1 = new TerminalExpression("A");
Expression terminal2 = new TerminalExpression("B");
Expression terminal3 = new TerminalExpression("C");
Expression terminal4 = new TerminalExpression("D");
// B C
Expression alternation1 = new OrExpression(terminal2, terminal3);
// A or (B C)
Expression alternation2 = new OrExpression(terminal1, alternation1);
// D and (A or (B C))
return new AndExpression(terminal4, alternation2);
}


public static void main(String[] args) {
Expression define = buildInterpreterTree();
System.out.println(define.interpret("D A"));
System.out.println(define.interpret("A B"));
}
}

运行结果如下所示。

1
2
true
false

21.3 优缺点

  • 优点

    最显著的优点就是扩展性,修改语法规则只需要修改相应的非终结符表达式,若扩展语法,则只需要增加非终结符类。

  • 缺点

    • 解释器模式会引起类膨胀
    • 解释器模式采用递归调用方法
    • 效率问题

22. 状态模式(State)

Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.

当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。

22.1 作用

允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它所属的类。状态模式的核心是封装,状态的变更引起了行为的变更,从外部看起来就好像对象对应的类发生了改变。

通用类图如下所示。

状态模式

22.2 实现代码

下述代码实现了电梯运行状态的限制。

a. LiftState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public abstract class LiftState {
protected Context context;

public void setContext(Context context) {
this.context = context;
}

public abstract void open();

public abstract void close();

public abstract void run();

public abstract void stop();
}

b. Context

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
public class Context {
public static final OpeningState openingState = new OpeningState();
public static final ClosingState closingState = new ClosingState();
public static final RunningState runningState = new RunningState();
public static final StoppingState stoppingState = new StoppingState();
private LiftState liftState;

public LiftState getLiftState() {
return liftState;
}

public void setLiftState(LiftState liftState) {
this.liftState = liftState;
this.liftState.setContext(this);
}

public void open() {
this.liftState.open();
}

public void close() {
this.liftState.close();
}

public void stop() {
this.liftState.stop();
}

public void run() {
this.liftState.run();
}
}

c. ClosingState

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 ClosingState extends LiftState{
@Override
public void open() {
super.context.setLiftState(Context.openingState);
super.context.getLiftState().open();
}

@Override
public void close() {
System.out.println("电梯门关闭...");
}

@Override
public void run() {
super.context.setLiftState(Context.runningState);
super.context.getLiftState().run();
}

@Override
public void stop() {
super.context.setLiftState(Context.stoppingState);
super.context.getLiftState().stop();
}
}

d. OpeningState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class OpeningState extends LiftState{
@Override
public void open() {
System.out.println("电梯门打开...");
}

@Override
public void close() {
super.context.setLiftState(Context.closingState);
super.context.getLiftState().close();
}

@Override
public void run() {
System.out.println("电梯门开着无法运行...");
}

@Override
public void stop() {

}
}

e. RunningState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class RunningState extends LiftState{
@Override
public void open() {

}

@Override
public void close() {

}

@Override
public void run() {
System.out.println("电梯运行...");
}

@Override
public void stop() {
super.context.setLiftState(Context.stoppingState);
super.context.getLiftState().stop();
}
}

f. StoppingState

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 StoppingState extends LiftState{
@Override
public void open() {
super.context.setLiftState(Context.openingState);
super.context.getLiftState().open();
}

@Override
public void close() {
super.context.setLiftState(Context.closingState);
super.context.getLiftState().close();
}

@Override
public void run() {
super.context.setLiftState(Context.runningState);
super.context.getLiftState().run();
}

@Override
public void stop() {
System.out.println("电梯停止了...");
}
}

g. Client

1
2
3
4
5
6
7
8
9
10
11
public class Client {
public static void main(String[] args) {
Context context = new Context();
context.setLiftState(Context.closingState);
context.open();
context.run();
context.close();
context.run();
context.stop();
}
}

运行结果如下所示。

1
2
3
4
5
电梯门打开...
电梯门开着无法运行...
电梯门关闭...
电梯运行...
电梯停止了...

22.3 优缺点

  • 优点

    • 结构清晰,避免了过多的if..else和switch语句
    • 遵循设计原则,很好地体现了开闭原则和单一职责原则
    • 封装性很好
  • 缺点

    子类过多,容易导致类膨胀

23. 策略模式(Strategy)

23.1 作用

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

定义一组算法,将每个算法都封装起来,并且使他们之间可以互换,策略模式可以让算法独立于使用它的客户端。其通用类图如下所示。

策略模式

23.2 实现代码

a. QuackBehavior

1
2
3
public interface QuackBehavior {
void quack();
}

b. Quack

1
2
3
4
5
6
public class Quack implements QuackBehavior{
@Override
public void quack() {
System.out.println("quack!");
}
}

c. Squeak

1
2
3
4
5
6
public class Squeak implements QuackBehavior{
@Override
public void quack() {
System.out.println("squeak!");
}
}

d. Duck

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Duck {
private QuackBehavior quackBehavior;

public void performQuack() {
if(quackBehavior != null) {
quackBehavior.quack();
}
}

public void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}
}

e. Client

1
2
3
4
5
Duck duck = new Duck();
duck.setQuackBehavior(new Squeak());
duck.performQuack();
duck.setQuackBehavior(new Quack());
duck.performQuack();

运行结果如下所示。

1
2
squeak!
quack!

23.3 优缺点

  • 优点

    • 提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为
    • 提供了管理相关的算法族的办法
    • 提供了可以替换继承关系的办法
    • 可以避免使用多重条件转移语句
  • 缺点

    • 策略类数量增多,每一个策略都是一个类,复用的可能性很小,类数量增多
    • 所有的策略类都需要对外暴露

24. 责任链模式(Chain Of Responsibility)

Avoid coupling the sender of a request its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.

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

24.1 作用

责任链模式「Chain Of Responsibility Pattern」的重点是在链上,由一条链去处理相似的请求在链中决定谁来处理这个请求,从而避免请求的发送者和接收者之间的耦合关系,直到由对象处理它为止。

其通用类图如图所示。

责任链模式

24.2 实现代码

a. Handler

1
2
3
4
5
6
7
8
9
public abstract class Handler {
protected Handler successor;

public Handler(Handler successor) {
this.successor = successor;
}

protected abstract void handleRequest(Request request);
}

b. ConcreteHandler1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ConcreteHandler1 extends Handler {
public ConcreteHandler1(Handler successor) {
super(successor);
}

@Override
protected void handleRequest(Request request) {
if(request.getType() == RequestType.TYPE1) {
System.out.println(request.getName() + " is handle by " + this.getClass().getName());
return;
}
if(successor != null) {
successor.handleRequest(request);
}
}
}

c. ConcreteHandler2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ConcreteHandler2 extends Handler{
public ConcreteHandler2(Handler successor) {
super(successor);
}

@Override
protected void handleRequest(Request request) {
if(request.getType() == RequestType.TYPE2) {
System.out.println(request.getName() + "is handle by " + this.getClass().getName());
return;
}
if(successor != null) {
successor.handleRequest(request);
}
}
}

d. RequestType

1
2
3
public enum RequestType {
TYPE1, TYPE2
}

e. Request

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Request {
private RequestType type;
private String name;

public Request(RequestType type, String name) {
this.type = type;
this.name = name;
}

public RequestType getType() {
return type;
}

public String getName() {
return name;
}
}

f. Client

1
2
3
4
5
6
7
ConcreteHandler1 concreteHandler1 = new ConcreteHandler1(null);
ConcreteHandler2 concreteHandler2 = new ConcreteHandler2(concreteHandler1);

Request request1 = new Request(RequestType.TYPE1, "request1");
concreteHandler2.handleRequest(request1);
Request request2 = new Request(RequestType.TYPE2, "request2");
concreteHandler2.handleRequest(request2);

运行结果如下所示。

1
2
request1 is handle by chainofresponsibility.ConcreteHandler1
request2is handle by chainofresponsibility.ConcreteHandler2

24.3 优缺点

  • 优点

    责任链模式的优点就是将请求和处理分开,请求者无需知道是谁处理的,处理者也无需知道请求的全貌,两者解耦,提高了系统的灵活性。

  • 缺点

    • 性能问题,每个请求都是从链头遍历到尾部,时间复杂度是O(n)
    • 调试不方便

25. 访问者模式(Vistor)

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.

封装一些作用用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的心的操作。

25.1 作用

为一个对象结构(比如组合结构)增加新能力。

其通用UML类图如下所示。

访问者模式

25.2 实现代码

以下代码实现的是通过访问者模式「Vistor Pattern」打印出公司下属的职员信息。

a. IVisitor

1
2
3
4
5
public interface IVisitor {
void visit(CommonEmployee commonEmployee);

void visit(Manager manager);
}

b. Visitor

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 Visitor implements IVisitor{
@Override
public void visit(CommonEmployee commonEmployee) {
System.out.println(this.getCommonEmployeeInfo(commonEmployee));
}

@Override
public void visit(Manager manager) {
System.out.println(this.getManagerInfo(manager));
}


private String getBaseInfo(Employee employee) {
return "姓名:" + employee.getName() + "\n"
+ "薪水:" + employee.getSalary() + "\n";
}

private String getManagerInfo(Manager manager) {
return getBaseInfo(manager) + "业绩:" + manager.getPerformance() + "\n";
}

private String getCommonEmployeeInfo(CommonEmployee employee) {
return getBaseInfo(employee) + "工作:" + employee.getJob() + "\n";
}
}

c. Employee

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public abstract class Employee {
private String name;
private int salary;

public String getName() {
return name;
}

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

public int getSalary() {
return salary;
}

public void setSalary(int salary) {
this.salary = salary;
}

public abstract void accept(IVisitor visitor);
}

d. CommonEmployee

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class CommonEmployee extends Employee{
private String job;

public String getJob() {
return job;
}

public void setJob(String job) {
this.job = job;
}

@Override
public void accept(IVisitor visitor) {
visitor.visit(this);
}
}

e. Manager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Manager extends Employee{
private String performance;

public String getPerformance() {
return performance;
}

public void setPerformance(String performance) {
this.performance = performance;
}

@Override
public void accept(IVisitor visitor) {
visitor.visit(this);
}
}

f. Client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Client {
public static void main(String[] args) {
Visitor visitor = new Visitor();
mockEmployee().forEach(e -> e.accept(visitor));
}

public static List<Employee> mockEmployee() {
List<Employee> employees = new ArrayList<>();
CommonEmployee commonEmployee = new CommonEmployee();
commonEmployee.setName("老王");
commonEmployee.setJob("Java开发工程师");
commonEmployee.setSalary(20000);
employees.add(commonEmployee);
Manager manager = new Manager();
manager.setName("老张");
manager.setPerformance("Good");
manager.setSalary(30000);
employees.add(manager);
return employees;
}
}

25.3 优缺点

  • 优点

    • 符合单一职责原则
    • 优秀的扩展性
    • 灵活性非常高
  • 缺点

    • 具体元素对访问者公布细节
    • 具体元素变更比较困难
    • 违背了依赖倒置原则