設計模式-七大設計原則-依賴倒轉原則
原型模式概述
原型模式是一種特殊的建立型模式,它通過複製一個已有物件來獲取更多相同或相似的物件。原型模式可以提高系統同類型物件的建立效率,簡化建立過程。
舉個栗子:你寫了一份簡歷,需要"複製"多份出來以便投給不同的企業,難道真的需要"手寫"多份出來嘛,那不是浪費紙張浪費筆墨嗎,後面我們會用程式碼來實現這個小栗子。
原型模式定義
使用原型例項指定待建立物件的型別,並通過複製這個原型來建立新的物件。
原型模式原理
將一個原型物件傳給要發動建立的物件(即客戶端物件),這個要發動建立的物件通過請求原型物件複製自己來實現新物件的建立過程。這種建立新物件的過程也稱為”克隆物件“,建立新物件的工廠就是原型類自身,工廠方法由負責複製原型物件的克隆方法來實現。
Tips:通過克隆物件建立的是全新的物件,它們在記憶體中擁有新的地址。通常對所克隆產生的新物件的建立過程。這種建立物件的過程也稱之為"克隆物件",建立新物件的工廠就是原型類自身,工廠方法由複製原型物件的克隆方法來實現。
原型模式的結構
- Prototype(抽象原型類):它是宣告克隆方法的介面,是所有具體原型類的公共父類,它可以是抽象類也可以是介面,甚至可以是具體實現類。
- ConcertePrototype(具體原型類):它實現在抽象原型類中宣告的克隆方法,在克隆方法中返回自己的一個克隆物件。
- Client:可以複製實現了原型介面的任何物件
原型模式的實現
原型模式的深拷貝與淺拷貝
首先我們先來看一段小樣例:
簡歷類:
public class Resume : ICloneable { public string Name { get; set; } public int Age { get; set; } public string Sex { get; set; } private WorkExperince workExperince; public Resume() { workExperince = new WorkExperince(); } public void SetWorkExperince(string workDate, string workCompany) { workExperince.WorkDate = workDate; workExperince.Company = workCompany; } public void Display() { Console.WriteLine($"個人資訊:\r\n{Name}\t{Sex}\t{Age}"); Console.WriteLine($"工作經歷:\r\n{workExperince.WorkDate}\t{workExperince.Company}\t"); } public object Clone() { return MemberwiseClone(); } }
工作經歷類:(簡歷類與工作經歷的類關係是1-->n)
public class WorkExperince
{
public string WorkDate { get; set; }
public string Company { get; set; }
}
假設我們現在需要3份簡歷,客戶端例項化3個:
static void Main(string[] args)
{
Resume resume1 = new Resume() { Name = "張三", Age = 22, Sex = "女" };
resume1.SetWorkExperince("2019-2020", "MicroSoft");
Resume resume2 = (Resume)resume1.Clone();
resume1.SetWorkExperince("2019-2020", "Tencent");
Resume resume3 = (Resume)resume1.Clone();
resume1.SetWorkExperince("2019-2020", "AliBaba");
resume1.Display();
resume2.Display();
resume3.Display();
Console.WriteLine("Press Any Key to end!");
Console.ReadKey();
}
輸出結果:
發現什麼問題了嗎?對,就是我們明明設定了3份不同的工作經歷,最後工作經歷卻是一樣的並且是最後一次設定的被"覆蓋"了。這是典型的淺表複製,因為工作經歷是一個引用型別,對於應引用型別的克隆,就只是複製了物件的引用,也就是記憶體指向的地址。
淺複製:被複制物件的所有變數都含有與原來相同的值,而所有的對其他物件的引用都仍然指向原來的物件。
那這顯然是沒有達到我們"複製簡歷"的要求呢?有淺克隆自然也有深克隆,那我們來試試深克隆,改造一下程式碼。
改造過程:
在簡歷類中新增一個私有建構函式
private Resume(WorkExperince work)
{
//提供Clone方法呼叫的私有建構函式,以便克隆"工作經歷"的資料
workExperince = (WorkExperince)work.Clone();
}
工作經歷類也繼承"ICloneable
"並實現
public class WorkExperince : ICloneable
{
public string WorkDate { get; set; }
public string Company { get; set; }
public object Clone()
{
return MemberwiseClone();
}
}
修改簡歷類中Clone方法的實現
Tips:呼叫私有建構函式,讓"工作經歷"克隆完成,然後再給這個簡歷的相關屬性賦值,最終返回一個深克隆的簡歷物件
public object Clone()
{
//return MemberwiseClone();
Resume resume = new Resume(workExperince);
resume.Name = Name;
resume.Age = Age;
resume.Sex = Sex;
return resume;
}
輸出結果:
OK。目標達成!
通過克隆實現
可以注意到,我們剛才的Clone方法是通過繼承ICloneable
來實現的。我們看下這個介面實現
通過觀察程式集進去發現ICloneable介面僅僅只有Clone()一個方法。另外可以看到返回的是一個object型別,F12進去
原型模式優缺點
優點
- 可以克隆物件, 而無需與它們所屬的具體類相耦合。
- 可以克隆預生成原型, 避免反覆執行初始化程式碼。
- 可以更方便地生成複雜物件。
- 可以用繼承以外的方式來處理複雜物件的不同配置。
缺點
- 克隆包含迴圈引用的複雜物件可能會非常麻煩。
原型模式使用場景
使用場景
- 如果你需要複製一些物件,同時又希望程式碼獨立於這些物件所屬的具體類, 可以使用原型模式。
- 果子類的區別僅在於其物件的初始化方式, 那麼你可以使用該模式來減少子類的數量。 別人建立這些子類的目的可能是為了建立特定型別的物件。
實現方式
-
建立原型介面, 並在其中宣告
克隆
方法。 如果你已有類層次結構, 則只需在其所有類中新增該方法即可。 -
原型類必須另行定義一個以該類物件為引數的建構函式。 建構函式必須複製引數物件中的所有成員變數值到新建實體中。 如果你需要修改子類, 則必須呼叫父類建構函式, 讓父類複製其私有成員變數值。
如果程式語言不支援方法過載, 那麼你可能需要定義一個特殊方法來複制物件資料。 在建構函式中進行此類處理比較方便, 因為它在呼叫
new
運算子後會馬上返回結果物件。 -
克隆方法通常只有一行程式碼: 使用
new
運算子呼叫原型版本的建構函式。 注意, 每個類都必須顯式重寫克隆方法並使用自身類名呼叫new
運算子。 否則, 克隆方法可能會生成父類的物件。 -
你還可以建立一箇中心化原型登錄檔, 用於儲存常用原型。
你可以新建一個工廠類來實現登錄檔, 或者在原型基類中新增一個獲取原型的靜態方法。 該方法必須能夠根據客戶端程式碼設定的條件進行搜尋。 搜尋條件可以是簡單的字串, 或者是一組複雜的搜尋引數。 找到合適的原型後, 登錄檔應對原型進行克隆, 並將複製生成的物件返回給客戶端。
最後還要將對子類建構函式的直接呼叫替換為對原型登錄檔工廠方法的呼叫。