前言
原型模式是一个创建型设计模式
。
定义:
用原型实例指定创建对象的种类,并通过复制这些原型创建新的对象。
使用场景:
- 类初始化需要消耗非常多的资源,这个资源包括数据、硬件资源,通过原型复制避免这些消耗;
- 通过new产生一个对象需要非常繁琐的数据准备或访问权限;
- 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式复制多个对象供调用者使用,即保护性拷贝。
注意:
通过实现Cloneable接口的原型模式在调用clone函数构造实例时并不一定比通过new操作速度快,只有当通过new构造对象较为耗时时或者说成本较高时,通过clone方法才能获取效率上的提升。
另外,实现原型模式也不一定非要实现cloneable接口,也有其他实现方式。
UML类图:
- Client:
客户端用户;
- Prototype:
抽象类或者接口,声明具备clone能力;
- ConcretePrototype:
具体的原型类;
实现示例
以文档拷贝为例,一个文档中包含文字和图片,编辑后的文档为了安全起见,需要进行文档拷贝,然后再在文档副本上进行修改,这里我们就可以使用原型模式
来实现。
package com.crystal.essayjoker.clone
/**
* Word文档类
* on 2022/12/21
*/
class Word : Cloneable {
/**
* 文本
*/
private var text: String? = null
/**
* 多张图片
*/
private var images: ArrayList<String> = arrayListOf()
constructor() {
println("Word Class constructor execute!")
}
public override fun clone(): Word {
val cloneWord = super.clone() as Word
cloneWord.text = this.text
cloneWord.images = this.images
return cloneWord
}
fun setText(text: String) {
this.text = text
}
fun getText(): String? {
return text
}
fun addImage(image: String) {
images.add(image)
}
fun getImages(): List<String> {
return images
}
fun showWord() {
println("-------showWord Start------")
println("this word text is:$text")
for (image in images) {
println("this word image is:$image")
}
println("-------showWord End-------")
println()
}
}
编写测试类:
object Test {
@JvmStatic
fun main(args: Array<String>) {
val originWord = Word()
//构造数据
originWord.setText("this is origin word")
originWord.addImage("first image")
originWord.addImage("second image")
originWord.addImage("third image")
//展示数据
originWord.showWord()
//拷贝文档副本
val cloneWord = originWord.clone()
cloneWord.showWord()
//修改clone word text
cloneWord.setText("this is clone word")
cloneWord.showWord()
//查看原来的word是否改变
originWord.showWord()
}
}
最终打印结果如下:
从上面实例我们可以看出:
通过clone拷贝对象时并不会执行构造函数;
- 修改clone后的数据并不会影响原始数据?
对于第2条事实真的如此吗?我们尝试修改images
对象中的数据:
val originWord = Word()
//构造数据
originWord.setText("this is origin word")
originWord.addImage("first image")
originWord.addImage("second image")
originWord.addImage("third image")
//展示数据
originWord.showWord()
//拷贝文档副本
val cloneWord = originWord.clone()
cloneWord.showWord()
//修改clone word text
cloneWord.setText("this is clone word")
cloneWord.addImage("fourth image") //新增一条数据
cloneWord.showWord()
//查看原来的word是否改变
originWord.showWord()
打印结果如下:
可以看到修改clone中的image数据,原始数据发生了改变!这就引出了两个概念:浅拷贝
和深拷贝
浅拷贝和深拷贝
浅拷贝:
对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。
深拷贝:
对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
对于上述原型模式的实现只是一个浅拷贝,也称为影子拷贝
,实际上只是将副本数据字段引用指向了原始文档的字段,并没有重新构造一个image对象,只是将数据添加到了原来的image对象中
;
那想解决上述问题,我们就可以采用深拷贝的方式,修改Word类中clone方法如下:
public override fun clone(): Word {
val cloneWord = super.clone() as Word
cloneWord.text = this.text
cloneWord.images = this.images.clone() as ArrayList<String> //对image对象进行深拷贝
return cloneWord
}
Android源码中的原型模式
对应Intent
我们在熟悉不过了,我们直接来看看它的clone
方法实现:
### Intent.clone()
public Object clone() {
return new Intent(this); //可以看到是直接new了个intent对象
}
### Intent(this)
public Intent(Intent o) {
this(o, COPY_MODE_ALL);
}
### this(o, COPY_MODE_ALL)
private Intent(Intent o, @CopyMode int copyMode) {
this.mAction = o.mAction;
this.mData = o.mData;
this.mType = o.mType;
this.mIdentifier = o.mIdentifier;
this.mPackage = o.mPackage;
this.mComponent = o.mComponent;
if (o.mCategories != null) {
this.mCategories = new ArraySet<>(o.mCategories);
}
...
if (o.mExtras != null) {
this.mExtras = new Bundle(o.mExtras);
}
...
}
}
可以看出Intent.clone
的实现是通过原型模式进行深拷贝
,而且并不是通过cloneable
接口来实现的,是通过new出一个新的对象来实现的,具体采用哪种方式需要根据构造对象的成本来决定。
总结
原型模式本质上就是对象拷贝,需要注意的就是深浅拷贝
的问题。可以解决复杂对象构建的资源消耗,提升创建对象的效率,另外一个重要的用途就是保护性拷贝,可以通过返回一个对象拷贝的形式实现只读的限制;
优点:
原型模式是对内存中二进制流的拷贝,比直接new性能好很多,特别是要在一个循环体内产生大量的对象时
。
缺点:
直接在内存中拷贝,构造函数是不会执行的
,这点需要特别注意!
结语
如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!ღ( ´・ᴗ・` )