目的
Prototype(也叫做Clone)是创建型模式的一种,允许你通过复制现有的对象来生成新的对象,而不会影响现有对象的内部。
问题
你有一个对象并且想要创建一个副本。你该怎么做?首先,你需要创建同样class的一个新对象。然后,你必须遍历源对象的所有字段并把值拷贝到新对象中。
但是这么做有一个问题。不是所有对象都可以通过这种方式被拷贝。一些对象拥有无法从外部访问的私有字段。
这么做还有另外的问题。因为你必须知道对象类能够被遍历的所有字段,你的代码需要依赖你要拷贝对象的类。在你仅知道拷贝对象的接口时无法拷贝对象。
解决
Prototype模式把克隆操作委托给对象自己。
他为所有需要克隆的对象声明通用的接口。他允许在不紧密耦合具体类的情况下克隆对象。通常,prototype接口只包含一个clone方法。
所有类对clone方法的实现都很像。这个方法创建一个当前类的对象,然后把他的字段值拷贝到新对象中。大多数编程语言都允许访问同一个类对象中的私有字段,所以这种拷贝过程比较直接。
可以被克隆的对象叫做prototype(原型)。有时,特别是当你的对象有很多字段并且一些字段可配置,原型可以作为子类的一种替代。在这种情况下,程序提前创建一些原型,然后克隆它们而不是从头开始重建对象。
真实世界的类比
工业和细胞分裂
在真实生活中,原型在产品量产之前用来做各种测试。然而,在这种情况下,原型并没有参与真正的生产,是一个被动的角色。
因为工业原型自己不会拷贝,对这个模式更相近的类比是有丝细胞分裂(生物学,还记得吗?)。在这种分裂后,两个完全一样的细胞就形成了。源细胞就是原型,在创建拷贝中是一个主动的角色。
结构
基本实现
原型注册实现
Prototype声明克隆接口。大多情况下,只要一个clone方法就够了。
Concrete(具体的)Prototype来实现克隆方法。除了直接告杯字段值到新对象外,这个方法也可以解决一些应当对客户端隐藏的警告。比如,克隆引用对象,解开递归依赖等。
客户端使用Prototype接口来检索一个对象的克隆。
Prototype registry提供了对常用原型的简单访问,存储预先创建的一组对象,随时可以复制。通常,它可以用简单的(name->prototype)哈希映射实现。但是为了方便,任何其他搜索条件都可以添加到注册表中。
伪代码
在这个例子中,原型被用来克隆代表几何形状的对象,而不耦合到它们的类。
所有的形状类都实现了只有克隆方法的通用克隆接口。子类先调用父类的克隆方法,然后把他们自己的字段拷贝到结果对象中。
因此,原型模式允许客户端代码克隆对象,即使不知道和独立于其特定的类。
1 | // Base prototype. |
适用性
当你的代码不应该依赖你要拷贝对象的具体类时。比如,对象的类是未知的,因为你通过接口和他们协作。
原型模式提供给客户端一个和所有原型协作的接口。这个接口对所有支持克隆的对象是通用的。这使得客户端对要克隆产品的具体类保持独立。
当你想要减少由不同方式配置相似对象(换句话说,每个类由唯一的字段值)组成的类层级结构的大小时。
原型模式允许创建一个原型对象集合,这些对象代表了一个对象所有可能的配置。
然后,客户端代码不是初始一个匹配一些配置的子类,而是寻找适当的原型并且克隆他。
如何实现
创建原型接口并在接口中声明clone方法。你可以简单的把这个方法添加到现有类层级中的所有类,如果你有类的话。
为所有原型类添加一个可选的构造方法,这个构造方法接收当前类的一个对象。这个构造函数必须从传递的对象中将类中定义的所有字段的值拷贝到当前实例。然后,它应该调用父类构造方法去拷贝父类中的字段。
如果你的编程语言不支持方法重载,你需要定义一个特别的方法来拷贝数据。构造方法仅仅是最便利的一个,因为他在new操作后就开始拷贝。
clone方法通常是有一行:运行一个使用原型构造方法的new操作符。注意,每个支持克隆的类必须明确重写clone方法,克隆方法要new一个他自己的类。否则,克隆将会生产出一个父类的对象。
此步骤可选。创建一个原型注册中心来存储常用的原型。甚至是相同类的对象,只是配置方式不同。
你可以在原型的基类中用工厂类或者工厂方法实现注册中心。这个工厂方法可以根据客户端代码传递的参数查找适当的原型。搜索条件可以仅仅是一个字符串标签或者复杂的搜索条件。在找到原型后,它应该克隆它并且把拷贝返回给客户端。
最后,我们应该把直接调用对象构造方法的clone方法改成调用原型注册中心的工厂方法。
优点
允许克隆对象而不耦合到具体的类。
减少重复的实例代码。
更快的创建复杂对象。
当你需要处理有许多可选配置的复杂对象时,可以把它当作替代子类的方案。
缺点
- 引用了许多其他对象的复杂对象很难被克隆。
和其他模式的关系
通常,设计从使用Factory Method开始(比较简单,并且可以通过子类实现定制),逐渐演变到Abstract Factory,Prototype,或者Builder(更加复杂,但更灵活),因为设计者发现它们需要更灵活的程序。
Abstract Factory类通常使用工厂方法实现,但也可以使用Prototype实现。
当您需要将Command的副本保存到历史记录中时,Prototype可以提供帮助。
大量使用Composite和Decorator模式的设计通常也可以从Prototype中受益。他允许克隆复杂结构,而不是从头构建他们。
Prototype不需要子类,但是需要初始操作。Factory Method需要子类,但是不需要初始化这个步骤。
Prototype是更简单的Memento替代,如果您想要把对象的某种状态保存在历史中,相当简单,没有外部资源的链接,或者链接很容易重新建立(Memento还不了解,此处很模糊)。
Abstract Factory,Builder和Prototype都可以实现为Singleton。
总结
Prototype是创建型模式的一种,允许你克隆对象,即使被克隆对象很复杂,它不需要耦合特定的被拷贝对象的类。
用例:
java.lang.Object#clone() (class 应当实现java.lang.Cloneable 接口)
鉴定:通过clone()或者copy()等方法轻松识别原型模式。