程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 詳解設計模式六大原則,詳解設計模式六大

詳解設計模式六大原則,詳解設計模式六大

編輯:C#入門知識

詳解設計模式六大原則,詳解設計模式六大


設計模式(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,也叫迪米特法則)

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

vC# 開閉原則

1.概念: 

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

2.模擬場景: 

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

3.Solution: 

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

4.注意事項: 

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

5.開閉原則的優點: 

  • 可復用性
  • 可維護性

6.開閉原則圖解: 

C# 開閉原則 vC# 裡氏代換原則

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

2.概念: 

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

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

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

4.C# 裡氏代換原則優點: 

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

5.C# 裡氏代換原則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.裡氏代換原則圖解: 

C# 裡氏代換原則 vC# 依賴倒轉原則

1.概念: 

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

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

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

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類就可以了。

vC# 接口隔離原則

1.概念: 

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

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的外部和內部都定義一個接口的話,那這個接口會很大,而且實現接口的類也會變得臃腫。

C# 接口隔離原則 vC# 合成/聚合復用原則

1.概念: 

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

2.合成/聚合解析: 

  • 聚合概念: 

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

    聚合關系UML類圖: 

    C# 合成/聚合復用原則

    代碼演示: 

    //------------------------------------------------------------------------------
    // <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類圖: 

    C# 合成/聚合復用原則

    代碼演示: 

    //------------------------------------------------------------------------------
    // <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();
            }
        }
    }

3.模擬場景: 

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

C# 合成/聚合復用原則

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

C# 合成/聚合復用原則 vC# 迪米特法則

1.概念: 

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

2.模擬場景: 

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

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

UML類圖:

C# 迪米特法則

代碼演示:

//------------------------------------------------------------------------------
// <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的例子)

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)
        {
            // 通知財務部門開始統計已結算公司的員工
            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/
關於作者:專注於微軟平台的項目開發。如有問題或建議,請多多賜教!
版權聲明:本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接。
特此聲明:所有評論和私信都會在第一時間回復。也歡迎園子的大大們指正錯誤,共同進步。或者直接私信我
聲援博主:如果您覺得文章對您有幫助,可以點擊文章右下角【推薦】一下。您的鼓勵是作者堅持原創和持續寫作的最大動力!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved