原型模式

原型模式

一、什么是原型模式?

原型设计模式(Prototype Design Pattern),是指根据实例原型、实例模型来生成新的实例对象。

也就是用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。

重点在于新实例对象,不是创建出来的,而是基于已有的原型对象复制的。

二、为什么要用原型模式?

  • 对象之间相同或相似,即只是个别的几个属性不同的时候
  • 有些对象的创建过程较为复杂,而且有时候需要频繁创建,创建新对象还不如复制已有对象来得快
    • 对象中的数据需要经过复杂的计算才能得到(比如排序、计算哈希值)
    • 对象中的数据需要从 RPC、网络、数据库、文件系统等非常慢速的 IO 中读取
  • 想要复制当前的对象,但是对象内部的数据异常复杂,通过构造函数创建难以实现
    • 比如一个图形,之前做了很多处理,比一开始创建的时候变动了很多,此时想要复制它,通过构造函数新建是难以实现的
  • 想要让生成的实例的框架不依赖于具体的类
    • 框架中不能指定类名来生成实例,所以会先“注册”一个“原型实例”,然后通过复制该“原型”来生成新实例

三、模型的结构是怎么样的?

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

  1. 抽象原型类(Prototype):负责定义用于复制现有实例来生成新实例的方法
  2. 具体原型类(ConcretePrototype):负责实现复制现有实例并生成新实例的方法
  3. 访问类(Client):负责使用复制实例的方法生成新的实例

原型结构

四、怎么实现原型模式?

原型模式有两种实现方法:深拷贝和浅拷贝。

  • 浅拷贝只会复制对象中基本数据类型数据和引用对象的内存地址,不会递归地复制引用对象,以及引用对象的引用对象……
  • 深拷贝得到的是一份完完全全独立的对象。所以,深拷贝比起浅拷贝来说,更加耗时,更加耗内存空间

深拷贝和浅拷贝的一些注意事项:

  • 如果要拷贝的对象是不可变对象,浅拷贝共享不可变对象是没问题的
  • 但对于可变对象来说,浅拷贝得到的对象和原始对象会共享部分数据,就有可能出现数据被修改的风险,也就变得复杂多了
  • 没有充分的理由,不要为了一点点的性能提升而使用浅拷贝

示例程序的类结构图:

示例程序类结构图

其中 Product 代表的是原型结构中的 Propotype,UnderlinePen 和 MessageBox 代表的是原型结构中的 ConcretePrototype,而 ProductManager 代表的是原型结构中的 Client。

4.1 Product 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface Product extends Cloneable {

/**
* 产品使用的方法
* @param s
*/
void use(String s);

/**
* 产品克隆的方法
* @return 克隆出来的新对象
*/
Product createClone();
}

4.2 UnderlinePen 和 MessageBox 具现类

UnderlinePen 是为输出字符串在左右两端添加双引号,并在下边添加下划符号。代码如下:

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
/**
* 为输出字符串添加下划线,例如:
*
* "Strong message product"
* ~~~~~~~~~~~~~~~~~~~~~~~~
*
*/
public class UnderlinePen implements Product {

// 下划符号
private char ch;

public UnderlinePen(char ch) {
this.ch = ch;
}

/**
* 输出字符串左右添加双引号,下边添加下划符号
* @param s 输出字符串
*/
@Override
public void use(String s) {
int len = s.getBytes().length;
System.out.println("\"" + s + "\"");
for (int i = 0; i < len + 2; i++) {
System.out.print(ch);
}
System.out.println();
}

@Override
public Product createClone() {
Product p = null;
try {
p = (Product) clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return p;
}
}

MessageBox 是为输出字符串添加一个输出框。代码如下:

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
/***
* 为输出字符串添加输出框,例如:
*
* ***********************
* * Warning box product *
* ***********************
*
*/
public class MessageBox implements Product {

// 输出框符号
private char ch;

public MessageBox(char ch) {
this.ch = ch;
}

/**
* 为输出字符串添加一个输出框
* @param s 输出字符串
*/
@Override
public void use(String s) {
int len = s.getBytes().length;
for (int i = 0; i < len + 4; i++) {
System.out.print(ch);
}
System.out.println();
System.out.println(ch + " " + s + " " + ch);
for (int i = 0; i < len + 4; i++) {
System.out.print(ch);
}
System.out.println();
}

@Override
public Product createClone() {
Product p = null;
try {
p = (Product) clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return p;
}
}

4.3 ProductManager 访问类

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 ProductManager {

// 保存产品原型实例
private HashMap<String, Product> products = new HashMap<>();

/**
* 注册产品原型实例
* @param name 原型名称
* @param product 原型实例
*/
public synchronized void register(String name, Product product) {
products.put(name, product);
}

/**
* 通过原型名称获取产品新实例对象
* @param name 产品原型名称
* @return 产品新实例对象
*/
public Product getProduct(String name) {
Product product = products.get(name);
return product.createClone();
}

}

五、原型模式有什么优缺点?

原型模式的优点:

  • 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过一个已有实例可以提高新实例的创建效率
  • 原型模式提供了简化的创建结构,可以使用深克隆方式保存对象的状态,以便在需要的时候使用(例如恢复到历史某一状态)

原型模式的缺点:

  • 需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,必须修改其源代码,违背了“开闭原则”
  • 当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦
  • 深克隆、浅克隆需要运用得当

扩展

  • Java 中提供了用于复制实例的方法 clone()
  • clone() 方法定义在 java.lang.Object 中,也就是说,所有 Java 类都有 clone() 方法
  • clone() 方法内部所执行的是基于内存二进制流的复制,复制了一份和原型对象一样的内存数据
  • clone() 方法复制的只是对象的内存数据,所以 clone() 方法是浅复制
  • clone() 方法只会进行复制,不会调用实例的构造函数
  • 重写 clone() 方法时,记得调用父类的 super.clone() 方法
  • 类使用 clone() 方法,这个类必须实现 java.lang.Clonable 接口(自己实现或者父类实现都行)
  • Clonable 是一个标记接口,用于标记该类可以执行 clone() 方法,自身没有声明任何方法
作者

jiaduo

发布于

2022-01-15

更新于

2023-04-03

许可协议