点击进入我的博客
原始模型模式通过给一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象。
2.7.1 原型模式结构
原型模式这种模式涉及到三个角色:
- 客户(Client)角色:客户类提出创建对象的请求
- 抽象原型(Prototype)角色:这是一个抽象角色,此角色给出所以的具体原型类所需的接口。
- 具体原型(Concrete Prototype):被复制的对象。
2.7.2 原型模式细节
主要目的
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
适用环境
- 创建新对象成本较大(例如初始化时间长,占用CPU多或占太多网络资源),新对象可以通过复制已有对象来获得,如果相似对象,则可以对其成员变量稍作修改。
- 系统要保存对象的状态,而对象的状态很小。
- 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的组合状态,通过复制原型对象得到新实例可以比使用构造函数创建一个新实例更加方便。
优点
- 当创建对象的实例较为复杂的时候,使用原型模式可以简化对象的创建过程,通过复制一个已有的实例可以提高实例的创建效率。
- 扩展性好,由于原型模式提供了抽象原型类,在客户端针对抽象原型类进行编程,而将具体原型类写到配置文件中,增减或减少产品对原有系统都没有影响。
- 原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构,而原型模式不需要这样,圆形模式中产品的复制是通过封装在类中的克隆方法实现的,无需专门的工厂类来创建产品。
- 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。
缺点
- 需要为每一个类配置一个克隆方法,而且该克隆方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违反了开闭原则。
- 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重签到引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。
2.7.3 Java的Clone
Object.clone()
clone()
方法返回的对象叫做原始对象的克隆体。一个克隆对象的基本特性必须是:
-
a.clone()!=a
,这也就意味着克隆对象和原始对象在java中是两个不同的对象。 -
a.clone().getClass == a.getClass()
,克隆对象与原对象类型相同 -
a.clone.equals(a)
,也就是说克隆对象完完全全是原始对象的一个拷贝。此条件是非必需的。
Cloneable接口
Object
类没有实现该接口,所以用户如果没有主动实现该接口时,调用clone()
方法会报错CloneNotSupportedException
。
Java实现步骤
- 实现
Cloneable
接口,这是步骤的关键之处。 - 重写
clone()
方法,并声明为public
,因为Object
的该方法是protected
的。 - 调用
super.clone()
来获取新的克隆对象。在运行时刻,Object
中的clone()
识别出你要复制的是哪一个对象,然后为此对象分配空间,并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中。
class A implements Cloneable {
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}
2.7.4 深复制和浅复制
浅复制
- 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。
-
Object.clone()
是浅复制。
深复制
- 被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。
- 深复制要深入到多少层是一个不易确定到问题,需要特别注意
- 深复制的过程中可能出现循环引用到问题,需要小心处理
利用串行化进行深复制
- 把对象写到流里的过程是串行化(Serilization)过程,但是在Java程序师圈子里又非常形象地称为“冷冻”或者“腌咸菜(picking)”
- 把对象从流中读出来的并行化(Deserialization)过程则叫做 “解冻”或者“回鲜(depicking)”过程。
- 应当指出的是,写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面,因此“腌咸菜”的只是对象的一个拷贝,Java咸菜还可以回鲜。
- 利用这个特性,在Java语言里深复制一个对象,可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里,再从流里读出来,便可以克隆对象。
- 这样做的前提是对象以及对象内部所有引用到的对象都是可串行化的,否则,就需要仔细考察那些不可串行化的对象可否设成
transient
,从而将之排除在复制过程之外。
public Object deepClone() throws Exception {
//将对象写到流里
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(this);
//从流里读出来
ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi = new ObjectInputStream(bi);
return(oi.readObject());
}
网友评论