原型模式
概述
《设计模式》一书中对 “原型模式” 的意图描述如下:
用原型实例指定创建对象的种类,并通过拷贝对象的原型创建新的对象
貌似有点难懂,概括性地讲就是实现对不同类型对象的复制,而不需要知道他们的具体类型
一般情况下的类结构图如下图所示:
![Prototype.png](https://s2.loli.net/2022/12/16/BQPhI3dbLCYcF8m.png)
原型模式适用于以下场景:
- 当一个系统需要独立于它的产品创建、构成和表示时
- 当要实例化的类是在运行时指定
- 为了避免创建一个与产品层次平行的工厂类层次时
- 当一个类的实例只能有几个不同状态组合中的一种时
具体实例
以 《设计模式》一书中提到的 “动机” 为例:现在需要创建一个通用的图形编辑器框架,以及增加一些表示音符、休止符和五线谱的新对象来构造一个乐谱编辑器。这个编辑器框架可能有一个工具选择板用于将这些音乐对象加入到乐谱中,同时,这个选择板可能还包括选择、移动和其它操纵音乐对象的工具。
首先我们定义所有图形对象都具备的接口 Graphic
:
public interface Graphic {
// 图形对象具备的公共行为
}
然后是针对乐符对象的操作类的接口 GraphicTool
:
public interface GraphicTool {
void moveToBashboard(Graphic g); // 移动乐符对象到工具版
}
现在,需要考虑如何移动乐符对象。如果乐符在系统中每种类型都是唯一的,那么直接将这些乐符对象设置为单例对象进行移动即可。然而,系统中可能存在不同类型的乐符,这些乐符之间的属性可能并不一样,甚至在移动乐符之后这些属性都会发生变化,因此设置为单例的方案是不可行的。
为了确保这些乐符之间不会相互影响,我们可以在移动乐符的时候创建对应的副本实例,从而隔离乐符对象之间的相互影响。然而,对于 Graphic
对象来讲,我们并不知道具体的类型,因此无法对乐符对象进行对应的实例化
可行的解决方案是在 Graphic
中定义一个方法,要求具体的子类实例类能够自己创建对应的副本对象,那么 GraphicTool
在移动乐符对象时就不需要再考虑对象实例化的问题,同时,对于可以设置为单例对象的乐符,也可以由子类自定义进行浅拷贝
最终,我们的 Graphic
会存在一个公共的 clone
方法以返回对应的副本实例:
public interface Graphic {
void draw(Position p); // 假设存在方法将图形绘画到指定位置
Graphic clone(); // 创建自身副本对象
}
而我们的 GraphicTool
在移动乐符对象时,可以自行获取对应的乐符对象进行操作:
public abstract class AbstractGraphicTool
implements GraphicTool {
@Override
public void moveToBashboard(Graphic g) {
Graphic p = g.clone();
p.draw(new Position());
}
}
总结
尽管原型模式存在许多的优点,但是实际用起来不是那么方便,因为没有确切的手段能够保证对象的克隆一定是深拷贝。因此在实现时,对于 clone()
方法的实现难度很大,一般情况下也不建议直接使用原型模式。特别是对于 Java 中 Object
定义的 clone()
方法,一般都不推荐重写它。一种比较合适的方式是对于 clone()
方法的实现通过重新构造对象实例来代替默认的 clone()
,具体可以查看 《Effective Java(第三版)》中第13条相关的描述
参考:
[1] 《设计模式—可复用面向对象基础》