詳解設計模式六大原則

設計模式(Design pattern)是一套被反覆使用、多數人知曉的、通過分類編目的、代碼設計經驗的總結。使用設計模式是爲了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。 毫無疑問,設計模式於己於他人於系統都是多贏的;設計模式使代碼編制真正工程化;設計模式是軟件工程的基石脈絡,如同大廈的結構同樣。 ios

借用並改編一下魯迅老師《故鄉》中的一句話,一句話歸納設計模式: 但願本無所謂有,無所謂無.這正如coding的設計模式,其實coding本沒有設計模式,用的人多了,也便成了設計模式 數據庫

六大原則

設計模式(面向對象)有六大原則: 編程

  • 開閉原則(Open Closed Principle,OCP)
  • 里氏代換原則(Liskov Substitution Principle,LSP)
  • 依賴倒轉原則(Dependency Inversion Principle,DIP)
  • 接口隔離原則(Interface Segregation Principle,ISP)
  • 合成/聚合複用原則(Composite/Aggregate Reuse Principle,CARP)
  • 最小知識原則(Principle of Least Knowledge,PLK,也叫迪米特法則)

開閉原則具備理想主義的色彩,它是面向對象設計的終極目標。其餘幾條,則能夠看作是開閉原則的實現方法。 設計模式就是實現了這些原則,從而達到了代碼複用、增長可維護性的目的。 設計模式

 

C# 開閉原則

1.概念: app

一個軟件實體如類、模塊和函數應該對擴展開放,對修改關閉。模塊應儘可能在不修改原(是「原」,指原來的代碼)代碼的狀況下進行擴展。 ide

2.模擬場景: 函數

在軟件的生命週期內,由於變化、升級和維護等緣由須要對軟件原有代碼進行修改時,可能會給舊代碼中引入錯誤,也可能會使咱們不得不對整個功能進行重構,而且須要原有代碼通過從新測試。 工具

3.Solution: 測試

當軟件須要變化時,儘可能經過擴展軟件實體的行爲來實現變化,而不是經過修改已有的代碼來實現變化。 this

4.注意事項:

  • 經過接口或者抽象類約束擴展,對擴展進行邊界限定,不容許出如今接口或抽象類中不存在的public方法
  • 參數類型、引用對象儘可能使用接口或者抽象類,而不是實現類
  • 抽象層儘可能保持穩定,一旦肯定即不容許修改

5.開閉原則的優勢:

  • 可複用性
  • 可維護性

6.開閉原則圖解:

 

C# 里氏代換原則

1.概述: 派生類(子類)對象可以替換其基類(父類)對象被調用

2.概念:

里氏代換原則(Liskov Substitution Principle LSP)面向對象設計的基本原則之一。 里氏代換原則中說,任何基類能夠出現的地方,子類必定能夠出現。 LSP是繼承複用的基石,只有當衍生類能夠替換掉基類,軟件單位的功能不受到影響時,基類才能真正被複用,而衍生類也可以在基類的基礎上增長新的行爲。里氏代換原則是對「開-閉」原則的補充。實現「開-閉」原則的關鍵步驟就是抽象化。而基類與子類的繼承關係就是抽象化的具體實現,因此里氏代換原則是對實現抽象化的具體步驟的規範。(源自百度百科)

3.子類爲何能夠替換父類的位置?:

當知足繼承的時候,父類確定存在非私有成員,子類確定是獲得了父類的這些非私有成員(假設,父類的的成員所有是私有的,那麼子類沒辦法從父類繼承任何成員,也就不存在繼承的概念了)。既然子類繼承了父類的這些非私有成員,那麼父類對象也就能夠在子類對象中調用這些非私有成員。因此,子類對象能夠替換父類對象的位置。

4.C# 里氏代換原則優勢:

需求變化時,只須繼承,而別的東西不會改變。因爲里氏代換原則才使得開放封閉成爲可能。這樣使得子類在父類無需修改的話就能夠擴展。

5.C# 里氏代換原則Demo:

代碼正文:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
namespaceTestApp
{
    using System;
 
    classProgram
    {
        staticvoidMain(string[] args)
        {
            Transportation transportation =newTransportation();
            transportation.Say();
            Transportation sedan =newSedan();
            sedan.Say();
            Console.ReadKey();
        }
    }
 
    classTransportation
    {
        publicTransportation()
        {
            Console.WriteLine("Transportation?");
        }
 
        publicvirtualvoidSay()
        {
            Console.WriteLine("121");
        }
    }
 
    classSedan:Transportation
    {
        publicSedan()
        {
            Console.WriteLine("Transportation:Sedan");
        }
 
        publicoverridevoidSay()
        {
            Console.WriteLine("Sedan");
        }
    }
 
    classBicycles : Transportation
    {
        publicBicycles()
        {
            Console.WriteLine("Transportation:Bicycles");
        }
 
        publicoverridevoidSay()
        {
            Console.WriteLine("Bicycles");
        }
    }
}

 

代碼效果:

6.里氏代換原則圖解:

 

C# 依賴倒轉原則

1.概念:

依賴倒置原則(Dependence Inversion Principle)是程序要依賴於抽象接口,不要依賴於具體實現。簡單的說就是要求對抽象進行編程,不要對實現進行編程,這樣就下降了客戶與實現模塊間的耦合。

2.C# 依賴倒轉原則用處:

有些時候爲了代碼複用,通常會把經常使用的代碼寫成函數或類庫。這樣開發新項目時,直接用就好了。好比作項目時大多要訪問數據庫,因此咱們就把訪問數據庫的代碼寫成了函數。每次作項目去調用這些函數。那麼咱們的問題來了。咱們要作新項目時,發現業務邏輯的高層模塊都是同樣的,但客戶卻但願使用不一樣的數據庫或存儲住處方式,這時就出現麻煩了。咱們但願能再次利用這些高層模塊,但高層模塊都是與低層的訪問數據庫綁定在一塊兒,沒辦法複用這些高層模塊。因此無論是高層模塊和低層模塊都應該依賴於抽象,具體一點就是接口或抽象類,只要接口是穩定的,那麼任何一個更改都不用擔憂了。

3.注意事項:

  • 高層模塊不該該依賴低層模塊。兩個都應該依賴抽象。
  • 抽象不該該依賴結節。細節應該依賴抽象。

4.模擬場景:

場景:

假設如今須要一個Monitor工具,去運行一些已有的APP,自動化來完成咱們的工做。Monitor工具須要啓動這些已有的APP,而且寫下Log。

代碼實現1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
namespaceTestLibrary.ExtensionsClass
{
    usingSystem;
 
    publicclassAppOne
    {
        publicboolStart()
        {
            Console.WriteLine("1號APP開始啓動");
            returntrue;
        }
 
        publicboolExportLog()
        {
            Console.WriteLine("1號APP輸出日誌");
            returntrue;
        }
    }
 
    publicclassAppTwo
    {
        publicboolStart()
        {
            Console.WriteLine("2號APP開始啓動");
            returntrue;
        }
 
        publicboolExportLog()
        {
            Console.WriteLine("2號APP輸出日誌");
            returntrue;
        }
    }
 
    publicclassMonitor
    {
        publicenumAppNumber
        {
            AppOne=1,
            AppTwo=2
        }
 
        privateAppOne appOne =newAppOne();
        privateAppTwo appTwo =newAppTwo();
        privateAppNumber number;
        publicMonitor(AppNumber number)
        {
            this.number = number;
        }
 
        publicboolStartApp()
        {
            returnnumber == AppNumber.AppOne ? appOne.Start() : appTwo.Start();
        }
 
        publicboolExportAppLog()
        {
            returnnumber == AppNumber.AppOne ? appOne.ExportLog() : appTwo.ExportLog();
        }
    }
}

代碼解析1:

在代碼實現1中咱們已經輕鬆實現了Monitor去運行已有APP而且寫下LOG的需求。而且代碼已經上線了.

春…夏…秋…冬…

春…夏…秋…冬…

春…夏…秋…冬…

就這樣,三年過去了。

一天客戶找上門了,公司業務擴展了,如今須要新加3個APP用Monitor自動化。這樣咱們就必須得改Monitor。

代碼實現2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
namespaceTestLibrary.ExtensionsClass
{
    usingSystem;
    usingSystem.Collections.Generic;
    usingSystem.Linq;
    usingSystem.Text;
    usingSystem.Threading.Tasks;
 
    publicclassMonitor
    {
        publicenumAppNumber
        {
            AppOne = 1,
            AppTwo = 2,
            AppThree = 3,
            AppFour = 4,
            AppFive = 5
        }
 
        privateAppOne appOne =newAppOne();
        privateAppTwo appTwo =newAppTwo();
        privateAppThree appThree =newAppThree();
        privateAppFour appFour =newAppFour();
        privateAppFive appFive =newAppFive();
        privateAppNumber number;
        publicMonitor(AppNumber number)
        {
            this.number = number;
        }
 
        publicboolStartApp()
        {
            boolresult =false;
            if(number == AppNumber.AppOne)
            {
                result = appOne.Start();
            }
            elseif(number == AppNumber.AppTwo)
            {
                result = appTwo.Start();
            }
            elseif(number == AppNumber.AppThree)
            {
                result = appThree.Start();
            }
            elseif(number == AppNumber.AppFour)
            {
                result = appFour.Start();
            }
            elseif(number == AppNumber.AppFive)
            {
                result = appFive.Start();
            }
 
            returnresult;
        }
 
        publicboolExportAppLog()
        {
            boolresult =false;
            if(number == AppNumber.AppOne)
            {
                result = appOne.ExportLog();
            }
            elseif(number == AppNumber.AppTwo)
            {
                result = appTwo.ExportLog();
            }
            elseif(number == AppNumber.AppThree)
            {
                result = appThree.ExportLog();
            }
            elseif(number == AppNumber.AppFour)
            {
                result = appFour.ExportLog();
            }
            elseif(number == AppNumber.AppFive)
            {
                result = appFive.ExportLog();
            }
 
            returnresult;
        }
    }
}

代碼解析2:

這樣會給系統添加新的相互依賴。而且隨着時間和需求的推移,會有更多的APP須要用Monitor來監測,這個Monitor工具也會被愈來愈對的if…else撐爆炸,並且代碼隨着APP越多,越難維護。最終會致使Monitor走向滅亡(下線)。

介於這種狀況,能夠用Monitor這個模塊來生成其它的程序,使得系統可以用在須要的APP上。OOD給咱們提供了一種機制來實現這種「依賴倒置」。

代碼實現3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
namespaceTestLibrary.ExtensionsClass
{
    usingSystem;
 
    publicinterfaceIApp
    {
        boolStart();
        boolExportLog();
    }
 
    publicclassAppOne : IApp
    {
        publicboolStart()
        {
            Console.WriteLine("1號APP開始啓動");
            returntrue;
        }
 
        publicboolExportLog()
        {
            Console.WriteLine("1號APP輸出日誌");
            returntrue;
        }
    }
 
    publicclassAppTwo : IApp
    {
        publicboolStart()
        {
            Console.WriteLine("2號APP開始啓動");
            returntrue;
        }
 
        publicboolExportLog()
        {
            Console.WriteLine("2號APP輸出日誌");
            returntrue;
        }
    }
 
    publicclassMonitor
    {
        privateIApp iapp;
        publicMonitor(IApp iapp)
        {
            this.iapp = iapp;
        }
 
        publicboolStartApp()
        {
            returniapp.Start();
        }
 
        publicboolExportAppLog()
        {
            returniapp.ExportLog();
        }
    }
}

代碼解析3:

如今Monitor依賴於IApp這個接口,而與具體實現的APP類沒有關係,因此不管再怎麼添加APP都不會影響到Monitor自己,只須要去添加一個實現IApp接口的APP類就能夠了。

 

C# 接口隔離原則

1.概念:

客戶端不該該依賴它不須要的接口,類間的依賴關係應該創建在最小的接口上

2.含義:

接口隔離原則的核心定義,不出現臃腫的接口(Fat Interface),可是「小」是有限度的,首先就是不能違反單一職責原則。

3.模擬場景:

一個OA系統,外部只負責提交和撤回工做流,內部負責審覈和駁回工做流。

4.代碼演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
namespaceTestLibrary.ExtensionsClass
{
    usingSystem;
    usingSystem.Collections.Generic;
    usingSystem.Linq;
    usingSystem.Text;
    usingSystem.Threading.Tasks;
 
    publicinterfaceIReview
    {
        voidReviewWorkFlow();
 
        voidRejectWorkFlow();
    }
 
    publicclassReview : IReview
    {
        publicvoidReviewWorkFlow()
        {
            Console.WriteLine("開始審覈工做流");
        }
 
        publicvoidRejectWorkFlow()
        {
            Console.WriteLine("已經駁回工做流");
        }
    }
 
    publicinterfaceISubmit
    {
        voidSubmitWorkFlow();
 
        voidCancelWorkFlow();
    }
 
    publicclassSubmit : ISubmit
    {
        publicvoidSubmitWorkFlow()
        {
            Console.WriteLine("開始提交工做流");
        }
 
        publicvoidCancelWorkFlow()
        {
            Console.WriteLine("已經撤銷工做流");
        }
    }
}

5.代碼解析:

其實接口隔離原則很好理解,在上面的例子裏能夠看出來,若是把OA的外部和內部都定義一個接口的話,那這個接口會很大,並且實現接口的類也會變得臃腫。

 

C# 合成/聚合複用原則

1.概念:

合成/聚合複用原則(Composite/Aggregate Reuse Principle,CARP)常常又叫作合成複用原則。合成/聚合複用原則就是在一個新的對象裏面使用一些已有的對象,使之成爲新對象的一部分;新的對象經過向這些對象的委派達到複用已有功能的目的。它的設計原則是:要儘可能使用合成/聚合,儘可能不要使用繼承。

2.合成/聚合解析:

聚合概念:

聚合用來表示「擁有」關係或者總體與部分的關係。表明部分的對象有可能會被多個表明總體的對象所共享,並且不必定會隨着某個表明總體的對象被銷燬或破壞而被銷燬或破壞,部分的生命週期能夠超越總體。例如,Iphone5和IOS,當Iphone5刪除後,IOS還能存在,IOS能夠被Iphone6引用。

聚合關係UML類圖:

C# 合成/聚合複用原則

代碼演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
namespaceTestLibrary.ExtensionsClass
{
    classIOS
    {
    }
 
    classIphone5
    {
        privateIOS ios;
        publicIphone5(IOS ios)
        {
            this.ios = ios;
        }
    }
}

合成概念:

合成用來表示一種強得多的「擁有」關係。在一個合成關係裏,部分和總體的生命週期是同樣的。一個合成的新對象徹底擁有對其組成部分的支配權,包括它們的建立和湮滅等。使用程序語言的術語來講,合成而成的新對象對組成部分的內存分配、內存釋放有絕對的責任。一個合成關係中的成分對象是不能與另外一個合成關係共享的。一個成分對象在同一個時間內只能屬於一個合成關係。若是一個合成關係湮滅了,那麼全部的成分對象要麼本身湮滅全部的成分對象(這種狀況較爲廣泛)要麼就得將這一責任交給別人(較爲罕見)。例如:水和魚的關係,當水沒了,魚也不可能獨立存在。

合成關係UML類圖:

代碼演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
namespaceTestLibrary.ExtensionsClass
{
    usingSystem;
 
    classFish
    {
        publicFish CreateFish()
        {
            Console.WriteLine("一條小魚兒");
            returnnewFish();
        }
    }
 
    classWater
    {
        privateFish fish;
        publicWater()
        {
            fish =newFish();
        }
 
        publicvoidCreateWater()
        {
            // 當建立了一個水的地方,那這個地方也得放點魚進去
            fish.CreateFish();
        }
    }
}

3.模擬場景:

好比說咱們先搖到號(這個比較困難)了,須要爲本身買一輛車,若是4S店裏的車默認的配置都是同樣的。那麼咱們只要買車就會有這些配置,這時使用了繼承關係:

不可能全部汽車的配置都是同樣的,因此就有SUV和小轎車兩種(只列舉兩種比較熱門的車型),而且使用機動車對它們進行聚合使用。這時採用了合成/聚合的原則:

 

C# 迪米特法則

1.概念:

一個軟件實體應當儘量少的與其餘實體發生相互做用。每個軟件單位對其餘的單位都只有最少的知識,並且侷限於那些與本單位密切相關的軟件單位。迪米特法則的初衷在於下降類之間的耦合。因爲每一個類儘可能減小對其餘類的依賴,所以,很容易使得系統的功能模塊功能獨立,相互之間不存在(或不多有)依賴關係。迪米特法則不但願類之間創建直接的聯繫。若是真的有須要創建聯繫,也但願能經過它的友元類來轉達。所以,應用迪米特法則有可能形成的一個後果就是:系統中存在大量的中介類,這些類之因此存在徹底是爲了傳遞類之間的相互調用關係——這在必定程度上增長了系統的複雜度。

2.模擬場景:

場景:公司財務總監發出指令,讓財務部門的人去統計公司已發公司的人數。

一個常態的編程:(確定是不符LoD的反例)

UML類圖:

代碼演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
namespaceTestLibrary.ExtensionsClass
{
    usingSystem;
    usingSystem.Collections.Generic;
 
    /// <summary>
    /// 財務總監
    /// </summary>
    publicclassCFO
    {
        /// <summary>
        /// 財務總監發出指令,讓財務部門統計已發工資人數
        /// </summary>
        publicvoidDirective(Finance finance)
        {
            List<Employee> employeeList =newList<Employee>();
            // 初始化已發工資人數
            for(inti = 0; i < 500; i++)
            {
                employeeList.Add(newEmployee());
            }
 
            // 轉告財務部門開始統計已結算公司的員工
            finance.SettlementSalary(employeeList);
        }
    }
 
    /// <summary>
    /// 財務部
    /// </summary>
    publicclassFinance
    {
        /// <summary>
        /// 統計已結算公司的員工
        /// </summary>
        publicvoidSettlementSalary(List<Employee> employeeList)
        {
            Console.WriteLine(string.Format("已結算工資人數:{0}", employeeList.Count));
        }
    }
 
    /// <summary>
    /// 員工
    /// </summary>
    publicclassEmployee
    {
 
    }
 
    /// <summary>
    /// 主程序
    /// </summary>
    publicclassRunner
    {
        publicstaticvoidmain(String[] args)
        {
            CFO cfo =newCFO();
            // 財務總監發出指令
            cfo.Directive(newFinance());
        }
    }
}

根據模擬的場景:財務總監讓財務部門總結已發工資的人數。 財務總監和員工是陌生關係(即總監不須要對員工執行任何操做)。根據上述UML圖和代碼解決辦法顯然能夠看出,上述作法違背了LoD法則。

依據LoD法則解耦:(符合LoD的例子)

UML類圖:

代碼演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
namespaceTestLibrary.ExtensionsClass
{
    usingSystem;
    usingSystem.Collections.Generic;
 
    /// <summary>
    /// 財務總監
    /// </summary>
    publicclassCFO
    {
        /// <summary>
        /// 財務總監發出指令,讓財務部門統計已發工資人數
        /// </summary>
        publicvoidDirective(Finance finance)
        {
            // 通知財務部門開始統計已結算公司的員工
            finance.SettlementSalary();
        }
    }
 
    /// <summary>
    /// 財務部
    /// </summary>
    publicclassFinance
    {
        privateList<Employee> employeeList; 
 
        //傳遞公司已工資的人
        publicFinance(List<Employee> _employeeList)
        {
            this.employeeList = _employeeList; 
    } 
 
        /// <summary>
        /// 統計已結算公司的員工
        /// </summary>
        publicvoidSettlementSalary()
        {
            Console.WriteLine(string.Format("已結算工資人數:{0}", employeeList.Count));
        }
    }
 
    /// <summary>
    /// 員工
    /// </summary>
    publicclassEmployee
    {
 
    }
 
    /// <summary>
    /// 主程序
    /// </summary>
    publicclassRunner
    {
        publicstaticvoidmain(String[] args)
        {
            List<Employee> employeeList =newList<Employee>();
 
            // 初始化已發工資人數
            for(inti = 0; i < 500; i++)
            {
                employeeList.Add(newEmployee());
            }
 
            CFO cfo =newCFO();
 
            // 財務總監發出指令
            cfo.Directive(newFinance(employeeList));
        }
    }
}

根據LoD原則咱們須要讓財務總監和員工之間沒有之間的聯繫。這樣纔是遵照了迪米特法則。

 

博客總結

想搞懂設計模式,必須先知道設計模式遵循的六大原則,不管是哪一種設計模式都會遵循一種或者多種原則。這是面向對象不變的法則。本文針對的是設計模式(面向對象)主要的六大原則展開的講解,並儘可能作到結合實例和UML類圖,幫助你們理解。在後續的博文中還會跟進一些設計模式的實例

相關文章
相關標籤/搜索