正确的使用原型模式,可以使我们的代码运行更高效。
原型模式是一个创建型的设计模式。从名字可以看出,原型模式应该有一个样板实例,我们可以从个样板实例复制出一个内部属性一致的对象,这个过程就是我们俗称的“克隆”。被复制的实例就是“原型”,原型是可定制的。原型模式多用于创建复杂或者构建耗时的实例,使用原型模式复制一个已经存在的实例可以使程序运行更高效。
下面看一下原型模式的简单实现:
/**
*定义一个图书类,作为原型,原型实现Cloneable接口,使原型可被复制
* 原型中有文本和图片
* */
class BookDocument:Cloneable{
var mText=""//文本
var mImages = arrayListOf<String>()//图片列表
fun BookDocument(){
System.out.println("------------------BookDocument的构造方法--------------------")
}
public override fun clone(): BookDocument{
try {
var book = super.clone() as BookDocument//强制转换类型
book.mText=this.mText
book.mImages=this.mImages
return book
}catch (e:Exception){
}
return null!!//如果为空抛出一个空指针异常
}
public fun showBook(){
System.out.println("--------------book content start--------------")
System.out.println("mText: "+mText)
System.out.println("mImagers List: ")
mImages.forEach {
s: String -> System.out.println("image name: "+s)
}
System.out.println("--------------book content ent--------------")
}
}
fun main(args: Array<String>) {
//构建图书对象
var book= BookDocument()
book.mText="这是一本图书"
book.mImages.add("插图1")
book.mImages.add("插图2")
book.mImages.add("插图3")
book.showBook()
//以book为原型,拷贝一个对象
var book1=book.clone()
book1.showBook()
//修改图书对象的文本属性,不会影响原型
book1.mText="这是图书的副本"
book1.showBook()
}
输出结果如下:
从输出结果可以看出,我们通过book.clone()拷贝的对象(book1)和book是一样的,然后我们修改book1的文本不会影响到book,这就保证了原型对象的安全性。需要注意的是,通过拷贝获得的对象不会执行构造方法,如果在构造方法中有一些特殊的初始化操作,在执行拷贝时要考虑到构造方法不会执行的问题。
上述的原型模式实际上是一种浅拷贝,也称为影子拷贝。这份拷贝并不是把图书类的所有属性都重新构造了一份,而是副本的文本字段引用了原型的文本。这种方法实际上是存在一定的问题的,就是当副本的文本发生改变时,实际上原型的文本也会改变,因为副本的文本和原型的文本都是指向了一个内存地址。下面我们把调用的代码改变下:
fun main(args: Array<String>) {
//构建图书对象
var book= BookDocument()
book.mText="这是一本图书"
book.mImages.add("插图1")
book.mImages.add("插图2")
book.mImages.add("插图3")
book.showBook()
//以book为原型,拷贝一个对象
var book1=book.clone()
book1.showBook()
//修改图书对象的文本属性,不会影响原型
book1.mText="这是图书的副本"
book1.showBook()
book1.mImages.add("haha")
book.showBook()
}
看输出结果:
这个结果图中,最后两个文档的输出是一致的,这是因为book1中的mImages只是指向了book的mImages,并没有重新构建新mImages对象,然后将原型book中的图片添加到了拷贝对象book1的mImages中,这就导致了原型和拷贝对象的mImages都是同一个对象,当我们改变book1的mImages时,原型book的mImages也会受影响,反之亦然。那么如何解决这个问题呢?答案就是深拷贝。我们对以上代码进行如下修改:
public override fun clone(): BookDocument{
try {
var book = super.clone() as BookDocument//强制转换类型
book.mText=this.mText
book.mImages= this.mImages.clone() as ArrayList<String>
return book
}catch (e:Exception){
}
return null!!//如果为空抛出一个空指针异常
}
然后运行代码,结果如下:
上述代码中book1的mImages指向了book.mImages的拷贝而非本身,这样book1的mImages发生改变时book的mImages就不会受到影响。可能有人发现了,为什么book1的mText没有指向book.mText的拷贝,但是2个对象还是互不影响,这就要说说String这个数据类型了,String如果通过new的方式是会得到一个对象,如果通过"="的方式获得的就是String类型的变量指向的是“=”右侧的值在常量池中的地址,因为book1和book的mText的值是不同的 ,所以mText指向的是不同的地址,所以原型和拷贝对象的mText是互不影响的。原型模式就学习到这里,在使用时建议使用深拷贝模式,避免操作副本时对原型的影响。