「很差意思,我是臥底!哇哈哈哈~」額......自從寫了上一篇的觀察者模式,就一直沉浸在這個角色當中,沒法自撥。昨晚在看《使徒行者2》,有一集說到啊炮仗哥印鈔票,我去,這就是想印多少就印多少的節奏。git
可是我以爲他們印鈔票的方法太low了,就用那「哧咔,哧咔~」的老機器沒日沒夜的印,看着都着急。github
這裏咱們能夠用原型模式優化印鈔票的致富之路,爲何,繼續往下看......設計模式
用原型實例指定全部建立對象的類型,而且經過複製這個拷貝建立新的對象。數組
1)必須存在一個現有的對象,也就是原型實例,經過原型實例建立新對象。bash
2)在Java中,實現Cloneable,而且由於全部的類都繼承Object類,重寫clone()方法來實現拷貝。ide
大量的對象,而且類初始化時消耗的資源多。沒人會嫌錢多的吧,除了某雲。post
這些鈔票的信息屬性基本一致,能夠調整個別的屬性。性能
印鈔票的工序很是複雜,須要進行繁瑣的數據處理。學習
從上面的UML圖能夠看出,原型模式涉及到的角色有以下三個:優化
- 客戶端角色:負責建立對象的請求。
- 抽象原型角色:該角色是一個抽象類或者是接口,提供拷貝的方法。
- 具體原型角色:該角色是拷貝的對象,須要重寫抽象原型的拷貝方法,實現淺拷貝或者深拷貝。
一塊兒來印鈔票,鈔票實例必須實現Cloneable接口,該接口只充當一個標記,而後重寫clone方法,具體原型角色代碼以下:
public class Money implements Cloneable {
private int faceValue;
private Area area;
public int getFaceValue() {
return faceValue;
}
public void setFaceValue(int faceValue) {
this.faceValue = faceValue;
}
public Money(int faceValue, Area area) {
this.faceValue = faceValue;
this.area = area;
}
public Area getArea() {
return area;
}
public void setArea(Area area) {
this.area = area;
}
public String getUnit() {
return unit;
}
public void setUnit(String unit) {
this.unit = unit;
}
@Override
protected Money clone() throws CloneNotSupportedException {
return (Money) super.clone();
}
}複製代碼
Area類代碼以下:
public class Area {
// 鈔票單位
private String unit;
public String getUnit() {
return unit;
}
public void setUnit(String unit) {
this.unit = unit;
}
}複製代碼
看看客戶端如何實現鈔票的拷貝,代碼以下:
public class Client {
public static void main(String[] args) {
Area area = new Area();
area.setUnit("RMB");
// 原型實例,100RMB的鈔票
Money money = new Money(100, area);
for (int i = 1; i <= 3; i++) {
try {
Money cloneMoney = money.clone();
cloneMoney.setFaceValue(i * 100);
System.out.println("這張是" + cloneMoney.getFaceValue() + cloneMoney.getArea().getUnit() + "的鈔票");
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
}複製代碼
大把大把的鈔票出來了
這張是100RMB的鈔票
這張是200RMB的鈔票
這張是300RMB的鈔票
從上面並無看到抽象原型角色的代碼,那該角色在哪?Object就是這個抽象原型角色,由於Java中全部的類都默認繼承Objet,在這提供clone方法。
在使用原型模式的時候,經常須要注意用的究竟是淺拷貝仍是深拷貝,固然這必須結合實際的項目需求。下面來了解學習這兩種拷貝的用法和區別:
首先咱們來看一個例子,只改變客戶端代碼:
public class Client {
public static void main(String[] args) {
Area area = new Area();
area.setUnit("RMB");
// 原型實例,100RMB的鈔票
Money money = new Money(100, area);
try {
Money cloneMoney = money.clone();
cloneMoney.setFaceValue(200);
area.setUnit("美圓");
System.out.println("原型實例的面值:" + money.getFaceValue() +money.getArea().getUnit());
System.out.println("拷貝實例的面值:" + cloneMoney.getFaceValue() + cloneMoney.getArea().getUnit());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}複製代碼
運行結果以下:
原型實例的面值:100美圓
拷貝實例的面值:200美圓
見鬼了,明明就把原型實例的單位改爲了美圓而已,拷貝實例怎麼也會跟着改變的。哪裏有鬼?實際上是Java在搞鬼。咱們用的是Object的clone方法,而該方法只拷貝按值傳遞的數據,好比String類型和基本類型,但對象內的數組、引用對象都不拷貝,也就是說內存中原型實例和拷貝實例指向同一個引用對象的地址,這就是淺拷貝。淺拷貝的內存變化以下圖:
從上圖能夠看出,淺拷貝先後的兩個實例對象共同指向同一個內存地址,即它們共有擁有area1實例,同時也存在着數據被修改的風險。注意,這裏不可拷貝的引用對象是指可變的類成員變量。
一樣的看例子,客戶端代碼以下:
public class Client {
public static void main(String[] args) {
Area area = new Area();
area.setUnit("RMB");
// 原型實例,100RMB的鈔票
Money money = new Money(100, area);
try {
Money cloneMoney = money.clone();
cloneMoney.setFaceValue(200);
area.setUnit("美圓");
System.out.println("原型實例的面值:" + money.getFaceValue() + money.getArea().getUnit());
System.out.println("拷貝實例的面值:" + cloneMoney.getFaceValue() + cloneMoney.getArea().getUnit());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}複製代碼
運行結果以下:
原型實例的面值:100美圓
拷貝實例的面值:200RMB
咦~這客戶端代碼不是跟淺拷貝的同樣嗎,可是運行結果卻又不同了。關鍵就在,實現深拷貝就須要徹底的拷貝,包括引用對象,數組的拷貝。因此Area類也實現了Cloneable接口,重寫了clone方法,代碼以下:
public class Area implements Cloneable{
// 鈔票單位
private String unit;
public String getUnit() {
return unit;
}
public void setUnit(String unit) {
this.unit = unit;
}
@Override
protected Area clone() throws CloneNotSupportedException {
Area cloneArea;
cloneArea = (Area) super.clone();
return cloneArea;
}
}複製代碼
另外,在Money鈔票類的clone方法增長拷貝Area的代碼:
public class Money implements Cloneable, Serializable {
private int faceValue;
private Area area;
public int getFaceValue() {
return faceValue;
}
public void setFaceValue(int faceValue) {
this.faceValue = faceValue;
}
public Money(int faceValue, Area area) {
this.faceValue = faceValue;
this.area = area;
}
public Area getArea() {
return area;
}
public void setArea(Area area) {
this.area = area;
}
@Override
protected Money clone() throws CloneNotSupportedException {
Money cloneMoney = (Money) super.clone();
cloneMoney.area = this.area.clone(); // 增長Area的拷貝
return cloneMoney;
}
}複製代碼
深拷貝的內存變化以下圖:
深拷貝除了須要拷貝值傳遞的數據,還須要拷貝引用對象、數組,即把全部引用的對象都拷貝。須要注意的是拷貝的引用對象是否還有可變的類成員對象,若是有就繼續對該成員對象進行拷貝,如此類推。因此使用深拷貝是注意分析拷貝有多深,以避免影響性能。
序列化實現深拷貝
這是實現深拷貝的另外一種方式,經過二進制流操做對象,從而達到深拷貝的效果。把對象寫到流裏的過程是序列化過程,而把對象從流中讀出來的過程則叫反序列化過程。深拷貝的過程就是把對象序列化(寫成二進制流),而後再反序列化(從流裏讀出來)。注意,在Java中,經常能夠先使對象實現Serializable接口,包括引用對象也要實現Serializable接口,否則會拋NotSerializableException。
只要修改Money,代碼以下:
public class Money implements Serializable {
private int faceValue;
private Area area;
public int getFaceValue() {
return faceValue;
}
public void setFaceValue(int faceValue) {
this.faceValue = faceValue;
}
public Money(int faceValue, Area area) {
this.faceValue = faceValue;
this.area = area;
}
public Area getArea() {
return area;
}
public void setArea(Area area) {
this.area = area;
}
@Override
protected Money clone() throws CloneNotSupportedException {
Money money = null;
try {
// 調用deepClone,而不是Object的clone方法
cloneMoney = (Money) deepClone();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return cloneMoney;
}
// 經過序列化深拷貝
public Object deepClone() throws IOException, ClassNotFoundException {
//將對象寫到流裏
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
//從流裏讀回來
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
}複製代碼
一樣運行客戶端代碼,最後來看看結果:
原型實例的面值:100美圓
拷貝實例的面值:200RMB
1)提升性能。不用new對象,消耗的資源少。
1)淺拷貝時須要實現Cloneable接口,深拷貝則要特別留意是否有引用對象的拷貝。
原型模式自己比較簡單,重寫Object的clone方法,實現淺拷貝仍是深拷貝。重點在理解淺拷貝和深拷貝,這是比較細但又重要,卻每每被忽略的知識點。好啦,原型模式就到這了,下一篇是策略模式,敬請關注,拜拜!
設計模式Java源碼GitHub下載:https://github.com/jetLee92/DesignPattern