天天看點

詳解設計模式六大原則

詳解設計模式六大原則

借用并改編一下魯迅老師《故鄉》中的一句話,一句話概括設計模式: 希望本無所謂有,無所謂無.這正如coding的設計模式,其實coding本沒有設計模式,用的人多了,也便成了設計模式。設計模式(Design pattern)是一套被反複使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是為了可重用代碼、讓代碼更容易被他人了解、保證代碼可靠性。 毫無疑問,設計模式于己于他人于系統都是多赢的;設計模式使代碼編制真正工程化;設計模式是軟體工程的基石脈絡,如同大廈的結構一樣。

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

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

詳解設計模式六大原則

v六大原則

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

  • 開閉原則(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,也叫迪米特法則)

開閉原則具有理想主義的色彩,它是面向對象設計的終極目标。其他幾條,則可以看做是開閉原則的實作方法。 設計模式就是實作了這些原則,進而達到了代碼複用、增加可維護性的目的。

v開閉原則

1.概念: 

一個軟體實體如類、子產品和函數應該對擴充開放,對修改關閉。子產品應盡量在不修改原(是“原”,指原來的代碼)代碼的情況下進行擴充。

2.模拟場景: 

在軟體的生命周期内,因為變化、更新和維護等原因需要對軟體原有代碼進行修改時,可能會給舊代碼中引入錯誤,也可能會使我們不得不對整個功能進行重構,并且需要原有代碼經過重新測試。

3.Solution: 

當軟體需要變化時,盡量通過擴充軟體實體的行為來實作變化,而不是通過修改已有的代碼來實作變化。

4.注意事項: 

  • 通過接口或者抽象類限制擴充,對擴充進行邊界限定,不允許出現在接口或抽象類中不存在的public方法
  • 參數類型、引用對象盡量使用接口或者抽象類,而不是實作類
  • 抽象層盡量保持穩定,一旦确定即不允許修改

5.開閉原則的優點: 

  • 可複用性
  • 可維護性

6.開閉原則圖解: 

詳解設計模式六大原則

v裡氏代換原則

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
//     原博文位址: http://www.cnblogs.com/toutou/
//     作      者: 請叫我頭頭哥
// </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.裡氏代換原則圖解: 

詳解設計模式六大原則

v依賴倒轉原則

依賴倒置原則(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
//     原博文位址: http://www.cnblogs.com/toutou/
//     作      者: 請叫我頭頭哥
// </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
//     原博文位址: http://www.cnblogs.com/toutou/
//     作      者: 請叫我頭頭哥
// </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
//     原博文位址: http://www.cnblogs.com/toutou/
//     作      者: 請叫我頭頭哥
// </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類就可以了。

v接口隔離原則

用戶端不應該依賴它不需要的接口,類間的依賴關系應該建立在最小的接口上

2.含義: 

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

3.模拟場景: 

一個OA系統,外部隻負責送出和撤回工作流,内部負責稽核和駁回工作流。

4.代碼示範: 

//------------------------------------------------------------------------------
// <copyright file="Dependency.cs" company="CNBlogs Corporation">
//     Copyright (C) 2015-2016 All Rights Reserved
//     原博文位址: http://www.cnblogs.com/toutou/
//     作      者: 請叫我頭頭哥
// </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的外部和内部都定義一個接口的話,那這個接口會很大,而且實作接口的類也會變得臃腫。

詳解設計模式六大原則

v合成/聚合複用原則

合成/聚合複用原則(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
    //     原博文位址: http://www.cnblogs.com/toutou/
    //     作      者: 請叫我頭頭哥
    // </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
    //     原博文位址: http://www.cnblogs.com/toutou/
    //     作      者: 請叫我頭頭哥
    // </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();
            }
        }
    }      

比如說我們先搖到号(這個比較困難)了,需要為自己買一輛車,如果4S店裡的車預設的配置都是一樣的。那麼我們隻要買車就會有這些配置,這時使用了繼承關系:

詳解設計模式六大原則

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

詳解設計模式六大原則

v迪米特法則

一個軟體實體應當盡可能少的與其他實體發生互相作用。每一個軟體機關對其他的機關都隻有最少的知識,而且局限于那些與本機關密切相關的軟體機關。迪米特法則的初衷在于降低類之間的耦合。由于每個類盡量減少對其他類的依賴,是以,很容易使得系統的功能子產品功能獨立,互相之間不存在(或很少有)依賴關系。迪米特法則不希望類之間建立直接的聯系。如果真的有需要建立聯系,也希望能通過它的友元類來轉達。是以,應用迪米特法則有可能造成的一個後果就是:系統中存在大量的中介類,這些類之是以存在完全是為了傳遞類之間的互相調用關系——這在一定程度上增加了系統的複雜度。

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

一個常态的程式設計:(肯定是不符LoD的反例)

UML類圖:

詳解設計模式六大原則

代碼示範:

//------------------------------------------------------------------------------
// <copyright file="Dependency.cs" company="CNBlogs Corporation">
//     Copyright (C) 2015-2016 All Rights Reserved
//     原博文位址: http://www.cnblogs.com/toutou/
//     作      者: 請叫我頭頭哥
// </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的例子)

詳解設計模式六大原則
//------------------------------------------------------------------------------
// <copyright file="Dependency.cs" company="CNBlogs Corporation">
//     Copyright (C) 2015-2016 All Rights Reserved
//     原博文位址: http://www.cnblogs.com/toutou/
//     作      者: 請叫我頭頭哥
// </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原則我們需要讓财務總監和員工之間沒有之間的聯系。這樣才是遵守了迪米特法則。

v部落格總結

想搞懂設計模式,必須先知道設計模式遵循的六大原則,無論是哪種設計模式都會遵循一種或者多種原則。這是面向對象不變的法則。本文針對的是設計模式(面向對象)主要的六大原則展開的講解,并盡量做到結合執行個體和UML類圖,幫助大家了解。在後續的博文中還會跟進一些設計模式的執行個體。

作  者:請叫我頭頭哥

出  處:http://www.cnblogs.com/toutou/

關于作者:專注于基礎平台的項目開發。如有問題或建議,請多多賜教!

版權聲明:本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連結。

特此聲明:所有評論和私信都會在第一時間回複。也歡迎園子的大大們指正錯誤,共同進步。或者直接私信我

聲援部落客:如果您覺得文章對您有幫助,可以點選文章右下角【推薦】一下。您的鼓勵是作者堅持原創和持續寫作的最大動力!