1 定義
原型模式(Prototype Pattern)屬於創建型設計模式之一,它主要用於當直接創建對象的代價較大時對已有對象進行克隆從而節約性能成本。例如一個對象需要高代價的IO或訪問數據庫之後才能被創建出來,像這情況類初始化需要消化非常多的資源,那麼我們一般可以將已經創建出來的對象作爲原型進行緩存,在下一次再需要同樣對象時對其進行克隆,克隆過程中原類的構造方法不會被執行。原型模式很少單獨出現,一般是跟工廠模式進行配合,先將原型對象進行初始化緩存,然後通過工廠提供給調用者。
2 實現
我們沿用工廠模式時的例子來講原型模式,假設有一個手機廠商要生產智能手機和智能手錶兩款硬件產品, 其中手機和手錶在創建對象時初始化成本很高,所以換成了原型模式的形式來克隆已經存在的原型對象。
產品類,手機和手錶都屬於硬件,將它們抽象出來,硬件抽象類需要繼承Cloneable並重寫clone方法:
public abstract class Hardware implements Cloneable {
protected int mId;
protected String mName;
@Override
public Object clone() {
Object clone = null;
try {
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
public abstract void showInfo();
}
public class Phone extends Hardware {
public Phone(int id, String name) {
// 假設這裏初始化成本很高
mId = id;
mName = name;
}
@Override
public void showInfo() {
System.out.println("手機{" + mId + "," + mName + "}性能很強悍!");
}
}
public class Watch extends Hardware {
public Watch(int id, String name) {
// 假設這裏初始化成本很高
mId = id;
mName = name;
}
@Override
public void showInfo() {
System.out.println("手錶{" + mId + "," + mName + "}時尚時尚最時尚!");
}
}
產品工廠(產品緩存),用於提前初始化原型進行緩存,要生成新的對象通過調用緩存對象的clone方法進行克隆出新對象:
public class HardwareFactory {
private static Map<String, Hardware> sHardwareMap = new HashMap<>();
public static Hardware createHardware(String hardwareName) {
Hardware hardware = sHardwareMap.get(hardwareName);
if (hardware == null) {
throw new UnsupportedOperationException("不支持該操作");
}
return (Hardware) hardware.clone();
}
public static void init() {
Hardware phone = new Phone(1, "手機1號");
sHardwareMap.put("手機", phone);
Hardware watch = new Watch(2, "手錶2號");
sHardwareMap.put("手錶", watch);
}
}
調用者:先對硬件工廠初始化生成產品對象原型,往後調用createHardware方法直接獲取克隆後的新對象:
HardwareFactory.init();
Hardware phone = HardwareFactory.createHardware("手機");
phone.showInfo();
Hardware phone2 = HardwareFactory.createHardware("手機");
phone2.showInfo();
Hardware watch = HardwareFactory.createHardware("手錶");
watch.showInfo();
Hardware watch2 = HardwareFactory.createHardware("手錶");
watch2.showInfo();
運行輸出:
手機{1,手機1號}性能很強悍!
手機{1,手機1號}性能很強悍!
手錶{2,手錶2號}時尚時尚最時尚!
手錶{2,手錶2號}時尚時尚最時尚!
3 深拷貝和淺拷貝
我們來理解兩個概念,淺拷貝和深拷貝。淺拷貝就是拷貝對象的內存地址,例如代碼:objA = objB;,其中這兩個obj無論哪一個對象裏的屬性發生變化都會影響到另一個對象,因爲這個賦值過程是一個淺拷貝,只拷貝了內存地址,就是它們實際上還是指向同一個對象。而深拷貝是創建出一個全新的對象,新舊兩個對象不會因爲其中一方對象的改變的發生影響。
那麼我們在上述實現的示例中是淺拷貝還是深拷貝?答案是淺拷貝。我們下面就來改進上述產品類來舉出一個反例:
產品類:本次新在硬件抽象類中將意新增了一個Map和自定義Cpu類:
public abstract class Hardware implements Cloneable {
protected int mId;
protected String mName;
protected Map<String, Integer> mScore;
protected Cpu mCpu;
public void setId(int id) {
mId = id;
}
public int getId() {
return mId;
}
public void setName(String name) {
mName = name;
}
public void setScore(Map<String, Integer> score) {
mScore = score;
}
public void setCpuType(String type) {
mCpu.setType(type);
}
@Override
public Object clone() {
Object clone = null;
try {
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
public abstract void showInfo();
}
public class Phone extends Hardware {
public Phone(int id, String name, Map<String, Integer> score, Cpu cpu) {
mId = id;
mName = name;
mScore = score;
mCpu = cpu;
}
@Override
public void showInfo() {
System.out.println("手機{" + mId + "," + mName + "}性能很強悍, CPU:" + mCpu.getType() + ", 跑分:" + mScore);
}
}
public class Cpu {
private String mType;
public Cpu(String type) {
mType = type;
}
public void setType(String type) {
this.mType = type;
}
public String getType() {
return mType;
}
}
調用者:
Map<String, Integer> score = new HashMap<>();
score.put("屏幕跑分", 999);
score.put("內存跑分", 888);
Map<String, Integer> score2 = new HashMap<>();
score2.put("屏幕跑分", 100);
score2.put("內存跑分", 200);
Phone phone1 = new Phone(1, "手機1號", score, new Cpu("驍龍865"));
phone1.showInfo();
Phone phone2 = (Phone)phone1.clone();
phone1.setId(2);
phone1.setName("手機2號");
phone1.setScore(score2);
phone1.setCpuType("驍龍770");
phone2.showInfo();
運行輸出:
手機{1,手機1號}性能很強悍, CPU:驍龍865, 跑分:{屏幕跑分=999, 內存跑分=888}
手機{1,手機1號}性能很強悍, CPU:驍龍770, 跑分:{屏幕跑分=999, 內存跑分=888}
解說:
爲了演示,我們本次省略了產品工廠(產品緩存)的角色,直接讓調用者來克隆產品對象。調用者中先創建出一個phone1對象作爲原型,接着克隆出phone2對象,然後對原型phone1進行4種不同類型的屬性的修改,分別是:int、String、Map和自定義的CPU。從輸出結可見,除了CPU類被修改,其餘屬性沒有因原型修改而被修改。
原因其實是因爲Cloneable的clone方法提供一種是淺拷貝,而且這個拷貝是有選擇性,比如基本類型如:int、float、double等都是拷貝其值,而String、Integer、Float、Double等和集合它們雖然是引用類型,但比較特殊它們也是拷貝其值的,而自定義的類就不行了,它只拷貝了地址,所以在phone1後面修改了後,隨着也會影響到phone2的值。
3.1 實現深拷貝改進
使產品類裏所引用到的類也繼承於Cloneable,並實現clone方法:
public class Cpu implements Cloneable {
private String mType;
public Cpu(String type) {
mType = type;
}
public void setType(String type) {
this.mType = type;
}
public String getType() {
return mType;
}
@Override
public Object clone() {
Object clone = null;
try {
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
對產品抽象類中clone方法進行添加對類內其它自定義類的克隆,這裏添加了代碼:mCpu = (Cpu)mCpu.clone();
public abstract class Hardware implements Cloneable {
protected int mId;
protected String mName;
protected Map<String, Integer> mScore;
protected Cpu mCpu;
public void setId(int id) {
mId = id;
}
public int getId() {
return mId;
}
public void setName(String name) {
mName = name;
}
public void setScore(Map<String, Integer> score) {
mScore = score;
}
public void setCpuType(String type) {
mCpu.setType(type);
}
@Override
public Object clone() {
Object clone = null;
try {
clone = super.clone();
mCpu = (Cpu)mCpu.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
public abstract void showInfo();
}
經修改後運行輸出:
手機{1,手機1號}性能很強悍, CPU:驍龍865, 跑分:{屏幕跑分=999, 內存跑分=888}
手機{1,手機1號}性能很強悍, CPU:驍龍865, 跑分:{屏幕跑分=999, 內存跑分=888}
3.2 使用Serializable實現深拷貝
除了上面所述讓產口類中所引用的所有類也繼承Cloneable實現clone來實現深拷貝外,還可以使用serializable接口通過讀取二進制流序列化的方式來實現對象的深複製。
使產品類裏所引用到的類直接繼承Serializable即可:
public class Cpu implements Serializable {
private String mType;
public Cpu(String type) {
mType = type;
}
public void setType(String type) {
this.mType = type;
}
public String getType() {
return mType;
}
}
對產品抽象類也經繼承Serializable,並修改其clone方法,代碼如:
public abstract class Hardware implements Cloneable, Serializable {
protected int mId;
protected String mName;
protected Map<String, Integer> mScore;
protected Cpu mCpu;
public void setId(int id) {
mId = id;
}
public int getId() {
return mId;
}
public void setName(String name) {
mName = name;
}
public void setScore(Map<String, Integer> score) {
mScore = score;
}
public void setCpuType(String type) {
mCpu.setType(type);
}
@Override
public Object clone() {
// Object clone = null;
// try {
// clone = super.clone();
// mCpu = (Cpu)mCpu.clone();
// } catch (CloneNotSupportedException e) {
// e.printStackTrace();
// }
// return clone;
Object clone = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(this);
ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
clone = objectInputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return clone;
}
public abstract void showInfo();
}
經修改後運行輸出:
手機{1,手機1號}性能很強悍, CPU:驍龍865, 跑分:{屏幕跑分=999, 內存跑分=888}
手機{1,手機1號}性能很強悍, CPU:驍龍865, 跑分:{屏幕跑分=999, 內存跑分=888}
4 總結
原型模式實現原理很簡單。因爲它是爲了性能優化而生,所以它的使用場景也很明確就是當初始化類的成本比緩存已有對象原型的成本更高時建議使用原型模式。使用上要注意就是克隆對象時,原類的構造方法是不會被執行的,還有就是考慮類中字段是否滿足深拷貝,如果不滿足就要對其進行支持改進,支持方式可以是讓引用到的類也繼承Cloneable接口並實現clone方法或者使用serializable接口通過讀取二進制流序列化的方式來實現對象的深複製。