詳解設(shè)計模式六大原則

詳解設(shè)計模式六大原則

設(shè)計模式(Design pattern)是一套被反復(fù)使用、多數(shù)人知曉的、經(jīng)過分類編目的、代碼設(shè)計經(jīng)驗的總結(jié)。使用設(shè)計模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。 毫無疑問,設(shè)計模式于己于他人于系統(tǒng)都是多贏的;設(shè)計模式使代碼編制真正工程化;設(shè)計模式是軟件工程的基石脈絡(luò),如同大廈的結(jié)構(gòu)一樣。

借用并改編一下魯迅老師《故鄉(xiāng)》中的一句話,一句話概括設(shè)計模式: 希望本無所謂有,無所謂無.這正如coding的設(shè)計模式,其實coding本沒有設(shè)計模式,用的人多了,也便成了設(shè)計模式

六大原則

設(shè)計模式(面向?qū)ο?有六大原則:

開閉原則(Open Closed Principle,OCP)

里氏代換原則(Liskov Substitution Principle,LSP)

依賴倒轉(zhuǎn)原則(Dependency Inversion Principle,DIP)

接口隔離原則(Interface Segregation Principle,ISP)

合成/聚合復(fù)用原則(Composite/Aggregate Reuse Principle,CARP)

最小知識原則(Principle of Least Knowledge,PLK,也叫迪米特法則)

開閉原則具有理想主義的色彩,它是面向?qū)ο笤O(shè)計的終極目標(biāo)。其他幾條,則可以看做是開閉原則的實現(xiàn)方法。 設(shè)計模式就是實現(xiàn)了這些原則,從而達(dá)到了代碼復(fù)用、增加可維護(hù)性的目的。

C# 開閉原則

1.概念:

一個軟件實體如類、模塊和函數(shù)應(yīng)該對擴(kuò)展開放,對修改關(guān)閉。模塊應(yīng)盡量在不修改原(是“原”,指原來的代碼)代碼的情況下進(jìn)行擴(kuò)展。

2.模擬場景:

在軟件的生命周期內(nèi),因為變化、升級和維護(hù)等原因需要對軟件原有代碼進(jìn)行修改時,可能會給舊代碼中引入錯誤,也可能會使我們不得不對整個功能進(jìn)行重構(gòu),并且需要原有代碼經(jīng)過重新測試。

3.Solution:

當(dāng)軟件需要變化時,盡量通過擴(kuò)展軟件實體的行為來實現(xiàn)變化,而不是通過修改已有的代碼來實現(xiàn)變化。

4.注意事項:

通過接口或者抽象類約束擴(kuò)展,對擴(kuò)展進(jìn)行邊界限定,不允許出現(xiàn)在接口或抽象類中不存在的public方法

參數(shù)類型、引用對象盡量使用接口或者抽象類,而不是實現(xiàn)類

抽象層盡量保持穩(wěn)定,一旦確定即不允許修改

5.開閉原則的優(yōu)點:

可復(fù)用性

可維護(hù)性

6.開閉原則圖解:

C# 里氏代換原則

1.概述: 派生類(子類)對象能夠替換其基類(父類)對象被調(diào)用

2.概念:

里氏代換原則(Liskov Substitution Principle LSP)面向?qū)ο笤O(shè)計的基本原則之一。 里氏代換原則中說,任何基類可以出現(xiàn)的地方,子類一定可以出現(xiàn)。 LSP是繼承復(fù)用的基石,只有當(dāng)衍生類可以替換掉基類,軟件單位的功能不受到影響時,基類才能真正被復(fù)用,而衍生類也能夠在基類的基礎(chǔ)上增加新的行為。里氏代換原則是對“開-閉”原則的補(bǔ)充。實現(xiàn)“開-閉”原則的關(guān)鍵步驟就是抽象化。而基類與子類的繼承關(guān)系就是抽象化的具體實現(xiàn),所以里氏代換原則是對實現(xiàn)抽象化的具體步驟的規(guī)范。(源自百度百科)

3.子類為什么可以替換父類的位置?:

當(dāng)滿足繼承的時候,父類肯定存在非私有成員,子類肯定是得到了父類的這些非私有成員(假設(shè),父類的的成員全部是私有的,那么子類沒辦法從父類繼承任何成員,也就不存在繼承的概念了)。既然子類繼承了父類的這些非私有成員,那么父類對象也就可以在子類對象中調(diào)用這些非私有成員。所以,子類對象可以替換父類對象的位置。

4.C# 里氏代換原則優(yōu)點:

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

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

代碼正文:

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# 依賴倒轉(zhuǎn)原則

1.概念:

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

2.C# 依賴倒轉(zhuǎn)原則用處:

有些時候為了代碼復(fù)用,一般會把常用的代碼寫成函數(shù)或類庫。這樣開發(fā)新項目時,直接用就行了。比如做項目時大多要訪問數(shù)據(jù)庫,所以我們就把訪問數(shù)據(jù)庫的代碼寫成了函數(shù)。每次做項目去調(diào)用這些函數(shù)。那么我們的問題來了。我們要做新項目時,發(fā)現(xiàn)業(yè)務(wù)邏輯的高層模塊都是一樣的,但客戶卻希望使用不同的數(shù)據(jù)庫或存儲住處方式,這時就出現(xiàn)麻煩了。我們希望能再次利用這些高層模塊,但高層模塊都是與低層的訪問數(shù)據(jù)庫綁定在一起,沒辦法復(fù)用這些高層模塊。所以不管是高層模塊和低層模塊都應(yīng)該依賴于抽象,具體一點就是接口或抽象類,只要接口是穩(wěn)定的,那么任何一個更改都不用擔(dān)心了。

3.注意事項:

高層模塊不應(yīng)該依賴低層模塊。兩個都應(yīng)該依賴抽象。

抽象不應(yīng)該依賴結(jié)節(jié)。細(xì)節(jié)應(yīng)該依賴抽象。

4.模擬場景:

場景:

假設(shè)現(xiàn)在需要一個Monitor工具,去運行一些已有的APP,自動化來完成我們的工作。Monitor工具需要啟動這些已有的APP,并且寫下Log。

代碼實現(xiàn)1:

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:

在代碼實現(xiàn)1中我們已經(jīng)輕松實現(xiàn)了Monitor去運行已有APP并且寫下LOG的需求。并且代碼已經(jīng)上線了.

春…夏…秋…冬…

春…夏…秋…冬…

春…夏…秋…冬…

就這樣,三年過去了。

一天客戶找上門了,公司業(yè)務(wù)擴(kuò)展了,現(xiàn)在需要新加3個APP用Monitor自動化。這樣我們就必須得改Monitor。

代碼實現(xiàn)2:

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:

這樣會給系統(tǒng)添加新的相互依賴。并且隨著時間和需求的推移,會有更多的APP需要用Monitor來監(jiān)測,這個Monitor工具也會被越來越對的if…else撐爆炸,而且代碼隨著APP越多,越難維護(hù)。最終會導(dǎo)致Monitor走向滅亡(下線)。

介于這種情況,可以用Monitor這個模塊來生成其它的程序,使得系統(tǒng)能夠用在需要的APP上。OOD給我們提供了一種機(jī)制來實現(xiàn)這種“依賴倒置”。

代碼實現(xiàn)3:

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:

現(xiàn)在Monitor依賴于IApp這個接口,而與具體實現(xiàn)的APP類沒有關(guān)系,所以無論再怎么添加APP都不會影響到Monitor本身,只需要去添加一個實現(xiàn)IApp接口的APP類就可以了。

C# 接口隔離原則

1.概念:

客戶端不應(yīng)該依賴它不需要的接口,類間的依賴關(guān)系應(yīng)該建立在最小的接口上

2.含義:

接口隔離原則的核心定義,不出現(xiàn)臃腫的接口(Fat Interface),但是“小”是有限度的,首先就是不能違反單一職責(zé)原則。

3.模擬場景:

一個OA系統(tǒng),外部只負(fù)責(zé)提交和撤回工作流,內(nèi)部負(fù)責(zé)審核和駁回工作流。

4.代碼演示:

namespaceTestLibrary.ExtensionsClass

{

usingSystem;

usingSystem.Collections.Generic;

usingSystem.Linq;

usingSystem.Text;

usingSystem.Threading.Tasks;

publicinterfaceIReview

{

voidReviewWorkFlow();

voidRejectWorkFlow();

}

publicclassReview : IReview

{

publicvoidReviewWorkFlow()

{

Console.WriteLine("開始審核工作流");

}

publicvoidRejectWorkFlow()

{

Console.WriteLine("已經(jīng)駁回工作流");

}

}

publicinterfaceISubmit

{

voidSubmitWorkFlow();

voidCancelWorkFlow();

}

publicclassSubmit : ISubmit

{

publicvoidSubmitWorkFlow()

{

Console.WriteLine("開始提交工作流");

}

publicvoidCancelWorkFlow()

{

Console.WriteLine("已經(jīng)撤銷工作流");

}

}

}

5.代碼解析:

其實接口隔離原則很好理解,在上面的例子里可以看出來,如果把OA的外部和內(nèi)部都定義一個接口的話,那這個接口會很大,而且實現(xiàn)接口的類也會變得臃腫。

C# 合成/聚合復(fù)用原則

1.概念:

合成/聚合復(fù)用原則(Composite/Aggregate Reuse Principle,CARP)經(jīng)常又叫做合成復(fù)用原則。合成/聚合復(fù)用原則就是在一個新的對象里面使用一些已有的對象,使之成為新對象的一部分;新的對象通過向這些對象的委派達(dá)到復(fù)用已有功能的目的。它的設(shè)計原則是:要盡量使用合成/聚合,盡量不要使用繼承。

2.合成/聚合解析:

聚合概念:

聚合用來表示“擁有”關(guān)系或者整體與部分的關(guān)系。代表部分的對象有可能會被多個代表整體的對象所共享,而且不一定會隨著某個代表整體的對象被銷毀或破壞而被銷毀或破壞,部分的生命周期可以超越整體。例如,Iphone5和IOS,當(dāng)Iphone5刪除后,IOS還能存在,IOS可以被Iphone6引用。

聚合關(guān)系UML類圖:

C# 合成/聚合復(fù)用原則

代碼演示:

namespaceTestLibrary.ExtensionsClass

{

classIOS

{

}

classIphone5

{

privateIOS ios;

publicIphone5(IOS ios)

{

this.ios = ios;

}

}

}

合成概念:

合成用來表示一種強(qiáng)得多的“擁有”關(guān)系。在一個合成關(guān)系里,部分和整體的生命周期是一樣的。一個合成的新對象完全擁有對其組成部分的支配權(quán),包括它們的創(chuàng)建和湮滅等。使用程序語言的術(shù)語來說,合成而成的新對象對組成部分的內(nèi)存分配、內(nèi)存釋放有絕對的責(zé)任。一個合成關(guān)系中的成分對象是不能與另一個合成關(guān)系共享的。一個成分對象在同一個時間內(nèi)只能屬于一個合成關(guān)系。如果一個合成關(guān)系湮滅了,那么所有的成分對象要么自己湮滅所有的成分對象(這種情況較為普遍)要么就得將這一責(zé)任交給別人(較為罕見)。例如:水和魚的關(guān)系,當(dāng)水沒了,魚也不可能獨立存在。

合成關(guān)系UML類圖:

代碼演示:

namespaceTestLibrary.ExtensionsClass

{

usingSystem;

classFish

{

publicFish CreateFish()

{

Console.WriteLine("一條小魚兒");

returnnewFish();

}

}

classWater

{

privateFish fish;

publicWater()

{

fish =newFish();

}

publicvoidCreateWater()

{

// 當(dāng)創(chuàng)建了一個水的地方,那這個地方也得放點魚進(jìn)去

fish.CreateFish();

}

}

}

3.模擬場景:

比如說我們先搖到號(這個比較困難)了,需要為自己買一輛車,如果4S店里的車默認(rèn)的配置都是一樣的。那么我們只要買車就會有這些配置,這時使用了繼承關(guān)系:

不可能所有汽車的配置都是一樣的,所以就有SUV和小轎車兩種(只列舉兩種比較熱門的車型),并且使用機(jī)動車對它們進(jìn)行聚合使用。這時采用了合成/聚合的原則:

C# 迪米特法則

1.概念:

一個軟件實體應(yīng)當(dāng)盡可能少的與其他實體發(fā)生相互作用。每一個軟件單位對其他的單位都只有最少的知識,而且局限于那些與本單位密切相關(guān)的軟件單位。迪米特法則的初衷在于降低類之間的耦合。由于每個類盡量減少對其他類的依賴,因此,很容易使得系統(tǒng)的功能模塊功能獨立,相互之間不存在(或很少有)依賴關(guān)系。迪米特法則不希望類之間建立直接的聯(lián)系。如果真的有需要建立聯(lián)系,也希望能通過它的友元類來轉(zhuǎn)達(dá)。因此,應(yīng)用迪米特法則有可能造成的一個后果就是:系統(tǒng)中存在大量的中介類,這些類之所以存在完全是為了傳遞類之間的相互調(diào)用關(guān)系——這在一定程度上增加了系統(tǒng)的復(fù)雜度。

2.模擬場景:

場景:公司財務(wù)總監(jiān)發(fā)出指令,讓財務(wù)部門的人去統(tǒng)計公司已發(fā)公司的人數(shù)。

一個常態(tài)的編程:(肯定是不符LoD的反例)

UML類圖:

代碼演示:

namespaceTestLibrary.ExtensionsClass

{

usingSystem;

usingSystem.Collections.Generic;

///

/// 財務(wù)總監(jiān)

///

publicclassCFO

{

///

/// 財務(wù)總監(jiān)發(fā)出指令,讓財務(wù)部門統(tǒng)計已發(fā)工資人數(shù)

///

publicvoidDirective(Finance finance)

{

List employeeList =newList();

// 初始化已發(fā)工資人數(shù)

for(inti = 0; i < 500; i++)

{

employeeList.Add(newEmployee());

}

// 轉(zhuǎn)告財務(wù)部門開始統(tǒng)計已結(jié)算公司的員工

finance.SettlementSalary(employeeList);

}

}

///

/// 財務(wù)部

///

publicclassFinance

{

///

/// 統(tǒng)計已結(jié)算公司的員工

///

publicvoidSettlementSalary(List employeeList)

{

Console.WriteLine(string.Format("已結(jié)算工資人數(shù):{0}", employeeList.Count));

}

}

///

/// 員工

///

publicclassEmployee

{

}

///

/// 主程序

///

publicclassRunner

{

publicstaticvoidmain(String[] args)

{

CFO cfo =newCFO();

// 財務(wù)總監(jiān)發(fā)出指令

cfo.Directive(newFinance());

}

}

}

根據(jù)模擬的場景:財務(wù)總監(jiān)讓財務(wù)部門總結(jié)已發(fā)工資的人數(shù)。 財務(wù)總監(jiān)和員工是陌生關(guān)系(即總監(jiān)不需要對員工執(zhí)行任何操作)。根據(jù)上述UML圖和代碼解決辦法顯然可以看出,上述做法違背了LoD法則。

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

UML類圖:

代碼演示:

namespaceTestLibrary.ExtensionsClass

{

usingSystem;

usingSystem.Collections.Generic;

///

/// 財務(wù)總監(jiān)

///

publicclassCFO

{

///

/// 財務(wù)總監(jiān)發(fā)出指令,讓財務(wù)部門統(tǒng)計已發(fā)工資人數(shù)

///

publicvoidDirective(Finance finance)

{

// 通知財務(wù)部門開始統(tǒng)計已結(jié)算公司的員工

finance.SettlementSalary();

}

}

///

/// 財務(wù)部

///

publicclassFinance

{

privateList employeeList;

//傳遞公司已工資的人

publicFinance(List _employeeList)

{

this.employeeList = _employeeList;

}

///

/// 統(tǒng)計已結(jié)算公司的員工

///

publicvoidSettlementSalary()

{

Console.WriteLine(string.Format("已結(jié)算工資人數(shù):{0}", employeeList.Count));

}

}

///

/// 員工

///

publicclassEmployee

{

}

///

/// 主程序

///

publicclassRunner

{

publicstaticvoidmain(String[] args)

{

List employeeList =newList();

// 初始化已發(fā)工資人數(shù)

for(inti = 0; i < 500; i++)

{

employeeList.Add(newEmployee());

}

CFO cfo =newCFO();

// 財務(wù)總監(jiān)發(fā)出指令

cfo.Directive(newFinance(employeeList));

}

}

}

根據(jù)LoD原則我們需要讓財務(wù)總監(jiān)和員工之間沒有之間的聯(lián)系。這樣才是遵守了迪米特法則。

博客總結(jié)

想搞懂設(shè)計模式,必須先知道設(shè)計模式遵循的六大原則,無論是哪種設(shè)計模式都會遵循一種或者多種原則。這是面向?qū)ο蟛蛔兊姆▌t。本文針對的是設(shè)計模式(面向?qū)ο?主要的六大原則展開的講解,并盡量做到結(jié)合實例和UML類圖,幫助大家理解。在后續(xù)的博文中還會跟進(jìn)一些設(shè)計模式的實例。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 設(shè)計模式之六大原則(轉(zhuǎn)載) 關(guān)于設(shè)計模式的六大設(shè)計原則的資料網(wǎng)上很多...
    霄霄霄霄閱讀 943評論 0 1
  • 1. [C#語言基礎(chǔ)]請簡述拆箱和裝箱。 答: 裝箱操作: 值類型隱式轉(zhuǎn)換為object類型或由此值類型實現(xiàn)的任何...
    胤醚貔貅閱讀 4,978評論 1 28
  • 本文出自《Android源碼設(shè)計模式解析與實戰(zhàn)》中的第一章。 1、優(yōu)化代碼的第一步——單一職責(zé)原則 單一職責(zé)原則的...
    MrSimp1e0閱讀 1,911評論 1 13
  • 我們在應(yīng)用開發(fā)中,一般要求盡量做到可維護(hù)性和可復(fù)用性 應(yīng)用程序的復(fù)用可以提高應(yīng)用程序的開發(fā)效率和質(zhì)量,節(jié)約開發(fā)成本...
    Yochi閱讀 583評論 0 0
  • 人們常容易犯的錯的是將生活和任務(wù)混淆,把某個目標(biāo)當(dāng)做當(dāng)下生活的一切。每天醒來,睜開眼睛,第一件事就是開始想,哦,今...
    蘇錸閱讀 194評論 0 1

友情鏈接更多精彩內(nèi)容