引子

  • 古人云:书非借不能读也。我深谙古人教诲,更何况现在 IT 书籍更新快、价格贵、质 量水平更是参差不齐,实在不忍心看到用自己的血汗钱买的书不到半年就要被淘汰,更不想 供养使用金山快译、词霸等现代化工具的翻译们。于是我去书店办了张借书卡,这样便没有 了后顾之忧了——书不好我可以换嘛! 但是,借书也有不爽的地方,就是看到有用或者比较重要的地方,不能在书旁标记下来。 一般我会将这页内容复印下来,这样作为我自己的东西就可以对其圈圈画画,保存下来了。
  • 在软件设计中,往往也会遇到类似或者相似的问题,GOF 将这种解决方案叫作原型模 式。也许原形模式会给你一些新的启迪。

定义与结构

  • 原型模式属于对象创建模式,GOF 给它的定义为:用原型实例指定创建对象的种类, 并且通过拷贝这些原型创建新的对象。 在 Java 中提供了 clone()方法来实现对象的克隆,所以 Prototype 模式实现变得简单许多。

  • 注:clone()方法的使用,请参考《Thinking in Java》或者《Effective Java》,对于许多原型模式中讲 到的浅克隆、深克隆,本文不作为谈论话题。

  • 使用克隆方式来创建对象与同样用来创建对象的工厂模式有什么不同?

    • 前面已经提过 工厂模式对新产品的适应能力比较弱:创建新的产品时,就必须修改或者增加工厂角色。而 且为了创建产品对象要先额外的创建一个工厂对象。
  • 那通过原型模式来创建对象会是什么样子呢? 先让我们来看看原型模式的结构吧。

    • 客户角色:让一个原型克隆自己来得到一个新对象。
    • 抽象原型角色:实现了自己的 clone 方法,扮演这种角色的类通常是抽象类,且它具有 许多具体的子类。
    • 具体原型角色:被复制的对象,为抽象原型角色的具体子类。

分析

  • 对于抽象原型角色和具体原型角色,它们就是一个继承或者实现关系,没有什么好讲的, 记住实现好 clone 方法就好了。

  • 那么客户是怎么来使用这些角色的对象的呢?最简单的方式就是:

    1
    2
    3
    4
    //先 new 一个具体原型角色作为样本
    Prototype p = new ConcretePrototype();
    //使用原型 p 克隆出一个新对象 p1
    Prototype p1 = (Prototype)p.clone();
  • 当然这只是简单的表述原型模式的运行过程。实际运用中,客户程序与原型角色之间往 往存在一个原型管理器(例子见下)。因此创建原型角色、拷贝原型角色就与客户程序分离 开来。这时才能真正的体会到原型模式带给我们的效果。

  • 使用原型管理器后,客户获得对象的方式

    1
    Prototype p1 = PrototypeManager. getManager().getPrototype(“ConcretePrototype”);

原型管理器的实现

  • 简单来说就是对原型清单的维护。可以考虑一下几点: 要保存一个原型对象的清单,我们可以使用一个 HashMap 来实现,使原型对象和它的名字 相对应;

  • 原型管理器只需要一个就够了,所以可以使用单例模式来实现控制;实现得到、注 册、删除原型对象的功能只是对 HashMap 的对应操作而已。

  • 代码如下:

    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
    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 能够动态的抽取当前对象运行时的状态并且克隆到新的对象中,新对象就可以在此基础上进行操作而不损坏原有对象;
    • 而 new 只能得到一个刚初始化的对象, 而在实际应用中,这往往是不够的。
  • 分析了这么多了,举一个使用原型模式较为经典的例子:绩效考核软件要对今年的各种考核数据进行年度分析,而这一组数据是存放在数据库中的。一般我们会将这一组数据封装在一个类中,然后将此类的一个实例作为参数传入分析算法中进行分析,得到的分析结果返回到类中相应的变量中。
  • 假设我们决定对这组数据还要做另外一种分析以对分析结果进行比较评定。这时对封装有这组数据的类进行 clone 要比再次连接数据库得到数据好的多。 任何模式都是存在缺陷的。
  • 原型模式主要的缺陷就是每个原型必须含有 clone 方法,在已有类的基础上来添加 clone 操作是比较困难的;而且当内部包括一些不支持 copy 或者循 环引用的对象时,实现就更加困难了。