文章

23种设计模式-原型模式

Prototype模式 原型模式

Intent/目的

使用原型实例创建指定种类的对象,通过复制原来的对象来创建新的对象。

Motivation/动机

你可以使用通用框架构建一个乐谱图形化编辑器,添加新的对象来代表音符、休止符和五线谱。编译器框架应该有一个工具的调色板用来添加新的音乐元素到乐谱上。调色板也需要包含工具的选择、移动还有其他的自定义音乐对象。用户可以点击四分音符的工具,添加一个四分音符到乐谱上。或者用户也可以通过移动工具来控制音符的上下移动。

假设框架提供了抽象的图形组件类Graphic,比如音符、五线谱。此外,还会提供一个抽象的类Tool来定义一些工具比如调色板。框架还会预定义GraghicTool子类作为创建图形对象实例的工具,并把图形添加到文档中.

但是GraghicTool类给框架设计者带来了一个问题。音符和五线谱对应的类是具体的应用程序来指定的,GraghicTool类是框架自身的。GraghicTool并不知道怎么创建这些音乐对象并放到乐谱上。这样的话我们需要给每个音乐元素对象创建GraghicTool的子类,从而出现特别多的子类区别仅仅是对应的音乐元素对象不一样。我们知道对象组合是替代子类化的一种灵活选择。问题是,框架如何使用它来通过GraphicTool的实例参数化Graphic的类,以便它们创建所需的图形。

解决方案在于让GraphicTool通过复制或“克隆”Graphic子类的实例来创建新的Graphic。我们称这个实例为原型。GraphicTool通过应该克隆的原型来进行参数化,并将其添加到文档中。如果所有的Graphic子类都支持克隆操作,那么GraphicTool就可以克隆任何类型的Graphic

在我们的音乐编辑器中,用于创建音乐对象的每个工具都是GraphicTool的一个实例,它们被初始化为不同的原型。每个GraphicTool实例将通过克隆其原型并将克隆添加到乐谱中来生成一个音乐对象。通过这种方式,我们可以灵活地使用不同的工具来创建不同类型的音乐对象,而不需要编写大量重复的代码

我们可以进一步使用原型模式来减少类的数量。我们有单独的类用于全音符和半音符,但这可能是不必要的。相反,它们可以是同一个类的实例,初始化时使用不同的位图和持续时间。创建全音符的工具仅仅是一个GraphicTool,其原型是一个被初始化为全音符的MusicalNote。这可以极大地减少系统中的类数量。它还使得向音乐编辑器添加新类型的音符变得更容易。

Applicability/应用场景

使用原型模式的情况包括:

  • 当系统应该独立于其产品的创建、组合和表示方式时;
  • 当需要在运行时指定要实例化的类,例如通过动态加载;
  • 或者为了避免构建与产品类层次结构相对应的工厂类层次结构;
  • 或者当类的实例只能具有几种不同状态的组合时。与每次手动实例化类并带有适当状态相比,安装相应数量的原型并克隆它们可能更方便。

Structure/结构

Participants/角色

  • Prototype (Graphic)
    • 声明克隆自己的接口
  • ConcretePrototype (Staff, WholeNote, HalfNote)
    • 实现克隆自己的接口
  • Client (GraphicTool)
    • 通过原形模式创建一个对象的类

Consequences/总结

原型模式与抽象工厂和构造器模式具有许多相同的结果:它将具体产品类隐藏在客户端之外,从而减少客户端了解的类名数量。此外,这些模式允许客户端在不修改代码的情况下使用特定于应用程序的类。

原形模式还有其他的优点:

  1. 可以在运行时添加和删除产品
  2. 通过变化值指定新对象
  3. 通过变化构造器指定新对象
  4. 降低类的数量
  5. 动态配置应用程序

原型模式的主要缺点是每个Prototype子类必须实现Clone操作,这可能很困难。例如,在考虑的类已经存在时,添加Clone操作可能很困难。当这些类的内部包含不支持复制或存在循环引用的对象时,实现Clone操作可能会很困难。

原型模式和抽象工厂模式在某些方面是竞争的模式。然而,它们也可以一起使用。抽象工厂可能会存储一组原型,用于克隆和返回产品对象。经常大量使用组合模式和装饰者模式的设计也经常会受益于原型模式。

本文由作者按照 CC BY 4.0 进行授权

© Poul.Y. 保留部分权利。