声明:文章内容绝大多数来自于南京大学 计算机科学与技术系 面向对象设计方法 课程课件

创建型模式

  • 创建模式是对类的实例化过程的抽象化。
    • 怎样创建对象?
    • 创建哪些对象?
    • 如何组合和表示这些对象?
  • 创建模式描述了怎样构造和封装这些动态决定

Simply Factory 简单工厂

又名Static Factory Method 静态工厂方法

  • 由一个工会参股地向决定创建出哪一种产品类的实例。
  • 负责将大量有共同接口的类实例化。

image-20220607172320786

  • 具体产品类:将需要创建的各种不同产品对象的相关代码封装到具体产品类中
  • 抽象产品类:将具体产品类公共的代码进行抽象和提取后封装在一个抽象产品类中
  • 工厂类:提供一个工厂类用于创建各种产品,在工厂类中提供一个创建产品的工厂方法,该方法可以根据所传入参数的不同创建不同的具体产品对象
  • 客户端:只需调用工厂类的工厂方法并传入相应的参数即可得到一个产品对象

简单工厂模式:定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类

在简单工厂模式中用于创建实例的方法通常是静态(static)方法,因此又被称为静态工厂方法(StaticFactory Method)模式

  • 要点:如果需要什么,只需要传入一个正确的参数,就可以获取所需要的对象,而无须知道其创建细节
  • 将对象的创建于使用分离的其他好处:
    • 防止用来实例化一个类的数据和代码在多个类中到处都是,可以将有关创建的知识搬移到一个工厂类中,解决代码重复、创建蔓延的问题
    • 有利于增加创建代码的可修改性
    • 构造函数的名字都与类名相同,从构造函数和参数列表中大家很难了解不同构造函数所构造的产品的差异à将对象的创建过程封装在工厂类中,可以提供一系列名字完全不同的工厂方法,每一个工厂方法对应一个构造函数,客户端可以以一种更加可读、易懂的方式来创建对象

如果一个类很简单,并且不存在太多变化(没有高可修改性的需求)且构造过程很简单,就无需为它提供工厂类,直接在使用之前实例化即可。

模式优点

  • 实现了对象创建和使用的分离
  • 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可
  • 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性

模式缺点

  • 工厂类集中了所有产品的创建逻辑,职责过重,一旦不能正常工作,整个系统都要受到影响
  • 增加系统中类的个数(引入了新的工厂类),增加了系统的复杂度和理解难度
  • 系统扩展困难,一旦添加新产品不得不修改工厂逻辑
  • 由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构,工厂类不能得到很好地扩展

适用环境

  • 工厂类负责创建的对象比较少,由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂
  • 客户端只知道传入工厂类的参数,对于如何创建对象并不关心

Factory Method 工厂方法

又名virtual constructor 虚构造器

定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。

image-20220607222138699

  • 工厂方法模式
    • 不再提供一个工厂类来统一负责所有产品的创建,而是将具体对象的创建过程交给专门的工厂子类去完成
    • 如果出现新的类型,只需要为这种新类型定义一个具体的工厂类就可以创建该新类型的实例。

工厂方法模式:定义一个用于创建对象的接口,但是让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类

  • 工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象
  • 目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类

image-20220607222919777

反射机制

  • Java反射(Java Reflection)是指在程序运行时获取已知名称的类或已有对象的相关信息的一种机制,包括类的方法、属性、父类等信息,还包括实例的创建和实例类型的判断等

  • Class类的实例表示正在运行的Java应用程序中的类和接口,其forName(String className)方法可以返回与带有给定字符串名的类或接口相关联的Class对象,再通过Constructor类获取此对象的所表示的类的构造方法。然后通过获得的构造方法,创建一个此对象所表示的类的一个新实例,即通过一个类名字符串得到类的实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Class c = null;
try {
c = Class.forName("com.nju.edu.erp.model.po.ProductPO");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Constructor<ProductPO> constructor = null;
try {
constructor = c.getConstructor();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
try {
assert constructor != null;
ProductPO po = constructor.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
  • 增加一个新产品
    • (1) 增加一个新的具体产品类作为抽象产品类的子类
    • (2) 增加一个新的具体工厂类作为抽象工厂类的子类,该工厂用于创建新增的具体产品对象
    • (3) 修改配置文件,用新的具体工厂类的类名字符串替换原有工厂类类名字符串
    • (4) 编译新增具体产品类和具体工厂类,运行客户端代码,即可完成新产品的增加和使用

模式优点

  • 工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节
  • 能够让工厂自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部
  • 在系统中加入新产品时,完全符合开闭原则

模式缺点

  • 系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,会给系统带来一些额外的开销
  • 增加了系统的抽象性和理解难度

适用环境

  • 客户端不知道它所需要的对象的类(客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体产品对象由具体工厂类创建)

  • 抽象工厂类通过其子类来指定创建哪个对象

Abstract Factory 抽象工厂

  • 一个工厂可以生产一系列产品(一族产品),极大减少了工厂类的数量

    • 产品等级结构:产品等级结构即产品的继承结构

    • 产品族:产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品

image-20220608102500795

  • 当系统所提供的工厂生产的具体产品并不是一个简单的对象,而是多个位于不同产品等级结构、属于不同类型的具体产品时就可以使用抽象工厂模式
  • 抽象工厂模式是所有形式的工厂模式中最为抽象和最具一般性的一种形式

image-20220608102628612

  • 当一个工厂等级结构可以创建出分属于不同产品等级结构的一个产品族中的所有对象时,抽象工厂模式比工厂方法模式更为简单、更有效率

image-20220608102757605

image-20220608102842844

  • 增加产品族时,抽象工厂模式很好的支持了开闭原则,只需要增加具体产品并对应增加一个新的具体工厂。
  • 增加新的产品等级结构时,违背了开闭原则,需要修改所有的工厂角色并在其中添加新的产品生产方法。

模式优点

  • 隔离了具体类的生成,使得客户端并不需要知道什么被创建
  • 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象
  • 增加新的产品族很方便,无须修改已有系统,符合开闭原则

模式缺点

  • 增加新的产品等级结构麻烦,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大的不便,违背了开闭原则

适用环境

  • 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节
  • 系统中有多于一个的产品族,但每次只使用其中某一产品族
  • 属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来
  • 产品等级结构稳定,在设计完成之后不会向系统中增加新的产品等级结构或者删除已有的产品等级结构

Builder 构造者模式

创建者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

  • 将客户端与包含多个部件的复杂对象的创建过程分离,客户端无需知道复杂对象的内部组成方式与装配方式,只需要知道所需建造者的类型即可。
  • 关注如何逐步创建一个复杂的对象,不同的建造者定义了不同的创建过程

image-20220608103706579

可拥有多个bulider并调用自己需要的建造者。

  • 如果需要更换具体角色建造者,只需要修改配置文件
  • 当需要增加新的具体角色建造者时,只需将新增具体角色建造者作为抽象角色建造者的子类,然后修改配置文件即可,原有代码无须修改,完全符合开闭原则

模式优点

  • 客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
  • 每一个具体建造者都相对独立,与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,扩展方便,符合开闭原则
  • 可以更加精细地控制产品的创建过程

模式缺点

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

适用环境

  • 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员变量
  • 需要生成的产品对象的属性相互依赖,需要指定其生成顺序
  • 对象的创建过程独立于创建该对象的类。在建造者模式中通过引入了指挥者类,将创建过程封装在指挥者类中,而不在建造者类和客户类中
  • 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品

与Abstract Factory的区别:

  • Builder着重于一步步构造一个复杂对象,而Abstract Factory着重于多个系列的产品对象;
  • Builder在最后的一步返回产品,而Abstract Factory中产品是立即返回的。

Prototype 原型模式

原型模式:使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象。

  • 工作原理:将一个原型对象传给要发动创建的对象(即客户端对象),这个要发动创建的对象通过请求原型对象制自己来实现创建过程
  • 创建新对象(也称为克隆对象)的工厂就是原型类自身,工厂方法由负责复制原型对象的克隆方法来实现
  • 通过克隆方法所创建的对象是全新的对象,它们在内存中拥有新的地址,每一个克隆对象都是独立的
  • 通过不同的方式对克隆对象进行修改以后,可以得到一系列相似但不完全相同的对象

image-20220608105224093

深克隆和浅克隆

  • 浅克隆(Shallow Clone):当原型对象被复制时,只复制它本身和其中包含的值类型的成员变量,而引用类型的成员变量并没有复制

image-20220608105437021

  • 深克隆(Deep Clone):除了对象本身被复制外,对象所包含的所有成员变量也将被复制

image-20220608105506056

实现拷贝的方式

继承Cloneable类重写clone()方法

  • 浅拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Data
@Builder
public class Address implements Cloneable{
String street1;
String city;
String province;
String country;
String postcode;

@Override
public Object clone() {
try {
System.out.println("克隆成功");
return super.clone();
} catch (CloneNotSupportedException e) {
System.out.println("克隆失败");
e.printStackTrace();
return null;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Data
@Builder
public class Customer implements Cloneable{
String name;
int age;
boolean gender;
Address address;

@Override
public Object clone() {
try {
System.out.println("克隆成功");
return super.clone();
} catch (CloneNotSupportedException e) {
System.out.println("克隆失败");
e.printStackTrace();
return null;
}
}
}
  • 深拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Data
@Builder
public class Address implements Cloneable{
String street1;
String city;
String province;
String country;
String postcode;

@Override
public Object clone() {
try {
System.out.println("克隆成功");
return super.clone();
} catch (CloneNotSupportedException e) {
System.out.println("克隆失败");
e.printStackTrace();
return null;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Data
@Builder
public class Customer implements Cloneable{
String name;
int age;
boolean gender;
Address address;

@Override
public Object clone(){
try {
System.out.println("克隆成功");
Customer temp = (Customer) super.clone();
temp.address = (Address) address.clone();
return temp;
} catch (CloneNotSupportedException e) {
System.out.println("克隆失败");
e.printStackTrace();
return null;
}
}
}

需要手动调用其中引用对象的拷贝方法

序列化实现拷贝

  • 浅拷贝
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
@Data
@Builder
public class Address implements Serializable {
String street1;
String city;
String province;
String country;
String postcode;

@Override
public Object clone() {
// 序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(bos);
} catch (IOException e) {
e.printStackTrace();
}

try {
assert oos != null;
oos.writeObject(this);
} catch (IOException e) {
e.printStackTrace();
}

// 反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(bis);
} catch (IOException e) {
e.printStackTrace();
}

try {
assert ois != null;
try {
return ois.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}

原型管理器

**原型管理器(Prototype Manager)**:将多个原型对象存储在一个集合中供客户端使用,它是一个专门负责克隆对象的工厂,其中定义了一个集合用于存储原型对象,如果需要某个原型对象的一个克隆,可以通过复制集合中对应的原型对象来获得

image-20220608110031459

模式优点

  • 简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率
  • 扩展性较好
  • 提供了简化的创建结构,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品
  • 可以使用深克隆的方式保存对象的状态,以便在需要的时候使用,可辅助实现撤销操作

模式缺点

  • 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违背了开闭原则
  • 实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦

模式适用环境

  • 创建新对象成本较大,新对象可以通过复制已有对象来获得,如果是相似对象,则可以对其成员变量稍作修改
  • 系统要保存对象的状态,而对象的状态变化很小
  • 需要避免使用分层次的工厂类来创建分层次的对象
  • Ctrl + C -> Ctrl + V

Singleton 单例模式

单例模式:确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例

  • 某个类只能有一个实例
  • 必须自行创建这个实例
  • 必须自行向整个系统提供这个实例

单例模式的实现

  • 静态私有成员变量
  • 私有构造函数
  • 静态公有方法——返回唯一实例
1
2
3
4
5
6
7
8
9
10
11
public class Singleton {
private static final Singleton instance;

private Singleton() {
instance = new Singleton();
}

public static Singleton getInstance() {
return instance;
}
}

饿汉式单例

image-20220608162421900

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

懒汉式单例

image-20220608162600224

1
2
3
4
5
6
7
8
9
10
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() { }
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}

延迟加载(只有在需要访问这个实例的实例的时候,该实例才会被创建,有利于内存的高效利用)

但是!这样不线程安全!

如果多个线程同时调用getInstance方法,有概率造成多个实例被创建

双重检查锁定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class LazySingleton {
private volatile static LazySingleton instance = null;
private LazySingleton() { }
public static LazySingleton getInstance() {
//第一重判断
if (instance == null) {
//锁定代码块
synchronized (LazySingleton.class) {
//第二重判断
if (instance == null) {
instance = new LazySingleton(); //创建单例实例
}
}
}
return instance;
}
}

第一重:当调用方法时,锁住该类,使得getInstance()方法被锁定

第二重:为防止锁住该类时多线程的调用,需要在创建实例时进行第二次判断

饿汉式单例类与懒汉式单例类的比较

  • 饿汉式单例类:无须考虑多个线程同时访问的问题调用速度和反应时间优于懒汉式单例资源利用效率不及懒汉式单例;系统加载时间可能会比较长
  • 懒汉式单例类:实现了延迟加载;必须处理好多个线程同时访问的问题;需通过双重检查锁定等机制进行控制将导致系统性能受到一定影响

内部类模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Singleton {
private Singleton() {
}
//静态内部类
private static class HolderClass {
private final static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return HolderClass.instance;
}
public static void main(String args[]) {
Singleton s1, s2;
s1 = Singleton.getInstance();
s2 = Singleton.getInstance();
System.out.println(s1==s2);
}
}

静态内部类只有在被需要调用的时候初始化,此时静态对象instance真正被创建

模式优点

  • 提供了对唯一实例的受控访问
  • 可以节约系统资源,提高系统的性能
  • 允许可变数目的实例(多例类)

模式缺点

  • 扩展困难(缺少抽象层)
  • 单例类的职责过重、一定程度上违背单一职责原则
  • 由于自动垃圾回收机制,可能会导致共享的单例对象的状态丢失

模式适用环境

  • 系统只需要一个实例对象,或者因为资源消耗太大而只允许创建一个对象
  • 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例