设计模式(7):原型模式PROTOTYPE

原型模式的定义

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。


使用场景

当一个系统应该独立于它的产品创建、构成和表示时,并且
• 当要实例化的类是在运行时刻指定时,例如,通过动态装载;或者
• 为了避免创建一个与产品类层次平行的工厂类层次时;或者
• 当一个类的实例只能有少数几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。


结构

原型模式的结构如下
ShowImage

  • Protype

    – 声明一个克隆自身的接口。

  • ConcretePrototype

    – 实现能够克隆自身的操作。

  • Client

    – 通过请求Prototype去克隆自身完成创建一个新的对象的客户端。


协作

客户端请求原型克隆自身


效果

Prototype有许多和Abstract Factory和Builder一样的效果:它对客户隐藏了具体的产品类,因此减少了客户知道的名字的数目。此外,这些模式使客户无需改变即可使用与特定应用相关的类。
除此之外,Prototype模式还有以下优点:

  • 运行时刻增加和删除产品

    Prototype允许只通过客户注册原型实例就可以将一个新的具体产品类并入系统。它比其他创建型模式更为灵活,因为调用者可以在运行时刻建立和删除原型。

  • 改变值以指定新对象

    高度动态的系统允许你通过对象复合定义新的行为,例如,通过为一个对象变量指定值—并且不定义新的类。你通过实例化已有类并且将这些实例注册为客户对象的原型,就可以有效定义新类别的对象。客户可以将职责代理给原型,从而表现出新的行为。这种设计使得用户无需编程即可定义新“类”。实际上,克隆一个原型类似于实例化一个类。Prototype模式可以极大的减少系统所需要的类的数目。

  • 改变结构以指定新对象

    许多应用由部件和子部件来创建对象。举个例子,一个模块由多个子部件构成,可以通过重复地使用一个特定的子部件来实例化复杂的结构。

  • 减少子类的构造

    Factory Method经常产生一个与产品类层次平行的Creator类层次。Prototype模式使得你克隆一个原型而不是请求一个工厂方法去产生一个新的对象。因此你根本不需要Creator类层次。

  • 用类动态配置应用

    允许一些运行时刻环境将类动态装载到应用中。


原型模式实现(Implement)

案例

学校的实验室里面有一台打印机Printer,打印机可以打印各种文件File,例如文档Document,论文Paper,报告Report等,但是由于文件的格式是固定的,因此每次去重新写十分麻烦,能不能保存一个模板呢?这里就可以用原型模式来实现。

结构

结构图如下
ShowImage

代码实现

Printer

1
2
3
public interface Printer {
FilePrototype printFileTemplate(String type) throws CloneNotSupportedException;
}

ConcretePrinter

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
public class ConcretePrinter implements Printer {
/**
* 使用单例构造ConcretePrinterFactory
* Use Singleton to construct ConcretePrinter
*/
private ConcretePrinter() {
}
public static class ConcretePrinterFactoryHolder {
private static final ConcretePrinter INSTANCE = new ConcretePrinter();
}
public static ConcretePrinter getInstance() {
return ConcretePrinterFactoryHolder.INSTANCE;
}
private static Map<String, FilePrototype> filePrototypeMap = new HashMap();
static {
filePrototypeMap.put(ModelType.DOCUMENT, new Document());
filePrototypeMap.put(ModelType.PAPER, new Paper());
filePrototypeMap.put(ModelType.REPORT, new Report());
}
@Override
public FilePrototype printFileTemplate(String type) throws CloneNotSupportedException {
return filePrototypeMap.get(type).clone();
}
}

FilePrototype

1
2
3
public interface FilePrototype extends Cloneable {
FilePrototype clone() throws CloneNotSupportedException;
}

Report类,其他实体类的代码在这里省略

1
2
3
4
5
6
7
8
9
10
11
public class Report implements FilePrototype {
@Override
public Report clone() throws CloneNotSupportedException {
System.out.println("Cloning Report Object...");
return (Report) super.clone();
}
public String getInfo() {
return "Report Template...";
}
}

Client调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void prototypeDemoReportTest() throws CloneNotSupportedException {
Printer academicPrinter = ConcretePrinter.getInstance();
Report report1 = (Report) academicPrinter.printFileTemplate("report");
System.out.println(report1.getInfo());
System.out.println(report1.toString());
Report report2 = (Report) academicPrinter.printFileTemplate("report");
System.out.println(report2.getInfo());
System.out.println(report2.toString());
Report report3 = (Report) academicPrinter.printFileTemplate("report");
System.out.println(report3.getInfo());
System.out.println(report3.toString());
}

得到输出结果如下

1
2
3
4
5
6
7
8
9
Cloning Report Object...
Report Template...
Report@2f333739
Cloning Report Object...
Report Template...
Report@77468bd9
Cloning Report Object...
Report Template...
Report@12bb4df8

上面的例子就是通过原型模式实现对象的拷贝,Report模板在Printer工厂初始化的时候已经new出来了,调用者去请求Printer打印3份Report模板,输出结果一共有3份内容相同Report,如果为Report增加String类型的成员变量name,会发现拷贝后它们对象中name内容不一样,这是为什么呢?这篇文章分析了Java中不同的拷贝方式Java中深拷贝与浅拷贝

总结

本文分析了原型模式Prototype,在创建新的对象开销比较大的情况下,可以考虑用原型模式。原型模式也有缺点,应用原型模式的每一个原型类都有一个克隆方法,需要修改时比较麻烦,违背了开闭原则;并且在深拷贝时,对象中若存在嵌套的对象,每一次都需要深拷贝,实现也比较复杂。

参考代码

------ 本文结束 ------

版权声明


BillyYccc's blog by Billy Yuan is licensed under a Creative Commons BY-NC-SA 4.0 International License.
本文原创于BillyYccc's Blog,转载请注明原作者及出处!