设计模式

1. 综述

软件设计模式使人们可以更加简单方便复用成功的设计和体系结构

1. 分类

设计模式有两种分类方法,一种根据模式的目的来分;另一种根据模式的作用来分。

image-20201024215450877

2. 23种设计模式概述

  1. 单例(Singleton)模式

某个类只能生成一个实例,该类提供了一个全局访问点,供外部获取该实例,其拓展是有限多个实例。

  1. 原型(Prototype)模式

将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。

  1. 工厂方法(Factory Method)模式

定义一个用户创建产品的接口,有子类决定生产什么产品。

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

提供一个创建产品族的接口,其每个子类可以生产一些列相关的产品。

  1. 建造者(Builder)模式

将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。

  1. 代理(Proxy)模式

为某个对象提供一种代理以控制对对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特征。

  1. 适配器(Adapter)模式

将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。

  1. 桥接(Bridge)模式

将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低 抽象和实现这两个可变维度的耦合度。
9. 装饰(Decorator)模式

动态的给对象增加一些职责,即增加其额外的功能。
10. 外观(Facade)模式

为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。

  1. 享元(Flyweight)模式

运用共享技术来有效地支持大量细粒度对象的复用。

  1. 组合(Composite)模式

将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。

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

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

  1. 策略(Strategy)模式

定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响算法的客户。

  1. 命令(Command)模式

将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。

  1. 职责链(Chain of Responsibility)模式

把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这个方式去除对象之间的耦合。

  1. 状态(State)模式

允许一个对象在其内部状态发生改变时改变其行为能力。

  1. 观察者(Observer)模式

多个对象间存在一对多的关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其它对象的行为。

  1. 中介者(Mediator)模式

定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象的耦合度,使原有对象之间不必户互了解。

  1. 迭代器(Iterator)模式

提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。

  1. 访问者(Visitor)模式

在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象。

  1. 备忘录(Memento)模式

在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后回复它。

  1. 解释器(Interpreter)

提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。

2. 创建型设计模式

1. 单例模式

在有些系统中,为了节省内存资源、保证数据内容的一致性,对某些类要求只能创建一个实例,这就是所谓的单例模式。

在计算机系统中,还有 Windows 的回收站、操作系统中的文件系统、多线程中的线程池、显卡的驱动程序对象、打印机的后台处理服务、数据库的连接池、网站的计数器、Web 应用的配置对象、应用程序中的对话框、系统中的缓存等常常被设计成单例。

单例模式有以下3个特点:

  1. 单例类只有一个对象;

  2. 该单例对象必须由单例类自行创建;

  3. 单例类对外提供一个访问该单例的全局访问点。

1.1 单例模式的结构与实现

通常,普通类的结构函数是公有的,外部类可以通过“new 构造函数()”来生成多个实例。但是,如果将类的构造函数设为私有的,外部类就无法通过调用该类的构造函数,也就无法生成多个实例。这是该类自身必须定义一个静态私有实例,并向外提供一个讲台的公有函数用于创建或获取静态私有实例。

下面分析单例模式的实现。

单例模式有懒汉式和饿汉式两种实现形式。

1.1.1 懒汉式

该模式的特点是类加载时没有生成单例,只有当第一次调用 getInstance() 方法才去创建单例。代码如下:

1
2
3
4
5
6
7
8
9
10
11
public class LazySingleton {
private static volatile LazySingleton instance = null; //保证 instance 在所有线程中同步
private LazySingleton() {} //private 避免类在外部被实例化
public static synchronized LazySingleton getInstance() {
// getInstance 方法前加同步
if(instance == null) {
instance = new LazySingleton();
}
return instance;
}
}

注意:如果编写的是多线程程序,则不要删除上例代码中的关键字 volatile 和 synchronized,否则将存在线程非安全的问题。如果不删除这两个关键字就能保证线程安全,但是每次访问时都要同步,会影响性能,且消耗更多的资源,这是懒汉式单例的缺点。

1.1.2 饿汉式

该模式的特点是类一旦加载就创建一个单例,保证在调用 getInstance 方法之前单例已经存在了。

1
2
3
4
5
6
7
public class HungrySingleton {
private static final HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance() {
return instance;
}
}

饿汉式单例在类 创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,以后线程安全的,可以直接用于多线程而不会出现问题。

1.2 单例模式的应用场景

  • 在应用场景中,某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
  • 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。
  • 当某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。

1.3 单例模式的拓展

单例模式可扩展为有效的多例(Multiple)模式,这种模式可以生成有限个实例并保存在 ArrayList 中,客户需要时可随机获取,其结构如图2-1所示。

image-20201024221526320

2. 原型模式

在有些系统中,存在大量相同或相似对象的创建问题,如果用传统的构造函方法创建对象,会比较复杂且耗时耗资源,用原型模式生成对象就很高效,就像孙悟空拔下猴毛轻轻一吹就变出很多孙悟空一样简单。

用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。这种方式创建对象非常高效,根本无需指定对象创建的细节。例如,Windows 操作系统的安装通常比较耗时,如果复制就快了很多。

2.1 原型模式的结构与实现

由于 Java 提供了对象的 clone() 方法,所以用 Java 实现原型模式很简单。

2.1.1 模式的结构

原型模式包含以下主要角色:

  • 抽象原型类:规定了具体原型对象必须实现的接口。java中为Cloneable()接口

  • 具体实现类:实现抽象原型类的 clone() 方法,它使可被复制的对象。

  • 访问类:使用具体原型类中的 clone() 方法来复制新对象。

2.1.2 模式的实现

原型模式的克隆分为浅克隆和深克隆,Java 中的 Object 类提供了浅克隆的 clone() 方法,具体原型类只要实现 Cloneable 接口就可实现对象的浅克隆,这里的 Cloneable 接口就是抽象原型类。

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 Realizetype implements Cloneable {
Realizetype() {
System.out.println("具体原型创建成功!");
}
public Object clone() throws CloneNotSupportedException {
System.out.println("具体原型复制成功!");
return (Realizetype)super.clone();
}
}

// 原型模式的测试类
public class PrototypeTest {
public static void main(String[] args)throws CloneNotSupportedException {
Realizetype obj1=new Realizetype();
Realizetype obj2=(Realizetype)obj1.clone(); // clone返回Object类,要强转
System.out.println("obj1==obj2?"+(obj1==obj2));
}
}

// output
// 具体原型创建成功!
// 具体原型复制成功!
// obj1==obj2?false
2.1.3 原型模式的应用实例

用原型模式除了可以生成相同的对象,还可以生成相似的对象,如以下实例:

分析:同一学校的“三好学生”奖状除了获奖人姓名不同,其他都相同,属于相似对象的复制,同样可以用原型模式创建,然后再做简单修改就可以了。图 4 所示是三好学生奖状生成器的结构图。

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
// 奖状类
class citation implements Cloneable {
String name;
String info;
String college;
citation(String name,String info,String college) {
this.name = name;
this.info = info;
this.college = college;
System.out.println("奖状创建成功!");
}
void setName(String name) {
this.name = name;
}
String getName() {
return(this.name);
}
void display() {
System.out.println(name+info+college);
}
public Object clone() throws CloneNotSupportedException {
System.out.println("奖状拷贝成功!");
return (citation)super.clone();
}
}

public class ProtoTypeCitation {
public static void main(String[] args) throws CloneNotSupportedException {
citation obj1 = new citation("张三","同学:在2016学年第一学期中表现优秀,被评为三好学生。","韶关学院");
obj1.display();
citation obj2 = (citation) obj1.clone();
obj2.setName("李四");
obj2.display();
}
}

// output
// 奖状创建成功!
// 张三同学:在2016学年第一学期中表现优秀,被评为三好学生。韶关学院
// 奖状拷贝成功!
// 李四同学:在2016学年第一学期中表现优秀,被评为三好学生。韶关学院
2.1.5 原型模式应用场景

原型模式通常适用于以下场景。

  • 对象之间相同或相似,即只是个别的几个属性不同的时候,
  • 对象的创建过程比较麻烦,但复制比较简单的时候。
2.1.6 原型模式的拓展

原型模式可扩展为带原型管理器的原型模式,它在原型模式的基础上增加了一个原型管理器 PrototypeManager 类。该类用 HashMap 保存多个复制的原型,Client 类可以通过管理器的 get(String id) 方法从中获取复制的原型。

image-20201025212553490

3. 工厂方法模式 // 仅针对一类产品

定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。这满足创建型模式中所要求的“创建与使用相分离”的特点。

我们把被创建的对象成为“产品”,把创建产品的对象成为“工厂”。如果要创建的产品不多,只要一个工厂类就可以完成,这种模式叫“简单工厂模式”,它不属于23种经典设计模式,它的缺点是增加新产品时会违背“开闭原则”。

本节介绍的“工厂方法模式”是对简单工厂模式的进一步抽象化,其好处是可以是系统自不修改原来代码的情况下引进新的产品,即满足开闭原则。

工厂方法模式的主要优点有:

  • 用户只需要指导具体工厂的名称就可以得到所需要的产品,无需知道产品的具体创建过程;
  • 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无需对原工厂进行任何修改,满足开闭原则。

缺点是:

  • 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。

3.1 工厂方法模式的结构与实现

工厂方法模式由抽象工厂、具体工厂、抽象产品和具体产品等 4 个要素构成。

3.1.1 模式结构

工厂方法模式的主要角色如下:

  1. 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品;

  2. 具体工厂(Concrete Factory):主要是实现抽象工厂中的抽象方法,具体产品的创建;

  3. 抽象产品(Product):定义了产品的规范,描述了产品的主要特征和功能;

  4. 具体产品(Concrete Product):实现了抽象产品角色所定义的接口,有具体工厂来创建,它同具体工厂之间一一对应。

image-20201026215042251

3.1.2 模式的实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 抽象工厂:提供了厂品的生成方法
interface AbstractFactory {
public Product newProduct();
}

//具体工厂:实现了厂品的生成方法
class ConcreteFactory1 implements AbstractFactory {
public Product newProduct() {
System.out.println("具体工厂1生成-->具体产品1...");
return new ConcreteProduct1();
}
}

// 抽象产品:提供了产品的接口
interface Product {
public void show();
}

//具体产品:实现抽象产品中的抽象方法
class ConcreteProduct1 implements Product {
public void show() {
System.out.println("具体产品1显示...");
}
}

3.2 模式应用场景

工厂方法模式通常适用于以下场景。

  • 客户只知道创建产品的工厂名,而不知道具体的产品名。如 TCL 电视工厂、海信电视工厂等。
  • 创建对象的任务由多个具体工厂中的某一个完成,而抽象工厂只提供创建产品的接口。
  • 客户不关心创建产品的细节,只关心产品的品牌。

3.3 模式的拓展

image-20201026220150193