最近學習了李建忠老師設計模式教程,感受有一種豁然開朗的感受。數據庫
"每個模式描述了在咱們周圍不斷重複發生變化的問題,以及該問題的解決方案的核心。這樣,你就能一次又一次的使用該方案而沒必要重複的勞動"——christopher alexander編程
固然,設計模式的有本很著名的書,常考教程:設計模式
書名:設計模式:可複用面向對象軟件的基礎學習
固然,我不是推銷書的。主要是書名很明確的給出了,軟件設計模式中使用的手法,經常使用就是面向對象,確實,在我第一次聽到面向對象的時候,個人理解是徹底不懂,在個人二次學習面向對象的時候,個人理解是封裝,繼承,多態。而到如今,我以爲,咱們的最終目的,仍是編寫可複用,可擴展,便於維護的軟件。固然,如何把面向對象的特性運用到極致,我想還得深刻去理解設計模式,否則,不少時候,即便咱們用了面嚮對象語言編程,每每出現拔苗助長的效果。spa
這裏先給出面向對象五大基本原則:設計
(DIP)依賴倒置原則:3d
高層模塊(穩定)不該該依賴於底層模塊(變化),兩者都應該依賴於抽象(穩定)。code
抽象(穩定)不該該依賴於實現細節(變化),實現細節依賴於抽象(穩定)。對象
個人理解:總結爲,穩定的東西不該該依賴於變化的東西。在面向對象設計過程當中,可能有時候,我會把一些具體實現依賴另一些具體實現,可是咱們知道,具體實現是容易變化的,若是依賴的實現發現變化,意味着上層也將發生變化。這裏的變化,不是說具體的邏輯變更,而是接口變更,簡單的說就是,面向對象應該面向抽象接口來實現,而不是面向具體的邏輯,這個抽象接口應該是十分穩定的,依賴於一個穩定的接口的好處,就是,咱們具體實現和耦合性會比較低。這意味着咱們須要很敏感的發現系統的穩定點和變化點,將穩定點變成抽象接口,把變化點變成擴展實現。blog
我本身想了一個壞栗子,不知道合不合適:
class Waiter //服務生 { } class Cater //計算員 { } class Guard //保安 { }
class Bank { public: vector<Waiter> waiterList; vector<Cater> caterList; vector<Guard> guardList; protect: void doWork(); } void Bank::doWord() { for(auto a in waiterList) { a.doWord(); } for(auto a in caterList) { a.doWord(); } for(auto a in guardList) { a.doWord(); }
做爲僞代碼,隨意看看吧,我想意思很簡單,首先有個員工文件,裏面我定義了三種員工,而後銀行定義了員工的LIst,固然,實際可能還須要添加員工犯法,dowork方法用來讓他的員工工做,代碼冗餘問題雖然還能夠解決,可是,最大的問題是,你應該發現,員工種類咱們假設是須要擴展的,這種方式,就是具體依賴於具體。你能夠假設若是如今多了一種員工,叫作數據庫管理員,那麼你對於上面的代碼應該作那些修改。我想你應該,寫一個類,而後,在銀行類裏面,添加一個數據庫管理員list對象,最後,還要在員工在工做裏面在添加方法。是否是很不利。
我想,好的方式是這樣:
class Staff { public: void doWork()=0; void ~Staff() {} } class Waiter:public Staff //服務生 { public: void doWork(); } class Cater:public Staff //計算員 { void doWork(); } class Guard:public Staff //保安 { void doWork(); }
class Bank { public: vector<Staff *> stafflList; protect: void doWork(); } void Bank::doWord() { for(auto a in staffList) { a.doWork(); }
你如今能夠想象下,若是這種狀況下,老闆讓你添加一種新的員工,叫作數據庫,管理員,那麼是否是方便,你只須要,在寫一個類,而後繼承抽象基類staff便可。這也就是面向抽象基礎編程。
開放封閉原則(OCP)
對擴展開放,對更改封閉。
類模塊應該可擴展的,可是不可更改。
個人理解:開放封閉原則,是最爲重要的設計原則。然後面的Liskov替換原則和合成/聚合複用原則爲開放封閉原則的實現提供保證。
我這裏無恥的引用百度百科的例子:
class BusyBankStaff { private BankProcess bankProc = new BankProcess(); // 定義銀行員工的業務操做 public void HandleProcess(Client client) { switch (client.ClientType) { case "存款用戶": bankProc.Deposit(); break; case "轉帳用戶": bankProc.Transfer(); break; case "取款戶": bankProc.DrawMoney(); break; } } }
其實這代碼跟第一個原則差很少,對擴展開發怎麼理解,這段代碼問題主要在於,若是有新的業務出現,那麼擴展,將在switch中進行修改,實際上違背了對修改封閉的原則,也就是說,若是出現新的業務,咱們須要的是擴展,而不是修改以前的類。那麼好的實現方法能夠是這樣的:
class IBankProcess { virtual void Process()=0; }
<-接口基類
class DepositProcess : IBankProcess { //IBankProcess Members public void : Process() { // 辦理存款業務 } } class TransferProcess : IBankProcess { //IBankProcess Members public : void Process() { // 辦理轉帳業務 } } class DrawMoneyProcess : IBankProcess { //IBankProcess Members public : void Process() { // 辦理取款業務 } }
<-不一樣業務
class EasyBankStaff { private : IBankProcess *bankProc = NULL; public : void HandleProcess(Client client) { bankProc = client.CreateProcess(); bankProc.Process(); } }
<-用戶切換
class BankProcess { public: void Main() { EasyBankStaff bankStaff = new EasyBankStaff(); bankStaff.HandleProcess(new Client("轉帳用戶")); } }
處理業務,就這個方法而言,對於銀行業務擴展,咱們只須要定義新的類,這叫對擴展開放,而不須要修改原先類的代碼,這叫對修改封閉。這樣一個原則,是否是很像一個衡量標準呢。
單一職責原則(SRP)
一個類應該僅有一個引發它變化的緣由。
變化的方向隱含着類的責任。
這是相對比較好理解的吧,"若是一個類承擔的職責過多,就等於把這些職責耦合在一塊兒了。一個職責的變化可能會削弱或者抑制這個類完成其餘職責的能力。這種耦合會致使脆弱的設計,當發生變化時,設計會遭受到意想不到的破壞。而若是想要避免這種現象的發生,就要儘量的遵照單一職責原則。此原則的核心就是解耦和加強內聚性。"
一個類有且只有一個能夠改變的理由。
liskov替換原則(LSP)
子類必須可以替換它們的基類(IS-A)。
繼承表達類型抽象。
接口隔離原則(ISP)
不能夠強迫客戶程序依賴他們不用的方法。
接口應該小而完備。
幾個額外的建議:
組合複用原則(carp):
優先使用對象組合,而不是類繼承
類繼承一般爲「白箱複用」,對象組合一般爲「黑箱複用」。
繼承在某種程度上破壞了封裝性,之類父類耦合度高。
而對象組合則只要求被組合的對象具備良好定義的接口,耦合度低
封閉變化點
使用封裝來建立對象之間的分界層。讓設計者能夠在變化一側進行修改,而不會影響另外一側產生不良影響,從而實現鬆耦合。
針對接口編程
針對接口編程,而不是針對實現編程
1.不將變量類型聲明爲某個特定的具體類,而是聲明爲某個接口
2.客戶程序不須要獲知對象的具體類型,只須要知道對象所具備的接口。
3.減小系統中各部分的依賴關係,從而實現「高內聚,鬆耦合」的設計類型。
對設計模式按照目的分類能夠分爲如下三種:
1.建立型:與對象建立有關。
2.結構型:處理類或對象的組合。
3.行爲型:模式對類或者對象怎麼交互和分配職責進行描述。
按照其做用範圍又可分爲類模式和對象模式 ,前者主要處理類和子類的關係,經過繼承創建,具備靜態,在編譯時刻就穩定下來。後者處理對象之間的關係,這些關係多是運行時刻變化的,具備動態性。
具體模式能夠參考書上的表格:
如何使用設計模式:
我想大概是要達到對症下藥的層次吧,在此以前,咱們更應該關注原則。