我的Java设计模式-原型模式
“不好意思,我是臥底!哇哈哈哈~”額......自從寫了上一篇的觀察者模式,就一直沉浸在這個角色當中,無法自撥。昨晚在看《使徒行者2》,有一集說到啊炮仗哥印鈔票,我去,這就是想印多少就印多少的節奏。
但是我覺得他們印鈔票的方法太low了,就用那“哧咔,哧咔~”的老機器沒日沒夜的印,看著都著急。
這里我們可以用原型模式優化印鈔票的致富之路,為什么,繼續往下看......
一、原型模式
定義
??用原型實例指定所有創建對象的類型,并且通過復制這個拷貝創建新的對象。
特點
??1)必須存在一個現有的對象,也就是原型實例,通過原型實例創建新對象。
??2)在Java中,實現Cloneable,并且因為所有的類都繼承Object類,重寫clone()方法來實現拷貝。
使用場景
大量的對象,并且類初始化時消耗的資源多。沒人會嫌錢多的吧,除了某云。
這些鈔票的信息屬性基本一致,可以調整個別的屬性。
印鈔票的工序非常復雜,需要進行繁瑣的數據處理。
UML圖
從上面的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;}@Overrideprotected 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;}@Overrideprotected 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;}@Overrideprotected 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;}@Overrideprotected 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
轉載于:https://www.cnblogs.com/AndroidJet/p/7737658.html
總結
以上是生活随笔為你收集整理的我的Java设计模式-原型模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 人生不只是上坡路
- 下一篇: Spring学习笔记--自动装配Bean