设计模式_原型模式
原型模式: Prototype Module
顾名思义,这个模式有一个样板实例,用户从这个样板对象中复制出一个内部属性一致的对象,这个过程也就是我们俗称的克隆。被复制的实例就被成为“原型”。
#定义
用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象
##使用场景
1. 类初始化需要消耗非常多的资源,包括数据,硬件资源,通过原型拷贝可以避免这些消耗
2. 通过new产生一个对象需要非常繁琐的数据准备或访问权限
3. 一个对象需要提供给其他对象访问,并且每个调用者可能都需要修改其值时,可以通过保护性原型拷贝拷贝多个对象供调用者使用。
注意
通过实现Cloneable接口的原型模式在调用clone函数构造实例时并不一定比通过new操作速度快,只有当通过new构造对象较为耗时或者是成本较高时,通过clone方法才能获得效率上的提升。
当然,原型模式除了实现Cloneable接口来实现,同样也存在这其他的实现方式.
#实现
##方法一: 实现Cloneable接口##
1 | import java.awt.List; |
1 | public class PrototypeModule { |
从结果中可以看出,当我们在被拷贝出来的对象中修改ArrayList中的数据时,原对象的ArrayList数据也改变了,这是因为我们这里使用的方式只是一个浅拷贝,它其实并不是将原始文档的所有字段都重新构造了一份,而是直接将副本文档的字段直接引用原始文档的字段,因此当我们改变副本文档时,原始文档也随着改变了。
注意
为什么我们也修改了text,但是原始文档的值还是没有变化?
String类型是一个值类型
String指向的对象是一个不可变的对象,String类型的每次变化其实都是创建一个新的对象,之后再将引用指向这个新的对象而已。之前的那个对象其实还是存在的,只是我们不指向它了。
如果将String mText更改为StringBuffer mText, setText方法更改为append(text)的话结果就一样了。
深拷贝
-
鉴于上面的浅拷贝存在的一些问题,我们可以采用深层拷贝来解决它。之所以会出现修改副本时原本会改变,是因为副本是直接指向原本的引用的。因此,我们只需要在拷贝对象时,对于引用型的字段也采用拷贝的形式,而不是单纯的引用。
修改如下:1
2
3
4
5
6
7@Override
protected WordDocument clone() CloneNotSupportedException {
WordDocument super.clone(); = (WordDocument)
this.mText; .mText =
this.mImages.clone(); .mImages = (ArrayList<String>)
return ;
}
这样的话副本的引用型字段就指向原本字段的拷贝了,而不是它的本身,这样就不会引起连锁反应。
而上面的text由于是String类型,是一个值类型,不是引用类型
实际使用
-
在Android中,Intent也是使用了一个原型模式。而Intent为什么要使用原型模式呢?就是因为这个Intent和以前的一个Intent在很多地方是一样的,我们只需要更改它的部分信息就可以了,因此先拷贝,再修改,节约资源。而在Intent中,存在一段代码,这段代码也很好的解释了之前总结的那句话:clone方法是要看情况的,需要根据构造对象的成本来决定是clone还是new一个新的。
1 |
|
这里并没有使用super.clone()方法来实现对象的拷贝,而是直接new的一个新的,因为考虑到成本的问题
在Bundle以及OkHttp中也是使用了原型模式,其余使用的地方自己可以多看看,多找找。
—————————-华丽丽的分割线——————————
在这里记录一个自己用到的地方吧:
在进行登陆操作时,会通过一个session来保存用户的登录信息,而这些信息会在APP模块中调用,比如说登陆失效的判断等等。在登陆时,会先获取一次之前的登录信息,登陆完成后就要从网络上获取一个新的信息,例如登陆的时间存放到session中。
而这个时候就可以使用原型模式了,深层拷贝的方法:
第一次登陆时使用拷贝出来的副本进行登陆,等网络请求结束,成功获取到当前登陆的服务器时间时就修改原本中的用户信息。
#总结
原型模式使用起来还是挺好的,而具体什么时候使用也是看项目的需求吧,看什么合适就使用什么模式