设计模式拾荒之原型模式(Prototype Pattern)

  1. 参考书籍: 《Design Patterns: Elements of Reusable Object-Oriented Software》
  2. 设计模式包教不包会

很多讲解原型模式的博文,甚至视屏教程中都把原型模式最主要的目的列为效率的提升, 美其名曰复制一个对象比创建一个对象开销更小,并由此引伸到“深拷贝”“浅拷贝”的知识点。 很显然, 这种说法差强人意, 并非原型模式的设计由来。

设计模式用前须知

  • 设计模式中一句出现频率非常高的话是,“ 在不改动。。。。的情况下, 实现。。。。的扩展“ 。
  • 对于设计模式的学习者来说,充分思考这句话其实非常重要, 因为这句往往只对框架/工具包的设计才有真正的意义。因为框架和工具包存在的意义,就是为了让其他的程序员予以利用,进行功能的扩展,而这种功能的扩展必须以不需要改动框架和工具包中代码为前提
  • 对于应用程序的编写者, 从理论上来说,所有的应用层级代码至少都是处于可编辑范围内的, 如果不细加考量, 就盲目使用较为复杂的设计模式, 反而会得不偿失, 毕竟灵活性的获得,也是有代价的

原型模式(Prototype)

  • 设计意图
    • 利用原型实例指定要创建的对象类型,通过拷贝该原型来创建新的实例。
  • 举例:

    • 有一个用于编写图形编辑器的通用框架, 我们用它来编写一个乐谱编辑器。 具体的做法是, 我们要在原有的框架基础上, 扩展定义一些新的基本图形元素(Graphic)对象, 例如音符, 休止符, 五线谱等元素。
    • 用户在使用该乐谱编辑器时, 最常见的操作可能是【添加操作】: 点击一个四分音符,然后将其拖动到文档中, 在相应的位置再点击一下,完成音符的添加。 (这里可以联想一下PhotoShop 或 Microsoft Viso 的操作界面) 。
    • 现在假设框架提供了一个Graphic 抽象类作为图形组件(音符、五线谱)的基类,同时也提供了抽象工具类Tool 作为各种图形操作工具的基类。 在此基础上, 实现了子类GraphicTool 用于创建各种图形组件对象的实例, 将其添加到文档中的操作。

    • 此时, 很自然的, 要利用这个框架编写乐谱编辑器, 我们会首先想定义一些乐谱元素的类,如四分音符(QuaterNote), 二分音符(HalfNote), 一分音符(OneNote), 休止符(Rest) , 这些类继承自Graphic 对象, 然后我们就可以扩展GraphicTool 类, 实现用于添加乐谱元素的工具。

    • 注意: 这里便对框架设计者提出了一个问题, 音符类和休止符类都是应用程序独有的类, 但是GraphicTool 是要包含在框架中的一个类, GraphicTool 并不知道怎么样去new 用户自定义的音符类。 当然, 框架设计者可以让用户针对不同的音符类去扩展GraphicTool , 实现QuaterNoteGraphicTool, HalfNoteGraphicTool, OneNoteGraphicTool 等, 但是这样会产生大量的子类, 而他们的区别仅仅是要创建的实例对象不同。 所以现在我们可能更希望可以仅仅将自定义的类QuarterNote作为一个参数传递给GraphicTool, GraphicTool就能完成相应的创建操作。
  • 图例说明

    • 图片中的空心三角箭头,代表着继承(extends)或实现(Implement)关系, 由继承者/实现者 指向 被继承者/被继承者。
    • 图片中的实心三角箭头且箭头末尾没有圆圈的, 代表着单一的引用关系, 但是被引用的对象也有可能被其他对象引用。
    • 图片中的实心三角箭头且箭头末尾有圆圈的, 代表着一对多的引用关系。
    • 图片中的虚线实心三角箭头, 代表着创建或者实例化的关系。
    • 图片中的末端有圆圈的虚线是一个对方法体内容用伪代码说明的关系
  • 这个地方听起来有点像简单工厂套路(注意简单工厂不是GOF 23种设计模式之一), 但是稍加思考就会发现并非如此, 因为如果应用简单工厂套路 , 那么GrphicTool 就需要持有一个工厂类负责不同对象的创建, 但是这个工厂类也许要包含在框架中, 很显然, 用户不能去修改这个工厂类中的内容去返回不同的对象,完成扩展。

  • 如果不像简单工厂套路, 那是否和工厂方法模式类似呢? 答案是不。 因为工厂方法模式的核心是将父类中的实例化操作延迟到子类进行, 这里的场景并不符合, 因为我们并不希望继承GrphicTool 去实现新的子类, 而是直接利用GraphicTool 创建不同的对象。
  • 那这里是否和抽象工厂模式类似呢?的确如此, 因为GrphicTool 可以持有一个抽象工厂对象AbstractGrphicFactory, 然后,我们可以向其传入不同的,由用户扩展实现子类(例如QuaterNoteGrphicFactory) , 完成不同对象(如QuaterNote)的实例化操作。那么抽象工厂和原型模式的区别是什么?

原型模式与抽象工厂模式的区别

  • 原型模式

    • 上文提到的乐谱编辑器的例子中, 原型模式的核心在于, GraphaicTool 创建一个新的Grphic 对象的方式, 是通过复制(Copying)或者克隆(Clone) 一个Graphic 子类的实例实现的。我们管这个实例叫做原型。 GrphicTool 被传入了“原型“参数(可以是在构造时传入,也可以是通过set 传入, 也可以是在调用方法时传入), 它可以利用这个原型来创建新的对象。 通过这种方式, 图形编辑器框架, 就可以实现一个GrphaicTool, 创建任何用户扩展于 Graphic 的对象(这里回想一下设计模式用前须知就可以理解原型模式的此处的必要性
  • 抽象工厂方法模式

    • 上述提到的问题,同样可以用抽象工厂模式来解决,GrphicTool 可以持有一个抽象工厂对象AbstractGrphicFactory, 然后,我们可以向其传入不同的,由用户扩展实现子类(例如QuaterNoteGrphicFactory) , 完成不同对象(如QuaterNote)的实例化操作。
    • 但是, 这样造成的结果是, 每扩展一个新的图形组件Grphic ,就需要专门再创建一个相对应的工厂, 只生产这一种产品。 这样不仅仅会产生大量的子类,还违背了抽象工厂模式设计的初衷: 解决一系列相互依赖的产品创建, 且保证不同族的产品不被混用。

原型模式总结

  • 原型模式适用场景:
    • 有一个通用的方法中需要创建一个实例。
    • 这个实例, 有可能被用户扩展出很多不同的类型。
    • 对于不同的扩展出的类型,都希望通用的方法可以被继续使用。

你可能感兴趣的:(设计模式,prototype)