所谓高贵的灵魂,即对自己怀有敬畏之心
——尼采
用原型实例指定创建对象的种类,在使用他的时候通过拷贝这些原型来创建新的对象
和其他创建型设计模式一样,原型对 client 隐藏了具体的产品类型,从而实现了 client 和具体产品之间的解耦;同时可以在不改动调用上下文的情况下切换具体产出的产品
原型的实现相当简单,一言以蔽之,就是通过复制当前对象的方式创建新的对象(现在绝大多数面向对象的语言已经提供了clone方法以便你实现)
但是问题在于,什么时候下我们才会需要不使用new,而是去复制一个对象呢?
所以在这个例子里,我将省略大部分实现,因为关键是为什么要用原型,而不是原型怎么用
准备好了?那我们开始:
假定现在我们在开发这样一个绘图工具,功能是这样的:
针对这样的组件内容,我们定义了如下的 组件类簇
,就像这样:
使用工厂方法(即使使用抽象工厂,问题也是一样的)生成出的效果,就像这样:
这种做法当然可以解决使用new时产生的硬编码问题,而且他封装了组件对象生成的细节
但是他和直接new也有相同的问题:
? 每次创建 ImageComponent 对象的时候,都要从磁盘上重新加载一遍图片,这是很消耗资源的
如果一个对象里所需要的信息可能要通过对磁盘中的文件进行解析 或者 通过数据库才能获取,这样一来生成这个对象的成本就会变高
如果此时这个对象又经常性需要
新的实例
,这时候我们就会考虑尽量不初始化他,转而采用复制
的方式直接从内存里再得到一个状态相同
的新实例
事实上上面讲的,这就是我们使用原型的第一个原因
? 正因为对象的初始化代价大,所以我们考虑直接复制他以获得新的实例
所以上面的组件如果采用原型模式的话,他是这样的:
新的结构允许 Component及其子类 通过复制自身的方式创建新的实例
这样一来就解决了在创建 ImageComponent 的时候的高损耗问题
可控的复制范围
那你会说,不对啊。这样一来样式也会被复制,到时候不都重叠到一起了吗?
说是复制,其实也未必真的要完全按照编程语言预设的clone方法来,只要你绕开了高损耗的部分,就算是完成任务了,比如这样:
public class ImageComponent extends Component{ …… public ImageComponent clone(){ ImageComponent result = new ImageComponent(); result.image = this.image; return result; } …… }
这样不就实现了既不会重叠,也避开了损耗,还没有把自己的字段泄露出去
事实上像这种新建大量大开销对象的需求并不少见,比如割草游戏里的草、俄罗斯方块里的方块还有祖玛嘴里的球。他们可能存活不到一秒钟,系统却要给他们设定各种各样的属性和贴图。
为了这些龙套浪费宝贵的系统资源是不值当的,这时候就是原型模式大显神威的时候
一般来说,我们不会让 client 直接去调用原型的 clone 方法,我们会使用一个原型的管理器,统一管理原型对象
这个原型管理器,我们称之为 原型库 ,就像这样:
//组件原型库
public class ComponentRepository{
private Map<Class<? extends Component>,Component> prototypeMap = new HashMap<>();
public ComponentRepository(){
把组件类的Class对象和原型对象的映射关系写入prototypeMap中
}
public Component createComponent(Class<? extends Component> c){
if(prototypeMap.containerKey(c)){
return c.clone();//返还复制体
}else {
return null;//原型库中不存在
}
}
public void setPrototype(Class<? extends Component> c,Component cpt){
prototypeMap.put(c,cpt);
}
}
原型库提供了类似工厂方法的 createComponent 让你可以获取想要的组件的复制类,同时还提供了 setPrototype 方法以供你随时替换原型对象
在下个需求里你就知道他有什么用了
现在我们有了新的需求,他是这样的:
但是原型模式中就不需要了,我们可以这样做:
//组件原型库
public class ComponentRepository{
……
public Component createComponent(Class<? extends Component> c){
if(prototypeMap.containerKey(c)){
Component result = c.clone();
setPrototype(c,result);//把复制体设定成新的原型
return result;//返还复制体
}else {
return null;//原型库中不存在
}
}
public void setPrototype(Class<? extends Component> c,Component cpt){
prototypeMap.put(c,cpt);
}
……
}
然后你就会发现再点击按钮A的时候生成的长方形,就已经和上一个的样式一样了
这事实上才是原型模式和工厂模式最大的区别,当你使用原型模式产出产品的时候,你对原型对象的修改,是可以即时的提现到后续产出的对象中的
原型模式的结构是相对简洁的,这是因为原型模式是唯一一个必须直接依附
在 产品类簇 上的创建型模式(静态工厂属于工厂方法的变例;单例则根本没有产品类簇的概念)
这样一来原型模式不会出现像工厂模式那样的平行类层次,也不需要生成器那样的 Director 来主导构筑对象的过程,整体会显得更加轻便