上篇博客學習了Build模式,本篇博客學習一下原型模式,其實說到原型模式,剛開始聽的時候一臉懵逼,但是學習完了之後,真的是感覺簡單。
原型模式介紹
原型模式是創建型模式的一種,其實就是想要以某一個對象爲“模版”,“複製”出相同的對象,也就是“克隆”出一摸一樣的對象。原型模式多用於需要構建複雜的對象時使用。因爲此時“複製”一個對象比創建new一個對象效率更高。
原型模式定義
用原型對象的實例執行創建對象的種類,並通過拷貝創建出新的對象
原型模式的使用場景
- 創建new一個對象需要消耗非常多的資源,這個資源包括數據,硬件的消耗等,通過複製拷貝的方式避免過的消耗資源
- 一個對象可能在多個地方使用,並且使用的地方都可能修改這個對象的屬性值,此時應該拷貝若干份對象,給不同的調用者使用
- 通過new創建對象需要複雜的數據準備或者數據設置,這時可以直接拷貝複製對象。
一般來說,我們實現原型模式的方法有兩種:
- 原型對象實現Cloneable接口,然後在clone方法內,調用父類的對象clone方法拷貝對象。
- 不使用Cloneable接口,自行在clone方法內處理邏輯
原型模式的簡單實現(實現Cloneable接口)
示例:假設現在我們正在寫一個文檔Document,文檔中包含圖片和文字,用戶寫了很長的文檔,此時需要修改裏面的東西,但是呢,我們又不確定修改編輯後的內容是否會被採用,此時我們就可以拷貝一份文檔Document,這就是典型的:原型模式。
原型
public class Document implements Cloneable{
private String content; //內容
private ArrayList<String> imageList = new ArrayList<>(); //圖片
public Document() {
Log.e("Document:", "構造方法");
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public List<String> getImageList() {
return imageList;
}
public void addImage(String image) {
this.imageList.add(image);
}
@Override
protected Document clone() {
Document documentCopy = null;
try{
documentCopy = (Document) super.clone();
documentCopy.content = this.content;
documentCopy.imageList = this.imageList;
} catch (CloneNotSupportedException e) {
}
return documentCopy;
}
@Override
public String toString() {
String diver = "\n------------start-------------\n";
String contentString = "content = " + this.content + " \n";
String imageString = "";
for(String imageValue : imageList) {
imageString += "圖片:" + imageValue + " \n";
}
String end = "------------end-------------\n";
Log.e("DocumentL:", diver + contentString + imageString + end);
return diver + contentString + imageString + end;
}
}
在原型Document中,我們實現了Cloneable接口,在clone方法內,拷貝了Document對象
Document document = new Document();
document.setContent("我是內容");
document.addImage("我是圖片1");
document.addImage("我是圖片2");
document.toString();
//clone
Document documentClone = document.clone();
documentClone.setContent("我是修改後的內容");
documentClone.addImage("我是圖片3");
documentClone.toString();
//再次打印原版
document.toString();
拷貝的Document對象修改了文本內容,但是原型對象文本內容並沒有被修改。 但是呢,細心的同學可能發現了,拷貝的對象添加了一張圖片,而原型對象中的圖片也隨之被修改了(添加了)。這是爲什麼?
深拷貝和淺拷貝
出現上述問題的原因是因爲是屬於:淺拷貝。
關於深拷貝和淺拷貝,用C++的地址內存來說的話就是:不管是基本數據類型還是引用數據類型,都會在棧中分配一塊內存,基本數據類型在棧中的那塊內存存儲的直接是它的值,而引用數據類型在棧中存儲的是一個地址(相當於一個指針),而這個地址指向了堆中的真正的值。也就是說:引用數據類型真正的值是存放在堆內存中的。
淺拷貝:只用把自己賦值給新對象,而不管自己是什麼類型(引用數據類型還是基本數據類型);
深拷貝:如果是引用數據類型,就調用起自己的clone方法拷貝,如果是基本數據類型就直接賦值。
還記得我們最早學習Java的時候的:值傳遞和引用傳遞嗎?對,就是這個東西。我們的文本內容是:基本數據類型,而圖片對象是:引用數據類型。此時我們的引用數據類型就相當於是淺拷貝,也就是說:
//直接複製的是值
documentCopy.content = this.content;
//直接傳遞的是數組的引用
documentCopy.imageList = this.imageList;
知道原因後,我們修改clone方法如下:
@Override
protected Document clone() {
Document documentCopy = null;
try{
documentCopy = (Document) super.clone();
ArrayList<String> imageListClone = (ArrayList<String>) this.imageList.clone();
documentCopy.content = this.content;
documentCopy.imageList = imageListClone;
} catch (CloneNotSupportedException e) {
}
return documentCopy;
}
由於ArrayList類本身實現了Cloneable接口,所以可以直接調用其clone方法複製對象。此時打印結果正確。
原型模式的其它實現方式
Intent的使用相信是非常熟悉的,看如下代碼:
Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse("smsto:123456"));
intent.putExtra("sms_body", "Hello world!");
Intent cloneIntent = (Intent) intent.clone();
startActivity(cloneIntent);
上述代碼是發送短信。clone了Intent對象,我們看下clone方法的源碼:
@Override
public Object clone() {
return new Intent(this);
}
private Intent(Intent o, @CopyMode int copyMode) {
this.mAction = o.mAction;
this.mData = o.mData;
this.mType = o.mType;
this.mPackage = o.mPackage;
this.mComponent = o.mComponent;
if (o.mCategories != null) {
this.mCategories = new ArraySet<>(o.mCategories);
}
if (copyMode != COPY_MODE_FILTER) {
this.mFlags = o.mFlags;
this.mContentUserHint = o.mContentUserHint;
this.mLaunchToken = o.mLaunchToken;
if (o.mSourceBounds != null) {
this.mSourceBounds = new Rect(o.mSourceBounds);
}
if (o.mSelector != null) {
this.mSelector = new Intent(o.mSelector);
}
if (copyMode != COPY_MODE_HISTORY) {
if (o.mExtras != null) {
this.mExtras = new Bundle(o.mExtras);
}
if (o.mClipData != null) {
this.mClipData = new ClipData(o.mClipData);
}
} else {
if (o.mExtras != null && !o.mExtras.maybeIsEmpty()) {
this.mExtras = Bundle.STRIPPED;
}
// Also set "stripped" clip data when we ever log mClipData in the (broadcast)
// history.
}
}
}
在clone的方法內,直接創建了Intent對象。而沒有使用拷貝的方法創建,因此我們可以知道,在實際項目中,我們要根據自身情況看是否要:創建對象。
源碼示例地址:
GitHub示例源碼
GitHub示例源碼
GitHub示例源碼