原型模式(Prototype Pattern)是指原型執行個體指定建立對象的種類,并且通過拷貝這些
原型建立新的對象。
原型模式主要适用于以下場景:
1、類初始化消耗資源較多。
2、new 産生的一個對象需要非常繁瑣的過程(資料準備、通路權限等)
簡單克隆
一個标準的原型模式代碼,應該是這樣設計的。先建立原型Prototype 接口:
public interface Prototype{
Prototype clone();
}
建立具體需要克隆的對象ConcretePrototype
import java.util.List;
public class ConcretePrototypeA implements Prototype {
private int age;
private String name;
private List hobbies;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List getHobbies() {
return hobbies;
}
public void setHobbies(List hobbies) {
this.hobbies = hobbies;
}
@Override
public ConcretePrototypeA clone() {
ConcretePrototypeA concretePrototype = new ConcretePrototypeA();
concretePrototype.setAge(this.age);
concretePrototype.setName(this.name);
concretePrototype.setHobbies(this.hobbies);
return concretePrototype;
}
}
建立Client 對象
public class Client {
private Prototype prototype;
public Client(Prototype prototype){
this.prototype = prototype;
}
public Prototype startClone(Prototype concretePrototype){
return (Prototype)concretePrototype.clone();
}
}
測試代碼:
import java.util.ArrayList;
import java.util.List;
public class PrototypeTest {
public static void main(String[] args) {
// 建立一個具體的需要克隆的對象
ConcretePrototypeA concretePrototype = new ConcretePrototypeA();
// 填充屬性,友善測試
concretePrototype.setAge(18);
concretePrototype.setName("prototype");
List hobbies = new ArrayList<String>();
concretePrototype.setHobbies(hobbies);
System.out.println(concretePrototype);
// 建立Client 對象,準備開始克隆
Client client = new Client(concretePrototype);
ConcretePrototypeA concretePrototypeClone = (ConcretePrototypeA)
client.startClone(concretePrototype);
System.out.println(concretePrototypeClone);
System.out.println("克隆對象中的引用類型位址值:" + concretePrototypeClone.getHobbies());
System.out.println("原對象中的引用類型位址值:" + concretePrototype.getHobbies());
System.out.println("對象位址比較:"+(concretePrototypeClone.getHobbies() ==
concretePrototype.getHobbies()));
}
}
從測試結果看出hobbies 的引用位址是相同的,意味着複制的不是值,而是引用的位址。
這樣的話, 如果我們修改任意一個對象中的屬性值, concretePrototype 和
concretePrototypeCone 的hobbies 值都會改變。這就是我們常說的淺克隆。隻是完整
複制了值類型資料,沒有指派引用對象。換言之,所有的引用對象仍然指向原來的對象,
顯然不是我們想要的結果。下面我們來看深度克隆繼續改造。
深度克隆
我們換一個場景,大家都知道齊天大聖。首先它是一隻猴子,有七十二般變化,把一根
毫毛就可以吹出千萬個潑猴,手裡還拿着金箍棒,金箍棒可以變大變小。這就是我們耳
熟能詳的原型模式的經典展現。
建立原型猴子Monkey 類:
import java.util.Date;
public class Monkey {
public int height;
public int weight;
public Date birthday;
}
建立引用對象金箍棒Jingubang 類:
import java.io.Serializable;
public class JinGuBang implements Serializable {
public float h = 100;
public float d = 10;
public void big(){
this.d *= 2;
this.h *= 2;
}
public void small(){
this.d /= 2;
this.h /= 2;
}
}
建立具體的對象齊天大聖QiTianDaSheng 類:
import java.io.*;
import java.util.Date;
public class QiTianDaSheng extends Monkey implements Cloneable,Serializable {
public JinGuBang jinGuBang;
public QiTianDaSheng(){
//隻是初始化
this.birthday = new Date();
this.jinGuBang = new JinGuBang();
}
@Override
protected Object clone() throws CloneNotSupportedException {
return this.deepClone();
}
public Object deepClone(){
try{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
QiTianDaSheng copy = (QiTianDaSheng)ois.readObject();
copy.birthday = new Date();
return copy;
}catch (Exception e){
e.printStackTrace();
return null;
}
}
public QiTianDaSheng shallowClone(QiTianDaSheng target){
QiTianDaSheng qiTianDaSheng = new QiTianDaSheng();
qiTianDaSheng.height = target.height;
qiTianDaSheng.weight = target.height;
qiTianDaSheng.jinGuBang = target.jinGuBang;
qiTianDaSheng.birthday = new Date();
return qiTianDaSheng;
}
}
測試代碼:
public class DeepCloneTest {
public static void main(String[] args) {
QiTianDaSheng qiTianDaSheng = new QiTianDaSheng();
try {
QiTianDaSheng clone = (QiTianDaSheng)qiTianDaSheng.clone();
System.out.println("深克隆:" + (qiTianDaSheng.jinGuBang == clone.jinGuBang));
} catch (Exception e) {
e.printStackTrace();
}
QiTianDaSheng q = new QiTianDaSheng();
QiTianDaSheng n = q.shallowClone(q);
System.out.println("淺克隆:" + (q.jinGuBang == n.jinGuBang));
}
}
運作結果:
克隆破壞單例模式
如果我們克隆的目标的對象是單例對象,那意味着,深克隆就會破壞單例。實際上防止
克隆破壞單例解決思路非常簡單,禁止深克隆便可。要麼你我們的單例類不實作
Cloneable 接口;要麼我們重寫clone()方法,在clone 方法中傳回單例對象即可 。