原型模式
原型模式,用起來其實就是做clone操作,clone一個對象,越過構造器,在特定使用場景下增加效率。
UML
使用場景:
- 類初始化需要消耗很多資源,比較耗時。
- new方式非常繁瑣,還涉及到權限之類的。用clone就減少操作。
- 一個對象需要提供給其他對象使用,而且各個使用都可能改到值,可以考慮用原型模式,做保護性拷貝。
先說說原型模式主要的淺拷貝和深拷貝:
淺拷貝:
可被clone的對象:
- 實現 Cloneable接口
- 重寫clone方法
- 先clone,再給參數賦值
public class Document implements Cloneable {private String mText;private ArrayList<String> list = new ArrayList<>();public String getmText() {return mText;}public void setmText(String mText) {this.mText = mText;}@Overrideprotected Document clone() throws CloneNotSupportedException {Document document = (Document) super.clone();document.mText = this.mText;document.list = this.list;return document;}
}
復制代碼
深拷貝:
對于上面ArrayList,它自身實現類cloneable接口的,所以它本身也是可以clone的,對于對象,賦值后,是指向同一個地址的,對于淺拷貝來說,如果改了list對象,那么原有的對象list也會被修改到。所以對于list對象,我們也需要拷貝一下,修改如下。
public class Document implements Cloneable {private String mText;private ArrayList<String> list = new ArrayList<>();public String getmText() {return mText;}public void setmText(String mText) {this.mText = mText;}@Overrideprotected Document clone() throws CloneNotSupportedException {Document document = (Document) super.clone();document.mText = this.mText;document.list = (ArrayList<String>) this.list.clone();return document;}
}復制代碼
檢驗
首先:對于淺拷貝
- 我們用下面一段代碼來做檢驗:
Document document = new Document();document.setmText("11111100000");document.getList().add("00");System.out.println("內容原始:" + document.toString());Document document1 = document.clone();document1.setmText("111111");document1.getList().add("111");System.out.println("內容修改:" + document1.toString());System.out.println("內容原始:" + document.toString());復制代碼
- 輸出:
內容原始:Document{mText='11111100000', list=[00]}
內容修改:Document{mText='111111', list=[00, 111]}
內容原始:Document{mText='11111100000', list=[00, 111]}
復制代碼
這里很明顯的能看到,我們改了String類型,也改了list對象。但是輸出后,原始的String類型的text沒變,但是list卻被改變了。 這是為什么呢?
原因其實是: String類型是不可變類型,所以我們不推薦在for循環中使用+號來拼接一樣,因為每+一次就會創建一塊內存來裝。同理,這里的雖然拷貝了string類型,但是第二個對象修改的時候,string的text是指向了新的地址而不是把原地址數據給改了,所以原對象的text并沒被改。但list就不一樣了,改的是原對象地址的數據 所以我們才會需要深拷貝。
然后我們再檢驗一次深拷貝的:
輸出內容:
內容原始:Document{mText='11111100000', list=[00]}
內容修改:Document{mText='111111', list=[00, 111]}
內容原始:Document{mText='11111100000', list=[00]}復制代碼
我們使用深拷貝的時候,將對象list也clone了,就沒有指向的不再是原有地址了,所以改動也就影響不到原來數據咯。
另外:實現Cloneable接口只是一種方式。要實現clone也不一定非要實現這個接口(使用Object來重寫clone方法的話一定要實現,不然會拋出異常)。
如果使用new的方式并不耗資源等,clone的時候我們傳入自己,用new的效率或許會更高:
public Document(Document document) {this.mText = document.getmText();this.list = document.getList();}@Overridepublic Document clone() throws CloneNotSupportedException {Document document = new Document(this);return document;}
復制代碼
總結:原型模式在copy資源非常方便,當然拷貝不一定比new快,所以需要評估測試再考慮是否不用new