Java的23种设计模式——原型模式
1、现象模拟
现如今,IT书籍更新快,价格高,质量水平参差不齐,购买的书籍很快就会被淘汰,所以,我想到了一个办法:办一张借书卡,这样的话,我就可以随便换书看了。
但是,借书也有很多不爽的地方,就是看到有用或者比较重要的地方,不能在书旁标记下来,一般我会将这页书的内容复印下来,这样作为我自己的东西我就可以随便做笔记了。
在软件设计中,往往也会遇到类似或者相似的问题,GOF将这样中解决方式叫做原型模式
2、定义与结构
- 原型模式属于对象创建模式
- GOF这样定义:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
- 在Java中提供了clone()方法来实现对象的克隆,随意Prototype模式实现变得简单了许多
- clone()方法的使用,可以参考 Java的clone()方法详解,本文不作为深究
使用克隆方式来创建对象与同样用来创建对象的工厂模式有什么不同?
前面已经提过工厂模式对新产品的适应能力比较弱:创建新的产品的时候,就必须修改或正增加工厂角色,而且为了创建产品对象就要先额外的创建一个工厂对象。
那么通过原型模式来创建对象是什么样子的呢?
- 原型模式结构
- 客户角色:让一个原型克隆自己来得到一个新对象
- 抽象原型角色:实现了自己的clone方法,扮演这种角色的类通常是抽象类,且它觉有许多具体的子类
- 具体原型角色:被复制的对象,为抽象原型角色的具体子类
按照定义客户角色不仅要负责使用对象,而且还要负责对象原型的生成和克隆。这样造成客户角色分工就不是很明确,所以我们把对象原型生成和克隆功能单独拿出来放到一个原型管理器中。
原型管理器维护了已有原型的清单。
客户在使用时会向原型管理器发出请求,而且可以修改原型管理器维护的清单。这样客户不需要编码就可以实现系统的拓展。
3、代码实现
3.1、抽象原型角色
/**
* 抽象原型角色 (实现了自己的clone方法,扮演这种角色的类通常是抽象类,且它觉有许多具体的子类)
*/
public abstract class Prototype implements Cloneable {
abstract void testMethod();
@Override
protected Object clone() {
Object object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return object;
}
}
3.2、具体原型角色
/**
* 被复制的对象,为抽象原型角色的具体子类
*/
public class ConcretePrototype extends Prototype {
@Override
void testMethod() {
System.out.println("ConcretePrototype");
}
}
3.3、原型管理器
- 简单对原型清单的维护
- 要考虑一下几点:
- 保存原型对象的清淡,可以使用HashMap来实现
- 使用HashMap可以使原型对象和它的名字相对应
- 原型管理器只要一个就够了,所以可以使用单例模式来实现控制
- 实现得到、注册、删除原型对象的功能
/**
* 原型管理器
*/
public class PrototypeManager {
private static PrototypeManager pm;
private Map prototypes = null;
private PrototypeManager() {
prototypes = new HashMap<>();
}
// 使用单例模式来得到原型管理器的唯一实例
public static PrototypeManager getManager() {
if (pm == null) {
pm = new PrototypeManager();
}
return pm;
}
public void register(String name, Object prototype) {
prototypes.put(name, prototype);
}
public void unregister(String name) {
prototypes.remove(name);
}
public Prototype getPrototype(String name) {
if (prototypes.containsKey(name)) {
// 将清单中对应原型的复制品返回给客户
return (Prototype) ((Prototype) prototypes.get(name)).clone();
} else {
Prototype object = null;
try {
object = (Prototype) Class.forName(name).newInstance();
register(name, object);
} catch (Exception e) {
System.err.println("Class " + name + "没有定义!");
}
return object;
}
}
}
- 这样客户自定义新的产品对象时,同时向原型管理器注册一个原型对象,而使用的类只需要根据客户的需要来从原型管理器中得到一个对象就可以了。这样就使得功能拓展变得更容易些
- 原型管理器不就是一个工厂。当然这样的一个工厂,经过了改进(例如,采用了Java的反射机制),去掉了像抽象工厂模式或者工厂方法模式那样繁多的子类,因此可以说原型模式就是在工厂模式的基础上加上了克隆方法
原型模式使用clone能够动态的抽取当前运行时的状态并且克隆到新的对象中,新对象就可以在此基础上进行操作而不损坏原有对象;而new只能得到一个刚初始化的对象,而且在实际应用中,这往往是不够的,特别当你的系统需要良好的拓展性时,在设计中使用原型模式也是很有必要的。比如:你的系统可以让客户自定义自己需要的类别,但是这种类别的初始化可能需要传递多于已有类别的参数,而这使得用它的类将不知道怎么来初始化它因为已经写死了),除非对类进行修改。可见 clone 方法是不能使用构造函数来代替的。
3.4、客户角色
对于抽象原型角色和具体原型角色,他们就是一个继承或者实现关系
那么客户是怎么来使用这些角色的对象的呢?最简单的方式:
public static void main(String[] args) {
Prototype prototypeOne=new ConcretePrototype();
Prototype prototypeTwo=(Prototype) prototypeOne.clone();
}
- 当然这是最简单的表述原型模式的运行过程
- 实际应用中,客户程序与原型角色之间往往存在一个原型管理器(3.3)
- 因此创建原型角色、拷贝原型角色就与客户程序分离开来了
- 使用原型管理器有更好的效果
public static void main(String[] args) {
Prototype prototypeOne=new ConcretePrototype();
//Prototype prototypeTwo=(Prototype) prototypeOne.clone();
PrototypeManager.getManager().register("prototypeOne", prototypeOne);
Prototype prototypeTwo=PrototypeManager.getManager().getPrototype("prototypeOne");
}
- 原型模式与其他创建型模式有着相同的特点:他们都将具体产品的创建过程进行包装,是的客户对创建不得可知
- 通过增加或者删除原型管理器中注册的对象,可以比其他创建型更方便的在运行时增加或者删除产品
- 如果一个对象的创建总是由几种固定组件不同方式组合而成;如果对象之间仅仅实例属性不同,将不同情况的对象缓存起来,直接克隆使用。也许这比采用传递参数重新new一个对象要来的快一些