前言
設計模式(Design pattern)是一套被反覆使用、多數人知曉的、經過分類編目的、程式碼設計經驗的總結。使用設計模式是為了可重用程式碼、讓程式碼更容易被他人理解、保證程式碼可靠性。 毫無疑問,設計模式於己於他人於系統都是多贏的;設計模式使程式碼編制真正工程化;設計模式是軟體工程的基石脈絡,如同大廈的結構一樣。
借用並改編一下魯迅老師《故鄉》中的一句話,一句話概括設計模式: 希望本無所謂有,無所謂無.這正如coding的設計模式,其實coding本沒有設計模式,用的人多了,也便成了設計模式
設計模式的六大原則:1.開閉原則(Open Closed Principle,OCP)
2.里氏代換原則(Liskov Substitution Principle,LSP)
3.依賴倒轉原則(Dependency Inversion Principle,DIP)
4.介面隔離原則(Interface Segregation Principle,ISP)
5.合成/聚合複用原則(Composite/Aggregate Reuse Principle,CARP)
6.最小知識原則(Principle of Least Knowledge,PLK,也叫迪米特法則)
開閉原則具有理想主義的色彩,它是面向物件設計的終極目標。其他幾條,則可以看做是開閉原則的實現方法。 設計模式就是實現了這些原則,從而達到了程式碼複用、增加可維護性的目的。
開閉原則1.概念:
一個軟體實體如類、模組和函式應該對擴充套件開放,對修改關閉。模組應儘量在不修改原(是“原”,指原來的程式碼)程式碼的情況下進行擴充套件。
2.模擬場景:
在軟體的生命週期內,因為變化、升級和維護等原因需要對軟體原有程式碼進行修改時,可能會給舊程式碼中引入錯誤,也可能會使我們不得不對整個功能進行重構,並且需要原有程式碼經過重新測試。
3.Solution:
當軟體需要變化時,儘量透過擴充套件軟體實體的行為來實現變化,而不是透過修改已有的程式碼來實現變化。
4.注意事項:
透過介面或者抽象類約束擴充套件,對擴充套件進行邊界限定,不允許出現在介面或抽象類中不存在的public方法
引數型別、引用物件儘量使用介面或者抽象類,而不是實現類
抽象層儘量保持穩定,一旦確定即不允許修改
5.開閉原則的優點:
可複用性
可維護性
6.開閉原則圖解:
里氏代換原則1.概述:
派生類(子類)物件能夠替換其基類(父類)物件被呼叫
2.概念:
里氏代換原則(Liskov Substitution Principle LSP)面向物件設計的基本原則之一。 里氏代換原則中說,任何基類可以出現的地方,子類一定可以出現。 LSP是繼承複用的基石,只有當衍生類可以替換掉基類,軟體單位的功能不受到影響時,基類才能真正被複用,而衍生類也能夠在基類的基礎上增加新的行為。里氏代換原則是對“開-閉”原則的補充。實現“開-閉”原則的關鍵步驟就是抽象化。而基類與子類的繼承關係就是抽象化的具體實現,所以里氏代換原則是對實現抽象化的具體步驟的規範。(源自百度百科)
3.子類為什麼可以替換父類的位置?:
當滿足繼承的時候,父類肯定存在非私有成員,子類肯定是得到了父類的這些非私有成員(假設,父類的的成員全部是私有的,那麼子類沒辦法從父類繼承任何成員,也就不存在繼承的概念了)。既然子類繼承了父類的這些非私有成員,那麼父類物件也就可以在子類物件中呼叫這些非私有成員。所以,子類物件可以替換父類物件的位置。
4.里氏代換原則優點:
需求變化時,只須繼承,而別的東西不會改變。由於里氏代換原則才使得開放封閉成為可能。這樣使得子類在父類無需修改的話就可以擴充套件。
5.里氏代換原則Demo:
程式碼正文:
//------------------------------------------------------------------------------// <copyright file="Program.cs" company="CNBlogs Corporation">// Copyright (C) 2015-2016 All Rights Reserved// </copyright> //------------------------------------------------------------------------------namespace TestApp{ using System; class Program { static void Main(string[] args) { Transportation transportation = new Transportation(); transportation.Say(); Transportation sedan = new Sedan(); sedan.Say(); Console.ReadKey(); } } class Transportation { public Transportation() { Console.WriteLine("Transportation?"); } public virtual void Say() { Console.WriteLine("121"); } } class Sedan:Transportation { public Sedan() { Console.WriteLine("Transportation:Sedan"); } public override void Say() { Console.WriteLine("Sedan"); } } class Bicycles : Transportation { public Bicycles() { Console.WriteLine("Transportation:Bicycles"); } public override void Say() { Console.WriteLine("Bicycles"); } }}
程式碼效果:
6.里氏代換原則圖解:
依賴倒轉原則1.概念:
依賴倒置原則(Dependence Inversion Principle)是程式要依賴於抽象介面,不要依賴於具體實現。簡單的說就是要求對抽象進行程式設計,不要對實現進行程式設計,這樣就降低了客戶與實現模組間的耦合。
2.依賴倒轉原則用處:
3.注意事項:
高層模組不應該依賴低層模組。兩個都應該依賴抽象。抽象不應該依賴細節。細節應該依賴抽象。4.模擬場景:
場景:
假設現在需要一個Monitor工具,去執行一些已有的APP,自動化來完成我們的工作。Monitor工具需要啟動這些已有的APP,並且寫下Log。
程式碼實現1:
//------------------------------------------------------------------------------// <copyright file="Dependency.cs" company="CNBlogs Corporation">// Copyright (C) 2015-2016 All Rights Reserved// </copyright> //------------------------------------------------------------------------------namespace TestLibrary.ExtensionsClass{ using System; public class AppOne { public bool Start() { Console.WriteLine("1號APP開始啟動"); return true; } public bool ExportLog() { Console.WriteLine("1號APP輸出日誌"); return true; } } public class AppTwo { public bool Start() { Console.WriteLine("2號APP開始啟動"); return true; } public bool ExportLog() { Console.WriteLine("2號APP輸出日誌"); return true; } } public class Monitor { public enum AppNumber { AppOne=1, AppTwo=2 } private AppOne appOne = new AppOne(); private AppTwo appTwo = new AppTwo(); private AppNumber number; public Monitor(AppNumber number) { this.number = number; } public bool StartApp() { return number == AppNumber.AppOne ? appOne.Start() : appTwo.Start(); } public bool ExportAppLog() { return number == AppNumber.AppOne ? appOne.ExportLog() : appTwo.ExportLog(); } }}
程式碼解析1:
在程式碼實現1中我們已經輕鬆實現了Monitor去執行已有APP並且寫下LOG的需求。並且程式碼已經上線了.
春...夏...秋...冬...
春...夏...秋...冬...
春...夏...秋...冬...
就這樣,三年過去了。
一天客戶找上門了,公司業務擴充套件了,現在需要新加3個APP用Monitor自動化。這樣我們就必須得改Monitor。
程式碼實現2:
//------------------------------------------------------------------------------// <copyright file="Dependency.cs" company="CNBlogs Corporation">// Copyright (C) 2015-2016 All Rights Reserved// </copyright> //------------------------------------------------------------------------------namespace TestLibrary.ExtensionsClass{ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; public class Monitor { public enum AppNumber { AppOne = 1, AppTwo = 2, AppThree = 3, AppFour = 4, AppFive = 5 } private AppOne appOne = new AppOne(); private AppTwo appTwo = new AppTwo(); private AppThree appThree = new AppThree(); private AppFour appFour = new AppFour(); private AppFive appFive = new AppFive(); private AppNumber number; public Monitor(AppNumber number) { this.number = number; } public bool StartApp() { bool result = false; if (number == AppNumber.AppOne) { result = appOne.Start(); } else if (number == AppNumber.AppTwo) { result = appTwo.Start(); } else if (number == AppNumber.AppThree) { result = appThree.Start(); } else if (number == AppNumber.AppFour) { result = appFour.Start(); } else if (number == AppNumber.AppFive) { result = appFive.Start(); } return result; } public bool ExportAppLog() { bool result = false; if (number == AppNumber.AppOne) { result = appOne.ExportLog(); } else if (number == AppNumber.AppTwo) { result = appTwo.ExportLog(); } else if (number == AppNumber.AppThree) { result = appThree.ExportLog(); } else if (number == AppNumber.AppFour) { result = appFour.ExportLog(); } else if (number == AppNumber.AppFive) { result = appFive.ExportLog(); } return result; } }}
程式碼解析2:
這樣會給系統新增新的相互依賴。並且隨著時間和需求的推移,會有更多的APP需要用Monitor來監測,這個Monitor工具也會被越來越對的if...else撐爆炸,而且程式碼隨著APP越多,越難維護。最終會導致Monitor走向滅亡(下線)。
介於這種情況,可以用Monitor這個模組來生成其它的程式,使得系統能夠用在需要的APP上。OOD給我們提供了一種機制來實現這種“依賴倒置”。
程式碼實現3:
//------------------------------------------------------------------------------// <copyright file="Dependency.cs" company="CNBlogs Corporation">// Copyright (C) 2015-2016 All Rights Reserved// </copyright> //------------------------------------------------------------------------------namespace TestLibrary.ExtensionsClass{ using System; public interface IApp { bool Start(); bool ExportLog(); } public class AppOne : IApp { public bool Start() { Console.WriteLine("1號APP開始啟動"); return true; } public bool ExportLog() { Console.WriteLine("1號APP輸出日誌"); return true; } } public class AppTwo : IApp { public bool Start() { Console.WriteLine("2號APP開始啟動"); return true; } public bool ExportLog() { Console.WriteLine("2號APP輸出日誌"); return true; } } public class Monitor { private IApp iapp; public Monitor(IApp iapp) { this.iapp = iapp; } public bool StartApp() { return iapp.Start(); } public bool ExportAppLog() { return iapp.ExportLog(); } }}
程式碼解析3:
現在Monitor依賴於IApp這個介面,而與具體實現的APP類沒有關係,所以無論再怎麼新增APP都不會影響到Monitor本身,只需要去新增一個實現IApp介面的APP類就可以了。
介面隔離原則1.概念:
客戶端不應該依賴它不需要的介面,類間的依賴關係應該建立在最小的介面上
2.含義:
介面隔離原則的核心定義,不出現臃腫的介面(Fat Interface),但是“小”是有限度的,首先就是不能違反單一職責原則。
3.模擬場景:
一個OA系統,外部只負責提交和撤回工作流,內部負責稽核和駁回工作流。
4.程式碼演示:
//------------------------------------------------------------------------------// <copyright file="Dependency.cs" company="CNBlogs Corporation">// Copyright (C) 2015-2016 All Rights Reserved// </copyright> //------------------------------------------------------------------------------namespace TestLibrary.ExtensionsClass{ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; public interface IReview { void ReviewWorkFlow(); void RejectWorkFlow(); } public class Review : IReview { public void ReviewWorkFlow() { Console.WriteLine("開始稽核工作流"); } public void RejectWorkFlow() { Console.WriteLine("已經駁回工作流"); } } public interface ISubmit { void SubmitWorkFlow(); void CancelWorkFlow(); } public class Submit : ISubmit { public void SubmitWorkFlow() { Console.WriteLine("開始提交工作流"); } public void CancelWorkFlow() { Console.WriteLine("已經撤銷工作流"); } }}
5.程式碼解析:
其實介面隔離原則很好理解,在上面的例子裡可以看出來,如果把OA的外部和內部都定義一個介面的話,那這個介面會很大,而且實現介面的類也會變得臃腫。
合成/聚合複用原則1.概念:合成/聚合複用原則(Composite/Aggregate Reuse Principle,CARP)經常又叫做合成複用原則。合成/聚合複用原則就是在一個新的物件裡面使用一些已有的物件,使之成為新物件的一部分;新的物件透過向這些物件的委派達到複用已有功能的目的。它的設計原則是:要儘量使用合成/聚合,儘量不要使用繼承。
2.合成/聚合解析:
聚合概念: 聚合用來表示“擁有”關係或者整體與部分的關係。代表部分的物件有可能會被多個代表整體的物件所共享,而且不一定會隨著某個代表整體的物件被銷燬或破壞而被銷燬或破壞,部分的生命週期可以超越整體。例如,Iphone5和IOS,當Iphone5刪除後,IOS還能存在,IOS可以被Iphone6引用。聚合關係UML類圖: 程式碼演示://------------------------------------------------------------------------------// <copyright file="Dependency.cs" company="CNBlogs Corporation">// Copyright (C) 2015-2016 All Rights Reserved// </copyright> //------------------------------------------------------------------------------namespace TestLibrary.ExtensionsClass{ class IOS { } class Iphone5 { private IOS ios; public Iphone5(IOS ios) { this.ios = ios; } }}
合成概念: 合成用來表示一種強得多的“擁有”關係。在一個合成關係裡,部分和整體的生命週期是一樣的。一個合成的新物件完全擁有對其組成部分的支配權,包括它們的建立和湮滅等。使用程式語言的術語來說,合成而成的新物件對組成部分的記憶體分配、記憶體釋放有絕對的責任。一個合成關係中的成分物件是不能與另一個合成關係共享的。一個成分物件在同一個時間內只能屬於一個合成關係。如果一個合成關係湮滅了,那麼所有的成分物件要麼自己湮滅所有的成分物件(這種情況較為普遍)要麼就得將這一責任交給別人(較為罕見)。例如:水和魚的關係,當水沒了,魚也不可能獨立存在。
合成關係UML類圖:
程式碼演示:
//------------------------------------------------------------------------------// <copyright file="Dependency.cs" company="CNBlogs Corporation">// Copyright (C) 2015-2016 All Rights Reserved// </copyright> //------------------------------------------------------------------------------namespace TestLibrary.ExtensionsClass{ using System; class Fish { public Fish CreateFish() { Console.WriteLine("一條小魚兒"); return new Fish(); } } class Water { private Fish fish; public Water() { fish = new Fish(); } public void CreateWater() { // 當建立了一個水的地方,那這個地方也得放點魚進去 fish.CreateFish(); } }}
3.模擬場景:
比如說我們先搖到號(這個比較困難)了,需要為自己買一輛車,如果4S店裡的車預設的配置都是一樣的。那麼我們只要買車就會有這些配置,這時使用了繼承關係:
不可能所有汽車的配置都是一樣的,所以就有SUV和小轎車兩種(只列舉兩種比較熱門的車型),並且使用機動車對它們進行聚合使用。這時採用了合成/聚合的原則:
迪米特法則1.概念:
2.模擬場景:
場景:公司財務總監發出指令,讓財務部門的人去統計公司已發公司的人數。
一個常態的程式設計:(肯定是不符LoD的反例)
UML類圖:
程式碼演示:
//------------------------------------------------------------------------------// <copyright file="Dependency.cs" company="CNBlogs Corporation">// Copyright (C) 2015-2016 All Rights Reserved// </copyright> //------------------------------------------------------------------------------namespace TestLibrary.ExtensionsClass{ using System; using System.Collections.Generic; /// <summary> /// 財務總監 /// </summary> public class CFO { /// <summary> /// 財務總監發出指令,讓財務部門統計已發工資人數 /// </summary> public void Directive(Finance finance) { List<Employee> employeeList = new List<Employee>(); // 初始化已發工資人數 for (int i = 0; i < 500; i++) { employeeList.Add(new Employee()); } // 轉告財務部門開始統計已結算公司的員工 finance.SettlementSalary(employeeList); } } /// <summary> /// 財務部 /// </summary> public class Finance { /// <summary> /// 統計已結算公司的員工 /// </summary> public void SettlementSalary(List<Employee> employeeList) { Console.WriteLine(string.Format("已結算工資人數:{0}", employeeList.Count)); } } /// <summary> /// 員工 /// </summary> public class Employee { } /// <summary> /// 主程式 /// </summary> public class Runner { public static void main(String[] args) { CFO cfo = new CFO(); // 財務總監發出指令 cfo.Directive(new Finance()); } }}
根據模擬的場景:財務總監讓財務部門總結已發工資的人數。 財務總監和員工是陌生關係(即總監不需要對員工執行任何操作)。根據上述UML圖和程式碼解決辦法顯然可以看出,上述做法違背了LoD法則。
依據LoD法則解耦:(符合LoD的例子)
UML類圖:
程式碼演示:
//------------------------------------------------------------------------------// <copyright file="Dependency.cs" company="CNBlogs Corporation">// Copyright (C) 2015-2016 All Rights Reserved// </copyright> //------------------------------------------------------------------------------namespace TestLibrary.ExtensionsClass{ using System; using System.Collections.Generic; /// <summary> /// 財務總監 /// </summary> public class CFO { /// <summary> /// 財務總監發出指令,讓財務部門統計已發工資人數 /// </summary> public void Directive(Finance finance) { // 通知財務部門開始統計已結算公司的員工 finance.SettlementSalary(); } } /// <summary> /// 財務部 /// </summary> public class Finance { private List<Employee> employeeList; //傳遞公司已工資的人 public Finance(List<Employee> _employeeList) { this.employeeList = _employeeList; } /// <summary> /// 統計已結算公司的員工 /// </summary> public void SettlementSalary() { Console.WriteLine(string.Format("已結算工資人數:{0}", employeeList.Count)); } } /// <summary> /// 員工 /// </summary> public class Employee { } /// <summary> /// 主程式 /// </summary> public class Runner { public static void main(String[] args) { List<Employee> employeeList = new List<Employee>(); // 初始化已發工資人數 for (int i = 0; i < 500; i++) { employeeList.Add(new Employee()); } CFO cfo = new CFO(); // 財務總監發出指令 cfo.Directive(new Finance(employeeList)); } }}
根據LoD原則我們需要讓財務總監和員工之間沒有之間的聯絡。這樣才是遵守了迪米特法則。
總結想搞懂設計模式,必須先知道設計模式遵循的六大原則,無論是哪種設計模式都會遵循一種或者多種原則。這是面向物件不變的法則。本文針對的是設計模式(面向物件)主要的六大原則展開的講解,並儘量做到結合例項和UML類圖,幫助大家理解。在後續的博文中還會跟進一些設計模式的例項。
原文:www.cnblogs.com/toutou