軟件設計原則與模式

第九章 設計模式與原則

      軟件設計模式(Design pattern)是一套被反覆使用的代碼設計經驗總結。使用設計模式是爲了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。好的設計,成就好的做品。但在軟件設計的過程當中,如有一些設計原則(Design Principle)的約束,那咱們的軟件會重構得更好。設計模式和設計原則博大精深,須要咱們長時間的實踐和總結才能真正領悟到其真諦,本章首先以「觀察者模式」爲例,介紹設計模式在Windows Forms中的應用(其餘經常使用設計模式略),以後詳細介紹五大設計原則(簡稱Solid原則)。數據庫

9.1軟件的設計模式

9.1.1觀察者模式

      程序的運行意味着模塊與模塊之間、對象與對象之間不停地有數據交換,觀察者模式強調的就是,當一個目標自己的狀態發生改變時(或者知足某一條件),它會主動發出通知,通知對該變化感興趣的其餘對象,若是將通知者稱爲「Subject」(主體),將被通知者稱爲「Observer」(觀察者),具體結構圖以下:編程

 

 

圖9-1 觀察者模式中類關係圖設計模式

如上圖9-1所示,圖中將主體和觀察者的邏輯抽象出來兩個接口,分別爲:ISubject和IObserver,ISubject接口中包含一個通知觀察者的NotifyObservers方法、一個添加觀察者的AddObserver方法和一個RemoveObserver方法,IObserver接口中則只包含一個接受通知的Notify方法,ISubject和IObserver接口的關係爲:一對多,一個主體能夠通知多個觀察者。框架

      具體代碼實現以下:ide

  1 //Code 9-1
  2 
  3 interface ISubject  //NO.1
  4 
  5 {
  6 
  7          void NotifyObservers(string msg);
  8 
  9          void AddObserver(IObserver observer);
 10 
 11          void RemoveObserver(IObserver observer);
 12 
 13 }
 14 
 15 interface IObserver  //NO.2
 16 
 17 {
 18 
 19          void Notify(string msg);
 20 
 21 }
 22 
 23 class MySubject:ISubject
 24 
 25 {
 26 
 27          //
 28 
 29          ArrayList _observers_list = new ArrayList();
 30 
 31          public void AddObserver(IObserver observer)  //NO.3
 32 
 33          {
 34 
 35                    if(!_observers_list.Contains(observer))
 36 
 37                    {
 38 
 39                             _observers_list.Add(observer);
 40 
 41                    }
 42 
 43          }
 44 
 45          public void RemoveObserver(IObserver observer)  //NO.4
 46 
 47          {
 48 
 49                    if(_observers_list.Contains(observer))
 50 
 51                    {
 52 
 53                             _observers_list.Remove(observer);
 54 
 55                    }
 56 
 57          }
 58 
 59          public void NotifyObservers(string msg)  //NO.5
 60 
 61          {
 62 
 63                    foreach(IObserver observer in _observers_list)
 64 
 65                    {
 66 
 67                             observer.Notify(msg);
 68 
 69                    }
 70 
 71          }
 72 
 73          public void DoSomething()
 74 
 75          {
 76 
 77                    //
 78 
 79                    if(…)  //NO.6
 80 
 81                    {
 82 
 83                             NotifyObservers(…);
 84 
 85                    }
 86 
 87          }
 88 
 89 }
 90 
 91 class MyObserver:IObserver
 92 
 93 {
 94 
 95          public void Notify(string msg)
 96 
 97          {
 98 
 99                    Console.WriteLine(「receive msg :」 + msg);  //NO.7
100 
101          }
102 
103 }
104 
105 class YourObserver:IObserver
106 
107 {
108 
109          public void Notify(string msg)
110 
111          {
112 
113                    //send email to others  NO.8
114 
115          }
116 
117 }
View Code

如上代碼Code 9-1中所示,NO.1和NO.2處分別定義了ISubject和IObserver接口,接着定義了一個具體的主體類MySubject,該類實現了ISubject接口,在AddObserver、RemoveObserver分別將觀察者加入或者移除集合_observers_list(NO.3和NO.4處),最後在NotifyObservers方法中,遍歷_observers_list集合,將通知發送到每一個觀察者(NO.5處),注意咱們能夠在DoSomething方法中當知足某一條件時,通知觀察者(NO.6處)。咱們使用IObserver接口定義了兩個具體的觀察者MyObserver和YourObserver,在二者的Notify方法中分別按照本身的邏輯去處理通知信息(一個直接將msg打印出來,一個將msg以郵件形式發送給別人)(NO.7和NO.8處)。工具

      如今咱們能夠將MySubject類對象看成一個具體的主體,將MyObserver類對象和YourObserver類對象當作具體的觀察者,那麼代碼中能夠這樣去使用:測試

 1 //Code 9-2
 2 
 3 ISubject subject = new MySubject();
 4 
 5 subject.AddObserver(new MyObserver());  //NO.1
 6 
 7 subject.AddObserver(new YourObserver());   //NO.2
 8 
 9  
10 
11 subject.NotifyObservers(「it's a test!」);  //NO.3
12 
13 (subject as MySubject).DoSomething();  //NO.4
View Code

如上代碼Code 9-2所示,咱們向主體subject中添加兩個觀察者(NO.1和NO.2處),以後使用ISubject.NotifyObservers方法通知觀察者(NO.3),另外,咱們還可使用MySubject.DoSomething方法去通知觀察者(當某一條件知足時),兩個觀察者分別會作不一樣的處理,一個直接將「it's a test」字符串打印輸出,而另外一個則將字符串以郵件的形式發送給別人。this

      注:Code 9-2中,咱們不能使用ISubject接口去調用DoSomething方法,而必須先將ISubject類型轉換成MySubject類型,由於DoSomething不屬於ISubject接口。spa

      觀察者模式中,整個流程見下圖9-2:設計

 

 

圖9-2 觀察者模式中的運行流程

如上圖9-2所示,在有些狀況中,NO.2處會作一些篩選,換句話說,主體有可能根據條件通知部分觀察者,NO.4處虛線框表示可選,若是主體關心觀察者的處理結果,那麼觀察者就應該將本身的處理結果返回給主體。「觀察者模式」是全部框架使用得最頻繁的設計模式之一,緣由很簡單,「觀察者模式」分隔開了框架代碼和框架使用者編寫的代碼,它是「好萊塢原則」(Hollywood Principle,don't call us,we will call you)的具體實現手段,而「好萊塢原則」是全部框架都嚴格遵照的。

      Windows Forms框架中的「觀察者模式」主要不是經過「接口-具體」這種方式去實現的,更多的是使用.NET中的「委託-事件」去實現,詳見下一小節。

9.1.2Windows Forms中的觀察者模式

      在Windows Forms框架中,能夠說「觀察者模式」無處不在,在第四章講Winform程序結構時已經有所說明,好比控件處理Windows消息時,最終是以「事件」的形式去通知事件註冊者的,那麼這裏的事件註冊者就是觀察者模式中的「觀察者」,控件就是觀察者模式中的「主體」。咱們回憶一下第四章中有關System.Windows.Forms.Control類的代碼(部分):

 1 //Code 9-3
 2 
 3 class Control:Component
 4 
 5 {
 6 
 7          //
 8 
 9          public event EventHandler Event1;
10 
11          public event EventHandler Event2;
12 
13          protected virtual void WndProc(ref Message m)
14 
15          {
16 
17                    switch(m.Msg)
18 
19                    {
20 
21                             case 1:  //NO.1
22 
23                             {
24 
25                                      //
26 
27                                      OnEvent1(…);
28 
29                                      break;
30 
31                             }
32 
33                             case 2:  //NO.2
34 
35                             {
36 
37                                      OnEvent2(…);
38 
39                                      break;
40 
41                             }
42 
43                             //
44 
45                    }
46 
47          }
48 
49          protected virtual void OnEvent1(EventArgs e)
50 
51          {
52 
53                    if(Event1 != null)
54 
55                    {
56 
57                             Event1(this,e); //NO.3
58 
59                    }
60 
61          }
62 
63          protected virtual void OnEvent2(EventArgs e)
64 
65          {
66 
67                    if(Event2 != null)
68 
69                    {
70 
71                             Event2(this,e);  //NO.4
72 
73                    }
74 
75          }
76 }
View Code

如上代碼Code 9-3所示,在Control類的WndProc窗口過程當中的switch/case塊中,會根據不一樣的Windows消息去激發不一樣的事件(NO.1和NO.2處),因爲WndProc是一個虛方法,全部在任何一個Control的派生類中,都可以重寫WndProc虛方法,處理Windows消息,而後以「事件」的形式去通知事件註冊者。

      若是咱們在Form1中註冊了一個Button類對象btn1的Click事件,那麼btn1就是觀察者模式中的「主體」,Form1(的實例)就是觀察者模式中的「觀察者」,以下代碼:

 1 //Code 9-4
 2 
 3 class Form1:Form
 4 
 5 {
 6 
 7          //
 8 
 9          public Form1()
10 
11          {
12 
13                    InitializeComponent();
14 
15                    btn1.Click += new EventHandler(btn1_Click);  //NO.1
16 
17          }
18 
19          private void btn1_Click(object sender,EventArgs e)  //NO.2
20 
21          {
22 
23                    //
24 
25          }
26 
27 }
View Code

如上圖Code 9-4代碼所示,咱們在Form1的構造方法中註冊了btn1的Click事件(NO.1處),那麼btn1就是「主體」,Form1(的實例)就是「觀察者」,當btn1須要處理Windows消息時,就會激發事件,通知Form1(的實例)。

      Windows Forms框架正是使用「觀察者模式」實現了框架代碼與框架使用者編寫的代碼相分離。

      注:咱們能夠認爲,事件的發佈者等於觀察者模式中的「主體」(Subject),而事件的註冊者等於觀察者模式中的「觀察者」,有關「事件編程」,請參考第六章。

9.2軟件的設計原則

9.2.1Solid原則介紹

         「Solid原則」表明軟件設計過程當中常見的五大原則,分別爲:

(1)S:單一職責原則(Single Responsibility Principle):

         一個類應該只負責一個(種)事情;

(2)O:開閉原則(Open Closed Principle):

         優先選擇在已有的類型基礎上擴展新的類型,避免修改已有類型(已有代碼);

(3)L:里氏替換原則(Liskov Substitution Principle):

         任何基類出現的地方,派生類必定能夠代替基類出現,言下之意就是,派生類必定要具有基類的全部特性;

(4)I:接口隔離原則(Interface Segregation Principle):

         一個類型不該該去實現它不須要的接口,換句話說,接口應該只包含同一類方法或屬性等;

(5)D:依賴倒置原則(Dependency Inversion Principle):

         高層模塊不該該依賴於低層模塊,高層模塊和低層模塊應該同時依賴於一個抽象層(接口層)。

         設計模式相對來說更具體,每種設計模式幾乎都能解決現實生活中某一具體問題,而設計原則相對來說更抽象,它是咱們在軟件設計過程當中的行爲準則,並不能用在某一具體情景之中。以上五大原則單從字面上理解起來不太直觀,下面依次舉例說明之。

9.2.2單一職責原則(SRP)

       「一個類應該只負責一個(種)事情」,緣由很簡單,負責的事情越多,那麼這個類型出錯或者須要修改的機率越大,假如如今有一個超市購物的會員類VIP:

 1 //Code 9-5
 2 
 3 class VIP:IData
 4 
 5 {
 6 
 7          public void Read()
 8 
 9          {
10 
11                    try
12 
13                    {
14 
15                             //read db here…
16 
17                    }
18 
19                    catch(Exception ex)
20 
21                    {
22 
23                             System.IO.File.WriteAllText(@"c:\errorlog.txt", ex.ToString());  //NO.1
24 
25                    }
26 
27          }
28 
29 }
30 
31 interface IData  //NO.2
32 
33 {
34 
35          void Read();
36 
37 }
View Code

如上代碼Code 9-5所示,定義了一個訪問數據庫的IData接口(NO.2處),該接口包含一個Read方法,用來讀取會員信息,會員類VIP實現了IData接口,在編寫Read方法時,咱們捕獲訪問數據庫的異常後,直接將錯誤信息寫入到了日誌文件(NO.1處)。這段代碼看似沒有任何問題,可是後期確會暴露出設計不合理的現象,若是咱們如今不想把日誌文件輸出到本地C盤(NO.1處),而是輸出到D盤,那咱們須要修改VIP的源碼,沒錯,原本咱們只是想修改日誌部分的邏輯,如今卻不得不更改VIP類的代碼。出現這種現象的緣由就是VIP類幹了本不該該它乾的事情:記錄日誌。就像下面這張圖描述的:

 

圖9-3 一個負責了太多事情的工具

如上圖9-3所示,一把包含太多功能的刀,若是哪天某個功能壞掉,咱們不得不將整把刀送去維修。正確解決以上問題的作法就是將日誌邏輯與VIP類分開,代碼以下:

 1 //Code 9-6
 2 
 3 class Logger  //NO.1
 4 
 5 {
 6 
 7          public void WriteLog(string error)
 8 
 9          {
10 
11                    System.IO.File.WriteAllText(@"c:\errorlog.txt", error);
12 
13          }
14 
15 }
16 
17 class VIP:IData
18 
19 {
20 
21          private Logger _logger = new Logger();  //NO.2
22 
23     public void Read()
24 
25     {
26 
27         try
28 
29         {
30 
31             //read db here…
32 
33         }
34 
35         catch (Exception ex)
36 
37         {
38 
39             _logger.WriteLog(ex.ToString());  //NO.3
40 
41         }
42 
43     }
44 }
View Code

如上代碼Code 9-6所示,咱們定義了一個類型Logger專門負責記錄日誌(NO.1處),在VIP類中經過Logger類型來記錄錯誤信息(NO.2和NO.3處),這樣一來,當咱們須要修改日誌部分的邏輯時,不須要再動VIP類的代碼。

       單一職責原則提倡咱們將複雜的功能拆分開來,分配到每一個單獨的類型當中,至於什麼是複雜的功能,到底將功能拆分到什麼程度,這個是沒有標準的,若是記錄日誌是一個繁瑣的過程(本小節示例代碼相對簡單),你還能夠將日誌類Logger的功能再繼續拆分。

9.2.3開閉原則(OCP)

      「優先選擇在已有的類型基礎上擴展新的類型,避免修改已有類型(已有代碼)」,修改已有代碼就意味着須要從新測試原有的功能,由於任何一次修改均可能影響已有功能。若是在普通VIP顧客的基礎之上,多了白銀會員(silver vip)顧客,這兩種顧客在購物時的折扣不同,若是VIP類定義以下(不全):

 1 //Code 9-7
 2 
 3 class VIP:IData
 4 
 5 {
 6 
 7          private int _viptype;  //vip type  NO.1
 8 
 9          //
10 
11          public virtual void Read()
12 
13          {
14 
15                    //
16 
17          }
18 
19          public double GetDiscount(double totalSales)
20 
21          {
22 
23                    if(_viptype == 1)  //vip
24 
25                    {
26 
27                             return totalSales – 10;  //NO.2
28 
29                    }
30 
31                    else  //silver vip
32 
33                    {
34 
35                             return totalSales – 50;  //NO.3
36 
37                    }
38 
39          }
40 
41 }
View Code

如上代碼Code 9-7所示,咱們在定義VIP類的時候,使用_viptype字段來區分當前顧客是普通VIP仍是白銀VIP(NO.1處),在打折方法GetDiscount中,根據不一樣的VIP種類返回不一樣打折後的價格(NO.2和NO.3處),這段代碼的確也能夠運行的很好,可是後期仍是會暴露出設計不合理的地方,若是如今不止增長一個白銀會員,還增長了一個黃金會員(gold vip),那麼咱們不得再也不去修改GetDiscount方法中的if/else塊,修改意味着原有功能可能會出現bug,所以咱們不得再也不去測試以前全部使用到了VIP這個類型代碼。出現這個問題的主要緣由就是咱們從一開始設計VIP類的時候就不合理:沒有考慮到未來可能會有普通會員的衍生體出現。

      若是咱們一開始在設計VIP類的時候就應用了面向對象思想,咱們的VIP類能夠這樣定義:

 1 //Code 9-8
 2 
 3 interface IDiscount  //NO.1
 4 
 5 {
 6 
 7          double GetDiscount(double totalSales);
 8 
 9 }
10 
11 class VIP:IData,IDiscount
12 
13 {
14 
15          //
16 
17          public virtual void Read()
18 
19          {
20 
21                    //
22 
23          }
24 
25          public virtual double GetDiscount(double totalSales)  //NO.2
26 
27          {
28 
29                    return totalSales – 10;
30 
31          }
32 
33 }
34 
35 class SilverVIP:VIP
36 
37 {
38 
39          //
40 
41          public override double GetDiscount(double totalSales)
42 
43          {
44 
45                    return totalSales – 50;  //NO.3
46 
47          }
48 
49 }
50 
51 class GoldVIP:SilverVIP
52 
53 {
54 
55          //
56 
57          public override double GetDiscount(double totalSales)
58 
59          {
60 
61                    return totalSales – 100;  //NO.4
62 
63          }
64 
65 }
View Code

如上代碼Code 9-8所示,咱們定義了一個IDiscount的接口(NO.1處),包含一個打折的GetDiscount方法,接下來讓VIP類實現了IDiscount接口,將接口中的GetDiscount方法定義爲虛方法(NO.2處),後面的白銀會員(SilverVIP)繼承自VIP類、黃金會員(GoldVIP)繼承自SilverVIP類,並分別重寫GetDiscount虛方法,返回相應的打折以後的總價格(NO.3和NO.4處)。這樣一來,新增長會員類型不須要去修改VIP類,也不影響以前使用了VIP類的代碼。

      下圖9-4顯示了從新設計VIP類的先後區別:

 

圖9-4 繼承發生以後

如上圖9-4所示,圖中左邊部分表示不採用繼承的方式去實現普通VIP、白銀VIP和黃金VIP的打折邏輯,能夠看出,每次須要增長一種會員時,都必須去修改VIP類的代碼,圖中右邊部分表示採用繼承方式以後,每種會員均定義成一個類型,每一個類型都可以負責本身的打折邏輯,之後無論新增多少種會員,都可以定義新的派生類,在派生類中定義新的打折邏輯。

       注:派生類中只須要重寫打折的邏輯,不須要從新去定義讀取數據庫的邏輯,由於這個邏輯在基類和派生類中並無發生變化。

9.2.4里氏替換原則(LSP)

      「任何基類出現的地方,派生類必定能夠代替基類出現,言下之意就是,派生類必定要具有基類的全部特性」,意思就是說,若是B是A的兒子,那麼B必定能夠代替A去作任何事情,不然,B就不該該是A的兒子。咱們在設計類型的時候,每每不去注意一個類型是否真的應該去繼承另一個類型,不少時候咱們只是爲了聽從所謂的「OO」思想。若是如今有一個管理員類Manager,由於管理員也須要讀取數據庫,因此咱們讓它繼承自VIP類,代碼以下:

 1 //Code 9-9
 2 
 3 class Manager:VIP
 4 
 5 {
 6 
 7          //
 8 
 9          public override void Read()
10 
11          {
12 
13                    //
14 
15          }
16 
17          public override double GetDiscount(double totalSales)
18 
19          {
20 
21                    throw new Exception(「don't have this function!」);  //NO.1
22 
23          }
24 
25 }
View Code

如上代碼Code 9-9所示,咱們定義Manager類,讓其繼承自VIP類,因爲Manager類並無「打折扣」的邏輯,所以咱們重寫GetDiscount方法時,拋出「don't have this function!」這樣的異常(NO.1處),接下來咱們可能編寫出以下這樣的代碼:

 1 //Code 9-10
 2 
 3 List<VIP> vips = new List<VIP>();  //NO.1
 4 
 5 vips.Add(new VIP());
 6 
 7 vips.Add(new SilverVIP());
 8 
 9 vips.Add(new GoldVIP());
10 
11 vips.Add(new Manager());
12 
13 //
14 
15 foreach(VIP v in vips)
16 
17 {
18 
19          /20 
21          double d = v.GetDiscount(…);  //NO.2
22 
23          //
24 
25 }
View Code

如上代碼Code 9-10所示,咱們定義了一個VIP類型的容器(NO.1處),依次將VIP、SilverVIP、GoldVIP以及Manager類型對象加入容器,最後經過foreach遍歷該容器,調用容器中每一個元素的GetDiscount方法(NO.2處),此段代碼一切正常經過編譯,由於編譯器認可「基類出現的地方,派生類必定可以代替其出現」,但事實上,程序運行以後,在調用Manager類對象的GetDiscount虛方法時會拋出異常,形成這個現象的主要緣由就是,咱們根本沒搞清楚類的繼承關係,Manager類雖然也要訪問數據庫,可是它並不是屬於VIP的一種,也就是說,Manager類不該該是VIP類的兒子,以下圖9-5:

 

 

圖9-5Manager錯誤的繼承關係

如上圖9-5所示,Manager類雖然須要讀取數據庫,可是它並不須要有與「折扣」相關的操做,並且它根本不屬於一種VIP的衍生物,正確的作法是讓Manager類直接實現IData接口便可,以下代碼:

 1 //Code 9-11
 2 
 3 class Manager:IData
 4 
 5 {
 6 
 7          //
 8 
 9          public void Read()
10 
11          {
12 
13                    //
14 
15          }
16 
17 }
View Code

如上代碼Code 9-11所示,Manager實現了IData接口以後,再也不跟VIP類有關聯,這樣一來,前面Code 9-10代碼在編譯時,就會通不過,

 1 //Code 9-12
 2 
 3 List<VIP> vips = new List<VIP>();  //NO.1
 4 
 5 vips.Add(new VIP());
 6 
 7 vips.Add(new SilverVIP());
 8 
 9 vips.Add(new GoldVIP());
10 
11 vips.Add(new Manager());  //NO.2
View Code

如上代碼Code 9-12所示,編譯器會在NO.2處報錯,緣由很簡單,Manager既然不是VIP的派生類了,就不能代替VIP出現。

     若是兩個類從邏輯上就沒有衍生的關係,就不該該有相互繼承出現,見下圖9-6:

 

圖9-6 沒有衍生關係的兩個物體

如上圖9-6所示,狗跟貓兩種動物沒有衍生關係,狗類(Dog)不能繼承自貓類(Cat),貓類也不能繼承自狗類,可是他們均可以同時繼承自動物類(Animal)。

9.2.5接口隔離原則(ISP)

      「一個類型不該該去實現它不須要的接口,換句話說,接口應該只包含同一類方法或屬性等」,若是把全部的方法都放在一個接口中,那麼實現了該接口的類型必須實現接口中的所有方法(即便不須要),同理,在一個已經很穩定的系統中,不該該再去修改已經存在的接口,由於這會影響到以前全部實現該接口的類型。如今若是須要新增長一種VIP顧客(SuperVIP),容許它修改數據庫,咱們可能這樣去修改IData接口:

 1 //Code 9-13
 2 
 3 interface IData
 4 
 5 {
 6 
 7          void Read();
 8 
 9          void Write();  //NO.1
10 
11 }
View Code

如上代碼Code 9-13所示,咱們修改已經存在的IData接口,使其包含一個寫數據庫的Write方法(NO.1處),知足SuperVIP類的須要,這個方法看似能夠,可是它要求咱們修改其餘已經實現了IData接口的類型,好比前面的VIP類,只要涉及到VIP類的更改,那麼其餘全部使用到了VIP類的地方都得從新測試,能夠看出,這會影響整個已經存在的系統。正確的作法應該是,新增長一個接口IData2,將數據庫的寫入方法放在該接口中,讓SuperVIP類實現該接口,代碼以下:

 1 Code 9-14
 2 
 3 interface IData2:IData  //NO.1
 4 
 5 {
 6 
 7          void Write();
 8 
 9 }
10 
11 class SuperVIP:IData2,IData
12 
13 {
14 
15          public void Read()
16 
17          {
18 
19                    //
20 
21          }
22 
23          public void Write()
24 
25          {
26 
27                    //
28 
29          }
30 
31 }
View Code

如上代碼Code 9-14所示,咱們定義了一個新的接口IData2(NO.1處),該接口包含一個Write方法,讓SuperVIP類實現該接口,這樣一來,整個過程不會影響已經存在的VIP類。

9.2.6依賴倒置原則(DIP)

      「高層模塊不該該依賴於低層模塊,高層模塊和低層模塊應該同時依賴於一個抽象層(接口層)」,本原則目的很明確,就是爲了下降模塊之間的耦合度,咱們觀察一下9.2.2小節示例代碼中的VIP類和Logger類,很明顯,VIP類直接依賴於Logger類,若是咱們想換種方式記錄日誌的話(也就是改變記錄日誌的邏輯),必須得從新修改Logger類中的代碼,如今若是讓VIP類依賴於一個抽象接口ILog,其餘全部記錄日誌的類型同時也依賴於ILog接口,那麼整個系統就會更加靈活,

 1 Code 9-15
 2 
 3 interface ILog  //NO.1
 4 
 5 {
 6 
 7          void Log(string error);
 8 
 9 }
10 
11 class FileLogger:ILog  //NO.2
12 
13 {
14 
15          public void Log(string error)
16 
17          {
18 
19                    //write error log to local file
20 
21          }
22 
23 }
24 
25 class EmailLogger:ILog  //NO.3
26 
27 {
28 
29          public void Log(string error)
30 
31          {
32 
33                    //send error log as email
34 
35          }
36 
37 }
38 
39 class NotifyLogger:ILog  //NO.4
40 
41 {
42 
43          public void Log(string error)
44 
45          {
46 
47                    //notify other modules
48 
49          }
50 
51 }
52 
53 class VIP:IData,IDiscount  //NO.5
54 
55 {
56 
57          //
58 
59          ILog _logger;
60 
61          public VIP(ILog logger)  //NO.6
62 
63          {
64 
65                    _logger = logger;
66 
67          }
68 
69          public virtual void Read()
70 
71          {
72 
73                    try
74 
75                    {
76 
77                             //…read db here
78 
79                    }
80 
81                    catch(Exception ex)
82 
83                    {
84 
85                             _logger.Log(ex.ToString());  //NO.7
86 
87                    }
88 
89          }
90 
91          public virtual double GetDiscount(double totalSales)
92 
93          {
94 
95                    return totalSales – 10;
96 
97          }
98 
99 }
View Code

如上代碼Code 9-15所示,咱們定義了一個日誌接口ILog做爲抽象層(NO.1處),以後定義了各類各樣的低層日誌模塊(NO.二、NO.3和NO.4處),這些記錄日誌的類均依賴(實現)ILog這個抽象接口,以後咱們在定義VIP類時,再也不讓它具體依賴於某個日誌類,換句話說,再也不讓高層模塊直接依賴低層模塊,取而代之的是,讓VIP類依賴於ILog這個抽象接口(NO.6處),咱們在使用VIP類的時候,能夠根據須要給它傳遞不一樣的日誌類對象(也能夠是除了示例代碼中的三個之外自定義類型,只要實現了ILog接口),程序運行後,會將錯誤日誌記錄到相應位置(NO.7處)。咱們能夠這樣使用VIP類:

 1 Code 9-16
 2 
 3 IData v = new VIP(new FileLogger());
 4 
 5 v.Read();  //NO.1
 6 
 7  
 8 
 9 IData v2 = new VIP(new EMailLogger());
10 
11 v2.Read();  //NO.2
12 
13  
14 
15 IData v3 = new VIP(new NotifyLogger());
16 
17 v3.Read();  //NO.3
View Code

如上代碼Code 9-16所示,NO.1處若是出現異常,錯誤日誌會保存到文件,NO.2處若是出現異常,錯誤日誌將會經過郵件發送給別人,NO.3處若是出現異常,VIP對象會自動把錯誤信息通知給別的模塊。

     依賴倒置原則提倡模塊與模塊之間不該該有直接的依賴關係,見下圖9-7:

 

圖9-7 依賴倒置發生先後

如上圖9-7所示,圖中左邊部分表示依賴倒置以前高層模塊與低層模塊之間的依賴關係,圖中右邊部分表示依賴倒置發生以後,高層模塊與低層模塊之間的依賴關係,很明顯,依賴倒置發生後,高層模塊再也不直接受低層模塊控制,高層模塊與低層模塊沒有具體的對應關係,靈活性增長,耦合度下降。

 

圖9-8 接力賽跑中的接力棒

如上圖9-8所示,接力過程當中先後兩人沒有具體對應關係。

       注:依賴倒置原則是每一個框架都必須遵循的,框架不可能受框架的使用者控制,換句話說,框架做爲「高層模塊」,不該該依賴於框架使用者編寫的代碼(低層模塊),而應該均依賴於一個抽象層,因此咱們在使用框架編寫代碼時,大部分時候均以框架庫做爲基礎,從已有的類型或者接口(抽象層)派生出新的類型。

9.3設計模式與設計原則對框架的意義

      「IT語境中的框架,特指爲解決一個開放性問題而設計的具備必定約束性的支撐結構。在此結構上能夠根據具體問題擴展、安插更多的組成部分,從而更迅速和方便地構建完整的解決問題的方案。」——摘自互聯網

     上面是一段摘自互聯網上描述「框架」的話,從這段話中咱們瞭解到,首先,每一個框架解決問題的範圍是有限的,好比Windows Forms框架只會幫助咱們完成Windows桌面應用程序的開發,這就是它的「約束性」,其次,框架自己解決不了什麼特定的問題,它只給瞭解決特定問題的相關模塊(或者組件)一個可插接、可組合的底子,這個底子爲咱們解決實際具體問題提供了支持,這就是框架的「支撐性」,見下圖9-9:

 

圖9-9 框架使用先後

如上圖9-9所示,圖中左邊部分表示使用框架以前,整個系統均由開發者編寫代碼的結構圖,咱們能夠看見,不管系統的「系統運行邏輯」仍是「業務處理邏輯」均由開發者負責,開發者本身調用本身的代碼,整個系統的運行流程由開發者控制;圖中右邊部分表示使用了框架以後,「系統運行邏輯」由框架接管了,開發者只須要把精力集中在「業務邏輯處理」之上(Windows Forms框架接管了消息循環、消息處理等,負責了整個Winform程序的運轉),除此以外,還有一個很是大並且很是重要的改變:開發者再也不(幾乎不)本身調用本身的代碼了,本身編寫的代碼均由框架調用,系統運行的控制權交給了框架。這就是全部框架所必須知足的「好萊塢原則」(Hollywood Principle,don't call us,we will call you),「好萊塢原則」跟「控制轉換原則」(IoC,Inversion of Control)相似,參見前面章節,能夠了解框架是怎樣反過來控制程序的運行。

      咱們在使用框架開發應用程序去解決實際具體的問題時,框架避免不了會與咱們開發者編寫的代碼進行交互,這就會產生一個問題,那就是怎樣去把握框架代碼和框架使用者編寫代碼二者之間的關聯性,也就是咱們常說的「高內聚,低耦合」。「高內聚,低耦合」在框架中要求更高,由於框架的使用人羣和範圍比通常普通系統更大更普遍,優秀的框架要想使用壽命更長口碑更好,就要求框架能在使用後期可以更容易升級、更方便擴展新的功能來知足使用者的各類須要,而這些大部分取決於框架最開始的設計好壞,正確地使用各類「設計模式」以及嚴格地遵照各類「設計原則」是決定框架後期可否應付各類變動、升級擴展的重要因素。

相關文章
相關標籤/搜索