工廠+單例模式

神牛003發表於2017-03-01

分享部落格文章馬上一年了,還沒有設計模式方面的文章呢,因此本篇將和大家分享的是工廠模式和單例模式,這裡舉例的工廠模式例子很簡單應該是大家常用的寫法與邏輯,後續分享的文章會進一步擴充套件工廠模式的寫法,敬請期待;這裡同時也講解常用單例模式寫法,並簡單提取了一個單例模式通用方法供大家使用;本章內容希望大家能夠喜歡,也希望各位多多"掃碼支援"和"推薦"謝謝!如果您想和我們交流更多mvc相關資訊可以來Ninesky框架作者:洞庭夕照 指定的官方群:428310563交流;

 

» 工廠模式設計圖

» 工廠模式測試用例

» 單例模式講解

» 單例模式測試用例

 

下面一步一個腳印的來分享:

» 工廠模式設計圖

首先,用使用一個簡單的工廠模式為系統服務需要對其原理或者說流程有大概的瞭解,這裡先通過一幅粗糙的手工圖展示下流程:

看圖能明顯看到一個工廠池,這個工廠池作用就是如圖所示來建立不同的型別的物件,而這些不同型別的物件通常有一個或一些列相似點,因此這裡能提取出來一個基類(或介面),加工池建立物件後直接返回建立的子類(或實現介面的型別),通過暴露父類(介面)提供給呼叫方想要的物件,這種做法使得呼叫方在使用工廠模式的時候,不需要關注具體物件,只需要關注暴露的父類(介面)即可,這就是工廠模式的好處;

 

» 工廠模式測試用例

這裡舉例使用工廠模式的場景是:家長,老師,學生這些社會人員的一次對話;他們都具有一個特性就是說話動作(當然特殊情況忽略),名字,稱呼等類似的屬性,因此這裡我們能夠抽出一個共同的基類MoPeopleClass

 1  /// <summary>
 2         /// 父類
 3         /// </summary>
 4         public class MoPeopleClass
 5         {
 6             /// <summary>
 7             /// 型別
 8             /// </summary>
 9             public EmPeople People { get; set; }
10 
11             /// <summary>
12             /// 名字
13             /// </summary>
14             public string Name { get; set; }
15 
16             public MoPeopleClass(EmPeople people = EmPeople.家長)
17             {
18                 this.People = people;
19             }
20 
21             /// <summary>
22             /// 說話
23             /// </summary>
24             /// <param name="myName"></param>
25             /// <param name="mySayContent"></param>
26             /// <returns></returns>
27             public string Say(string mySayContent)
28             {
29                 if (string.IsNullOrWhiteSpace(this.Name)) { this.Name = "匿名"; }
30 
31                 return string.Format("【{1}】{0} {2}:{3}<br/>",
32                     this.Name,
33                     this.People,
34                     DateTime.Now.ToString("HH:mm:ss.fff"),
35                     mySayContent);
36             }
37         }

因為只需要測試只有說話交流的動作因此這個基類預設屬於家長型別,這裡我定義了一個人員分類的列舉EmPeople:

1 public enum EmPeople
2         {
3             家長 = 1,
4             老師 = 2,
5             學生 = 3,
6             校長 = 4,
7             校花 = 5
8         }

然後分別定義MoTeacher(老師類),學生類(MoStudent),繼承父類MoPeopleClass並繼承她的帶引數的建構函式:

 1 /// <summary>
 2         /// 老師類
 3         /// </summary>
 4         public class MoTeacher : MoPeopleClass
 5         {
 6             public MoTeacher() : base(EmPeople.老師) { }
 7 
 8         }
 9 
10         /// <summary>
11         /// 學生類
12         /// </summary>
13         public class MoStudent : MoPeopleClass
14         {
15             public MoStudent() : base(EmPeople.學生) { }
16         }

再來咋們就是定義工廠類MoSimpleFactory並且定義個根據人員型別返回對應的實體方法GetPeople

 1 /// <summary>
 2         /// 工廠類
 3         /// </summary>
 4         public class MoSimpleFactory
 5         {
 6 
 7             public MoPeopleClass GetPeople(EmPeople people)
 8             {
 9                 switch (people)
10                 {
11                     case EmPeople.家長:
12                         return new MoPeopleClass();
13                     case EmPeople.老師:
14                         return new MoTeacher();
15                     case EmPeople.學生:
16                         return new MoStudent();
17 
18                     default:
19                         throw new Exception("沒有找到對應型別");
20                 }
21             }
22         }

看起來的確簡單,那不廢話了來直接看在Action中的測試程式碼:

 1 var sbSimpleFactoryLog = new StringBuilder(string.Empty);
 2 
 3             SimpleFactory.MoSimpleFactory simpleFactory = new SimpleFactory.MoSimpleFactory();
 4             var p1 = simpleFactory.GetPeople(FactoryPattern.SimpleFactory.EmPeople.家長);
 5             p1.Name = "神牛步行1";
 6             sbSimpleFactoryLog.AppendFormat("{0}", p1.Say("老師你好"));
 7             var p2 = simpleFactory.GetPeople(FactoryPattern.SimpleFactory.EmPeople.老師);
 8             p2.Name = "神牛步行2";
 9             sbSimpleFactoryLog.AppendFormat("{0}", p2.Say("你好,家長"));
10 
11             var p3 = simpleFactory.GetPeople(FactoryPattern.SimpleFactory.EmPeople.學生);
12             p3.Name = "神牛步行3";
13             var say3 = p3.Say(string.Format("我是學生{0},老師您好。", p3.Name));
14             sbSimpleFactoryLog.AppendFormat("{0}", say3);
15             ViewBag.SimpleFactoryLog = sbSimpleFactoryLog;

能看出這裡通過類似程式碼: simpleFactory.GetPeople(FactoryPattern.SimpleFactory.EmPeople.家長) 來獲取不同型別的物件,工廠模式就是這樣至少需要一個引數來標記您獲取物件的型別,不然無法確定你想要的物件,再來看下頁面效果:

能從圖上看出來我們獲取到了3個不同型別的物件,並且執行了說的動作,這就是工廠模式的效果;本篇分享的是最基礎的工廠模式用法,後面文章會逐步的擴充套件敬請期待。

 

» 單例模式講解

首先,咋們需要明白單例模式的意義:系統全域性只會存在一個對應的例項(通俗點就是整個系統中,對於某個類只會存在一次new的結果);由於靜態變數屬於靜態儲存方式能夠被儲存到記憶體中的靜態資料區,所以通常單例模式都是用靜態變數來保持例項的唯一性,單例模式常用寫法有以下幾種:

1. 執行緒不安全模式

2. 執行緒安全模式(使用鎖來保證安全)

3. 靜態初始化

4. 延遲初始化

這裡還有其他的寫法,但是個人覺得不怎麼常用暫時忽略吧;這裡以父類(MoPeopleClass)為例,貼下具體程式碼:

1. 執行緒不安全模式

 1  static MoPeopleClass moPeopleClass = null;
 2             /// <summary>
 3             /// 呼叫方式MoPeopleClass.Current
 4             /// </summary>
 5             public static MoPeopleClass Current
 6             {
 7                 get
 8                 {
 9                     return moPeopleClass ?? new MoPeopleClass();
10                 }
11             }

2. 執行緒安全模式(使用鎖來保證安全)

 1   static object lockPeople = new object();
 2             static MoPeopleClass moPeopleClass = null;
 3             /// <summary>
 4             /// 呼叫方式MoPeopleClass.Current
 5             /// </summary>
 6             public static MoPeopleClass Current
 7             {
 8                 get
 9                 {
10                     if (moPeopleClass == null)
11                     {
12                         lock (lockPeople)
13                         {
14                             moPeopleClass = moPeopleClass ?? new MoPeopleClass();
15                         }
16                     }
17                     return moPeopleClass ?? new MoPeopleClass();
18                 }
19             }

第一種和第二種差別在於後者增加了一個鎖,這個鎖就能避免多執行緒呼叫的時候建立多個物件,值得注意的是lock的程式碼裡面再次通過 moPeopleClass ?? new MoPeopleClass(); 來判斷了如果不為空直接返回物件,因為在lock的時候可能其他地方在建立同樣的物件,因此需要再次做非空判斷;

3. 靜態初始化

 1 static MoPeopleClass moPeopleClass = new MoPeopleClass();
 2             /// <summary>
 3             /// 呼叫方式MoPeopleClass.Current
 4             /// </summary>
 5             public static MoPeopleClass Current
 6             {
 7                 get
 8                 {
 9                     return moPeopleClass;
10                 }
11             }

只能說這種方式程式碼實現最快,但是有個缺點是因為這裡用的是static靜態字元修飾的變數所以在建立例項後,會直接載入到記憶體中,不管您系統中是否有呼叫此例項,這種方式如果在物件多的話很容易造成記憶體問題;

4. 延遲初始化

 1 /// <summary>
 2         /// 父類
 3         /// </summary>
 4         public class MoPeopleClass
 5         {
 6 
 7             /// <summary>
 8             /// 呼叫方式MoPeopleClass.Current
 9             /// </summary>
10             public static MoPeopleClass Current
11             {
12                 get
13                 {
14                     return MoPeopleExtend.moPeopleClass;
15                 }
16             }
17             //內部內來建立物件
18             private class MoPeopleExtend
19             {
20                 internal static readonly MoPeopleClass moPeopleClass = new MoPeopleClass();
21             }
22         }

這種方式使用內部類的方式建立例項;

 

» 單例模式測試用例

下面我這裡使用執行緒安全方式來建立單例,我這裡封裝了一個這種型別的通用方法,程式碼如下:

 1  /// <summary>
 2         /// 單例模式
 3         /// </summary>
 4         /// <typeparam name="T"></typeparam>
 5         public class Singleton<T> where T : class,new()
 6         {
 7             static object lockt = new object();
 8             static T t = null;
 9             /// <summary>
10             /// 加鎖單例
11             /// </summary>
12             public static T Current
13             {
14                 get
15                 {
16                     if (t == null)
17                     {
18                         lock (lockt)
19                         {
20                             t = t ?? new T();
21                         }
22                     }
23                     return t;
24                 }
25             }
26 
27             /// <summary>
28             /// 不加鎖單例
29             /// </summary>
30             public static T CurrentUnLock
31             {
32                 get
33                 {
34                     if (t == null)
35                     {
36                         t = t ?? new T();
37                     }
38                     return t;
39                 }
40             }
41         }
42     }

然後為了測試我增加列舉型別:

1 public enum EmPeople
2         {
3             家長 = 1,
4             老師 = 2,
5             學生 = 3,
6             校長 = 4,
7             校花 = 5
8         }

因為新增的“校長”和“校花”通常咋們認定一個學校就一人吧哈哈,那這個時候單例就有用武之地了;修改工廠類程式碼如下:

 1    /// <summary>
 2         /// 工廠類
 3         /// </summary>
 4         public class MoSimpleFactory
 5         {
 6 
 7             public MoPeopleClass GetPeople(EmPeople people)
 8             {
 9                 switch (people)
10                 {
11                     case EmPeople.家長:
12                         return new MoPeopleClass();
13                     //case EmPeople.老師:
14                     //     return new MoTeacher();
15                     //case EmPeople.學生:
16                     //    return new MoStudent();
17                     case EmPeople.校長:
18                         return ServiceCollection.Singleton<MoHeadeMaster>.Current;
19                     case EmPeople.校花:
20                         return ServiceCollection.Singleton<MoStudent>.Current;
21                 }
22             }
23 
24         }

注意為了方便我直接在工廠裡面只用了剛才通用的單例方法: ServiceCollection.Singleton<T> ,然後為了測試我們同時呼叫兩次工廠類建立兩次校長的物件和兩次校花的物件:

 1   var SingltonLog = new StringBuilder(string.Empty);
 2             var p0 = simpleFactory.GetPeople(FactoryPattern.SimpleFactory.EmPeople.校長);
 3             p0.Name = "神牛步行0";
 4             SingltonLog.AppendFormat("{0}", p0.Say("我是校長。"));
 5             var p00 = simpleFactory.GetPeople(FactoryPattern.SimpleFactory.EmPeople.校長);
 6             SingltonLog.AppendFormat("{0}", p00.Say("我是校長!"));
 7    
 8             var p6 = simpleFactory.GetPeople(FactoryPattern.SimpleFactory.EmPeople.校花);
 9             p6.Name = "神牛步行6";
10             SingltonLog.AppendFormat("{0}", p6.Say("我是校花。"));
11             var p66 = simpleFactory.GetPeople(FactoryPattern.SimpleFactory.EmPeople.校花);
12             SingltonLog.AppendFormat("{0}", p66.Say("我是校花!"));
13 
14             ViewBag.SingltonLog = SingltonLog;

注意了這裡第一次呼叫工廠生成校長例項後,我給校長賦了名稱,但是第二次沒有,校花也是如此,通常來說第二次沒有賦名稱應該會顯示“匿名”,但由於這裡用了單例,在第一次例項化校長(或校花)後我賦值了名稱,第二次獲取的校長(校花)其實和第一次是同一個例項獨享(通俗點就是同一記憶體地址),因此第二次顯示的名稱應該和第一次的一樣才對;咋們執行看效果:

從效果圖能看出咋們的單例模式成功了,為了進一步驗證單例的存在,我們重新建立一個Action和試圖並錄入如下程式碼:

 1   public ActionResult FatoryPatternTest()
 2         {
 3             var SingltonLog = new StringBuilder(string.Empty);
 4 
 5             SimpleFactory.MoSimpleFactory SimpleFactory = new SimpleFactory.MoSimpleFactory();
 6             var p00 = SimpleFactory.GetPeople(FactoryPattern.SimpleFactory.EmPeople.校長);
 7             SingltonLog.AppendFormat("{0}", p00.Say("我是校長!"));
 8             var p66 = SimpleFactory.GetPeople(FactoryPattern.SimpleFactory.EmPeople.校花);
 9             SingltonLog.AppendFormat("{0}", p66.Say("我是校花!"));
10 
11             ViewBag.SingltonLog = SingltonLog;
12 
13             return View();
14         }
View Code

此時我們並沒有賦值暱稱屬性,按照單例模式的說明這個時候由於前一個頁面有賦值名稱,這個測試新頁面應該也會顯示名稱才是真確的:

和咋們預想的情況一樣,這就是單例模式;好了本次分享到這裡就結束了,馬上快到3.8婦女節了,如果你想給你的她或者自己買件衣服或者裙子,不妨來這裡看看服裝店:神牛衣櫃3,謝謝你的支援。

相關文章