设计模式——0_3 原型(Prototype)
所谓高贵的灵魂,即对自己怀有敬畏之心
——尼采
定义
用原型实例指定创建对象的种类,在使用他的时候通过拷贝这些原型来创建新的对象
和其他创建型设计模式一样,原型对 client 隐藏了具体的产品类型,从而实现了 client 和具体产品之间的解耦;同时可以在不改动调用上下文的情况下切换具体产出的产品
图纸
一个例子:在程序里加入一个长方形
原型的实现相当简单,一言以蔽之,就是通过复制当前对象的方式创建新的对象(现在绝大多数面向对象的语言已经提供了clone方法以便你实现)
但是问题在于,什么时候下我们才会需要不使用new,而是去复制一个对象呢?
所以在这个例子里,我将省略大部分实现,因为关键是为什么要用原型,而不是原型怎么用
准备好了?那我们开始:
假定现在我们在开发这样一个绘图工具,功能是这样的:
- 点击按钮A,画板上会生成一个长方形组件。你可以拖拽他,修改他的尺寸和背景色
- 点击按钮B,画板上会生成一个正方形组件。你可以拖拽他,修改他的尺寸和背景色
- 点击按钮C,画板上会放入一副50*50尺寸的正方形图片组件
针对这样的组件内容,我们定义了如下的 组件类簇
,就像这样:
组件工厂
使用工厂方法(即使使用抽象工厂,问题也是一样的)生成出的效果,就像这样:
这种做法当然可以解决使用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 方法以供你随时替换原型对象
在下个需求里你就知道他有什么用了
原型和原型的状态
现在我们有了新的需求,他是这样的:
- 当我修改了长方形的样式后,再点击按钮A所生成的长方形必须是修改样式后的长方形
如果现在我们是用其他的方式创建组件对象,那就必须要新增一个类用来记录当前应用的长方形的样式,并在每次创建新的组件对象的时候都访问那个记录样式的类,根据他的信息来生成新的长方形
但是原型模式中就不需要了,我们可以这样做:
//组件原型库
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 来主导构筑对象的过程,整体会显得更加轻便
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!