今天我們來講一下原型模式。老樣子,我們先舉一個案例: 一、案例 我們找工作,需要投簡歷,一份簡歷是不夠的,我們需要多複製幾分簡歷。 好,我們用簡單的控制台程式來完成上述的需求。 二、演繹 1、第一步演繹 客戶端調用: OK,我們實現了上述的功能,搞了三份簡歷出來。我們可以通過複製粘貼,複製更多分簡歷 ...
今天我們來講一下原型模式。老樣子,我們先舉一個案例:
一、案例
我們找工作,需要投簡歷,一份簡歷是不夠的,我們需要多複製幾分簡歷。
好,我們用簡單的控制台程式來完成上述的需求。
二、演繹
1、第一步演繹
1 /// <summary> 2 /// 簡歷類 3 /// </summary> 4 class Resume 5 { 6 private string name;//姓名 7 private string sex;//性別 8 private string age;//年齡 9 private string timeArea;//工作時間 10 private string company;//工作單位 11 12 public Resume(string name) 13 { 14 this.name = name; 15 } 16 //設置個人信息 17 public void SetPersonalInfo(string sex, string age) 18 { 19 this.sex = sex; 20 this.age = age; 21 } 22 //設置工作經歷 23 public void SetWorkExperience(string timeArea, string company) 24 { 25 this.timeArea = timeArea; 26 this.company = company; 27 } 28 //顯示 29 public void Display() 30 { 31 Console.WriteLine($"{name}{sex}{age}"); 32 Console.WriteLine($"工作經歷:{timeArea}{company}"); 33 } 34 }
客戶端調用:
1 public static void Main() 2 { 3 Resume a = new Resume("小魔王"); 4 a.SetPersonalInfo("男", "29"); 5 a.SetWorkExperience("2000-2000", "XX公司"); 6 7 Resume b = new Resume("小魔王"); 8 b.SetPersonalInfo("男", "29"); 9 b.SetWorkExperience("2000-2000", "XX公司"); 10 11 Resume c = new Resume("小魔王"); 12 c.SetPersonalInfo("男", "29"); 13 c.SetWorkExperience("2000-2000", "XX公司"); 14 15 a.Display(); 16 b.Display(); 17 c.Display(); 18 Console.Read(); 19 }
OK,我們實現了上述的功能,搞了三份簡歷出來。我們可以通過複製粘貼,複製更多分簡歷出來。
那麼問題來了。
①需要三份簡歷,客戶端就需要實例化三次,如果需要20份簡歷,100份簡歷,那麼客戶端豈不是要瘋掉了。
②如果我簡歷上寫錯了一個字,年齡29要改成30,那麼客戶端豈不是需要修改3次,20份簡歷的話,就需要修改20次,好恐怖啊。
2、第二步演繹
針對上述問題,我們將代碼做一下修改:
我們將客戶端進行一下優化:
1 Resume a = new Resume("小魔王"); 2 a.SetPersonalInfo("男", "29"); 3 a.SetWorkExperience("2000-2000", "XX公司"); 4 Resume b = a; 5 Resume c = a; 6 a.Display(); 7 b.Display(); 8 c.Display(); 9 Console.Read(); 10 }
哈哈,這樣的話,省卻了不少的代碼呢。
好,我們來看一看他的本質, Resume b = a;Resume c = a; 實際上是傳的引用,而不是傳值,就如同,有b、c兩張紙寫著簡歷在a處一樣,實際沒有任何內容。
好,那麼接下來,我們正式的給大家介紹一下原型模式,他很好的解決了上述的問題。
什麼是原型模式呢?
gof 給了我們很好的定義:原型模式,用原型實例指定創建對象的種類,並通過拷貝這些原型創建新的對象。
說白了,原型模式就是從一個對象創造另外一個可定製的對象,而且不需要知道任何的創造細節。
好,下麵我們來看一下最基本的原型模式的代碼
1 /// <summary> 2 /// 原型類 3 /// </summary> 4 internal abstract class Prototype 5 { 6 public string Id { get; } 7 8 protected Prototype(string id) 9 { 10 this.Id = id; 11 } 12 13 public abstract Prototype Clone(); 14 } 15 /// <summary> 16 /// 具體的原型方法 17 /// </summary> 18 class ConcretePrototype1 : Prototype 19 { 20 public ConcretePrototype1(string id) : base(id) 21 { 22 23 } 24 //克隆 25 public override Prototype Clone() 26 { 27 return (Prototype) this.MemberwiseClone();//MemberwiseClone()這個方法大家可以去百度谷歌一下。 28 } 29 }
客戶端調用
1 public static void Main() 2 { 3 ConcretePrototype1 p1 = new ConcretePrototype1("I"); 4 ConcretePrototype1 c1 = (ConcretePrototype1) p1.Clone(); 5 Console.WriteLine($"Cloned:{c1.Id}"); 6 Console.ReadKey(); 7 }
好了,上面就是原型模式的基本代碼,核心就是一個Clone() 方法。
因為就克隆而言,在我們的日常編碼中,太常用了,所以,.net在System命名空間中提供了一個ICloneable的介面,這個介面中有一個唯一的方法Clone(),跟我們的抽象類Prototype很相似,所以,以後我們想要實現原型模式,就不用費力去寫Prototype這個抽象類了,只需要將具體的原型類繼承ICloneable這個介面即可,就能實現原型模式了。
好,下麵我們就將我們文章開頭中提到的案例用原型模式來寫一遍。
3、第三步演繹
案例中我麽Resume類只需要繼承ICloneable介面,然後實現這個介面中的Clone()方法就可以了。其他的不用變。
1 class Resume:ICloneable 2 { 3 private string name;//姓名 4 private string sex;//性別 5 private string age;//年齡 6 private string timeArea;//工作時間 7 private string company;//工作單位 8 9 public Resume(string name) 10 { 11 this.name = name; 12 } 13 //設置個人信息 14 public void SetPersonalInfo(string sex, string age) 15 { 16 this.sex = sex; 17 this.age = age; 18 } 19 //設置工作經歷 20 public void SetWorkExperience(string timeArea, string company) 21 { 22 this.timeArea = timeArea; 23 this.company = company; 24 } 25 //顯示 26 public void Display() 27 { 28 Console.WriteLine($"{name}{sex}{age}"); 29 Console.WriteLine($"工作經歷:{timeArea}{company}"); 30 } 31 //實現ICloneable介面中的Clone()方法 32 public object Clone() 33 { 34 return this.MemberwiseClone(); 35 } 36 }
客戶端
1 public static void Main() 2 { 3 //第一份簡歷 4 Resume a = new Resume("小魔王"); 5 a.SetPersonalInfo("男", "29"); 6 a.SetWorkExperience("2000-2000","XX公司"); 7 8 //生成一份新簡歷 9 Resume b = (Resume) a.Clone(); 10 //可以對新簡歷的細節進行修改。 11 b.SetWorkExperience("1998-1999","AA公司"); 12 13 Resume c = (Resume) a.Clone(); 14 c.SetPersonalInfo("男", "28"); 15 16 a.Display(); 17 b.Display(); 18 c.Display(); 19 Console.ReadKey(); 20 }
好了,下麵我們來看一下這個原型模式有什麼好處。
相比之前的代碼,每new一次,就會調用一下構造函數,如果創造這個對象的過程很複雜,也就是說,構造函數很複雜的話,那麼調用構造函數執行起來就非常的慢,浪費很多的資源。這麼多次的執行這個初始化操作,效率實在是太低了。所以,一般在初始化i信息不變的情況下,克隆是最好的變法,既隱藏了對象創建的細節,又提高的效率。
原型模式就這麼結束了嗎?那我在給小伙伴們挖個坑來跳跳,哈哈。
現在 Resume中的欄位都是string類型的,也就是說都是值類型的。如果,我們將Resume中的欄位換成引用類型的,將會出現怎樣的效果呢?
來,我們來變一下。
我們增加一個工作經歷的類
1 class WorkExperience 2 { 3 public string workDate { get; set; } 4 public string company { get; set; } 5 }
然後,Resume類中就這麼寫了
1 class Resume : ICloneable 2 { 3 private string name;//姓名 4 private string sex;//性別 5 private string age;//年齡 6 private WorkExperience work; 7 8 public Resume(string name) 9 { 10 this.name = name; 11 work = new WorkExperience(); 12 } 13 //設置個人信息 14 public void SetPersonalInfo(string sex, string age) 15 { 16 this.sex = sex; 17 this.age = age; 18 } 19 //設置工作經歷 20 public void SetWorkExperience(string timeArea, string company) 21 { 22 work.workDate = timeArea; 23 work.company = company; 24 } 25 //顯示 26 public void Display() 27 { 28 Console.WriteLine($"{name}{sex}{age}"); 29 Console.WriteLine($"工作經歷:{ work.workDate}{ work.company}"); 30 } 31 //實現ICloneable介面中的Clone()方法 32 public object Clone() 33 { 34 return this.MemberwiseClone(); 35 } 36 }
客戶端:
1 public static void Main() 2 { 3 //第一份簡歷 4 Resume a = new Resume("小魔王"); 5 a.SetPersonalInfo("男", "29"); 6 a.SetWorkExperience("2000-2000", "XX公司"); 7 8 //生成一份新簡歷 9 Resume b = (Resume)a.Clone(); 10 //可以對新簡歷的細節進行修改。 11 b.SetWorkExperience("1998-1999", "AA公司"); 12 13 Resume c = (Resume)a.Clone(); 14 c.SetPersonalInfo("男", "28"); 15 16 a.Display(); 17 b.Display(); 18 c.Display(); 19 Console.ReadKey(); 20 }
好,我們來運行一下。
嗯?有什麼不對嗎?這不是我預期的效果啊~
工作經歷結果就是我們最後設置的那個值,這是什麼原因呢? 我們可以查一下MemberwiseClone()就應該知道是什麼原因了。
好,那麼我們該如何解決這個問題呢?
1、將WorkExperience 類 實現 ICloneable 介面
1 class WorkExperience:ICloneable 2 { 3 public string workDate { get; set; } 4 public string company { get; set; } 5 public object Clone() 6 { 7 return MemberwiseClone(); 8 } 9 }
2、修改Resume類
1 class Resume : ICloneable 2 { 3 private string name;//姓名 4 private string sex;//性別 5 private string age;//年齡 6 private WorkExperience work; 7 8 public Resume(string name) 9 { 10 this.name = name; 11 work = new WorkExperience(); 12 } 13 //私有構造函數,以便克隆工作經歷的數據 14 private Resume(WorkExperience work) 15 { 16 this.work = (WorkExperience)work.Clone(); 17 } 18 //設置個人信息 19 public void SetPersonalInfo(string sex, string age) 20 { 21 this.sex = sex; 22 this.age = age; 23 } 24 //設置工作經歷 25 public void SetWorkExperience(string timeArea, string company) 26 { 27 work.workDate = timeArea; 28 work.company = company; 29 } 30 //顯示 31 public void Display() 32 { 33 Console.WriteLine($"{name}{sex}{age}"); 34 Console.WriteLine($"工作經歷:{ work.workDate}{ work.company}"); 35 } 36 //實現ICloneable介面中的Clone()方法 37 public object Clone() 38 { 39 Resume obj = new Resume(this.work);//調用/私有構造函數,將工作經歷克隆。 40 obj.name = this.name; 41 obj.sex = this.sex; 42 obj.age = this.age; 43 return obj; 44 } 45 }
好,下麵運行一下,果然達到了我們預期的效果。
這兩種克隆的方式分別叫做 淺複製,深複製。
其實,在一些特定的場合,我們會經常用到,例如: DataSet 中,有Clone 和 Copy 兩個方法,一個是淺複製,一個是深複製。
好了,說了這麼多了,小伙伴們好好體會一下吧!
幾天就講到這了,下一篇會講 模板方法模式
本系列將持續更新,喜歡的小伙伴可以點一下關註和推薦,謝謝大家的支持。