1. 程式人生 > >C#軟體設計——小話設計模式原則之:開閉原則OCP

C#軟體設計——小話設計模式原則之:開閉原則OCP

前言:這篇繼續來看看開閉原則。廢話少說,直接入正題。

軟體設計原則系列文章索引

一、原理介紹

1、官方定義

開閉原則,英文縮寫OCP,全稱Open Closed Principle。

原始定義:Software entities (classes, modules, functions) should be open for extension but closed for modification。

字面翻譯:軟體實體(包括類、模組、功能等)應該對擴充套件開放,但是對修改關閉。

2、自己理解

2.1、原理解釋

  • 對擴充套件開放。模組對擴充套件開放,就意味著需求變化時,可以對模組擴充套件,使其具有滿足那些改變的新行為。換句話說,模組通過擴充套件的方式去應對需求的變化。
  • 對修改關閉。模組對修改關閉,表示當需求變化時,關閉對模組原始碼的修改,當然這裡的“關閉”應該是儘可能不修改的意思,也就是說,應該儘量在不修改原始碼的基礎上面擴充套件元件。

2.2、為什麼要“開”和“閉”

一般情況,我們接到需求變更的通知,通常方式可能就是修改模組的原始碼,然而修改已經存在的原始碼是存在很大風險的,尤其是專案上線執行一段時間後,開發人員發生變化,這種風險可能就更大。所以,為了避免這種風險,在面對需求變更時,我們一般不修改原始碼,即所謂的對修改關閉。不允許修改原始碼,我們如何應對需求變更呢?答案就是我們下面要說的對擴充套件開放。

通過擴充套件去應對需求變化,就要求我們必須要面向介面程式設計,或者說面向抽象程式設計。所有引數型別、引用傳遞的物件必須使用抽象(介面或者抽象類)的方式定義,不能使用實現類的方式定義;通過抽象去界定擴充套件,比如我們定義了一個介面A的引數,那麼我們的擴充套件只能是介面A的實現類。總的來說,開閉原則提高系統的可維護性和程式碼的重用性。

二、場景示例

1、對實現類程式設計,你死得很慘

下面就結合之前博主在園子裡面看到的一個使用場景來一步步呈現使用實現類程式設計的弊端。

場景說明:馬上中秋節了, **公司希望研發部門研發一套工具,實現給公司所有員工傳送祝福郵件。

接到開發需求,研發部立刻開會成立研發小組,進入緊張的開發階段,經過1個月的艱苦奮戰,系統順利上線。程式碼實現如下:

1.1 EmailMessage工具類

namespace Utility
{
    //傳送郵件的類
    public class EmailMessage
    {
        //裡面是大量的SMTP傳送郵件的邏輯

        
//傳送郵件的方法 public void SendMessage(string strMsg) { Console.WriteLine("Email節日問候:" + strMsg); } } }

1.2 MessageService服務

namespace Service
{
    public class MessageService
    {
        private EmailMessage emailHelper = null;
        public MessageService()
        {
            emailHelper = new EmailMessage();
        }

        //節日問候
        public void Greeting(string strMsg)
        {
            emailHelper.SendMessage(strMsg);
        }
    }
}

1.3 業務呼叫模組

    class Program
    {
        static void Main(string[] args)
        {
            Service.MessageService oService = new Service.MessageService();
            oService.Greeting("祝大家中秋節快樂。");

            Console.ReadKey();
        }
    }

一切都很順利,系統也得到公司好評。

日復一日,年復一年,隨著時間的推移,公司發現郵件推送的方式也存在一些弊病,比如某些網路不發達地區不能正常地收到郵件,並且在外出差人員有時不能正常收到郵件。這個時候公司領導發現簡訊推送是較好的解決辦法。於是乎,需求變更來了:增加簡訊推送節日祝福的功能,對於行政部等特殊部門保留郵件傳送的方式。

研發部的同事們雖然已有微言,但是沒辦法,也只有咬著牙忙了,於是程式碼變成了這樣。

1.1 工具類裡面增加了傳送簡訊的幫助類

namespace Utility
{
    //傳送郵件的類
    public class EmailMessage
    {
        //裡面是大量的SMTP傳送郵件的邏輯

        //傳送郵件的方法
        public void SendMessage(string strMsg)
        {
            Console.WriteLine("Email節日問候:" + strMsg);
        }
    }

    //傳送簡訊的類
    public class PhoneMessage
    { 
        //手機端傳送簡訊的業務邏輯

        //傳送簡訊的方法
        public void SendMessage(string strMsg)
        {
            Console.WriteLine("簡訊節日問候:" + strMsg);
        }
    }

}

1.2 MessageService服務裡面增加了一個列舉型別MessageType判斷是哪種推送方式

namespace Service
{
    public enum MessageType
    { 
        Email,
        Phone
    }

    public class MessageService
    {
        private EmailMessage emailHelper = null;
        private PhoneMessage phoneHelper = null;
        private MessageType m_oType;
        public MessageService(MessageType oType)
        {
            m_oType = oType;
            if (oType == MessageType.Email)
            {
                emailHelper = new EmailMessage();
            }
            else if (oType == MessageType.Phone)
            {
                phoneHelper = new PhoneMessage();
            }
        }

        //節日問候
        public void Greeting(string strMsg)
        {
            if (m_oType == MessageType.Email)
            {
                emailHelper.SendMessage(strMsg);
            }
            else if (m_oType == MessageType.Phone)
            {
                phoneHelper.SendMessage(strMsg);
            }
        }
    }
}

1.3 業務呼叫模組

    class Program
    {
        static void Main(string[] args)
        {
            Service.MessageService oEmaliService = new Service.MessageService(Service.MessageType.Email);
            oEmaliService.Greeting("祝大家中秋節快樂。");

            Service.MessageService oPhoneService = new Service.MessageService(Service.MessageType.Phone);
            oPhoneService.Greeting("祝大家中秋節快樂。");
            Console.ReadKey();
        }
    }

經過一段時間的加班、趕進度。終於大功告成。

隨著公司的不斷髮展,很多產品、平臺都融入了微信的功能,於是乎公司領導又希望在保證原有功能的基礎上增加微信的推送方式。這個時候研發部的同事們就怨聲載道了,這樣一年改一次,何時是個頭?並且隨著時間的推移,研發部員工可能發生過多次變換,現在維護這個系統的員工早已不是當初的開發者,在別人的程式碼上面改功能,做過開發的應該都知道,簡直苦不堪言,因為你不知道別人哪裡會給你埋一個“坑”。並且在現有程式碼上面改,也存在很大的風險,即使做好之後所有的功能都必須重新經過嚴格的測試。

事情發展到這裡,就可以看出使用實現類去程式設計,你會因為需求變更而死得很慘,這個時候我們就能看出遵守開閉原則的重要性了,如果這個系統設計之初就能考慮這個原則,所有的可變變數使用抽象去定義,可能效果截然不同。

2、對抽象程式設計,就是這麼靈活

如果專案設計之初我們定義一個ISendable介面,我們看看效果怎樣呢?

2.1 工具類

namespace IHelper
{
    public interface ISendable
    {
        void SendMessage(string strMsg);
    }
}
namespace Utility
{//傳送郵件的類
    public class EmailMessage:ISendable
    {
        //裡面是大量的SMTP傳送郵件的邏輯

        //傳送郵件的方法
        public void SendMessage(string strMsg)
        {
            Console.WriteLine("Email節日問候:" + strMsg);
        }
    }

    //傳送簡訊的類
    public class PhoneMessage:ISendable
    { 
        //手機端傳送簡訊的業務邏輯

        //傳送簡訊的方法
        public void SendMessage(string strMsg)
        {
            Console.WriteLine("簡訊節日問候:" + strMsg);
        }
    }

    //傳送微信的類
    public class WeChatMessage:ISendable
    {
        //微信訊息推送業務邏輯

        //傳送微信訊息的方法
        public void SendMessage(string strMsg)
        {
            Console.WriteLine("簡訊節日問候:" + strMsg);
        }
    }
}

2.2 MessageService服務

namespace Service
{
    public class MessageService
    {
        private ISendable m_oSendHelper = null;
        public MessageService(ISendable oSendHelper)
        {
            m_oSendHelper = oSendHelper;
        }

        //節日問候
        public void Greeting(string strMsg)
        {
            m_oSendHelper.SendMessage(strMsg);
            
        }
    }
}

2.3 業務呼叫模組

    class Program
    {
        static void Main(string[] args)
        {
            var strMsg = "祝大家中秋節快樂。";
            ISendable oEmailHelper = new EmailMessage();
            Service.MessageService oEmaliService = new Service.MessageService(oEmailHelper);
            oEmaliService.Greeting(strMsg);

            ISendable oPhoneHelper = new PhoneMessage();
            Service.MessageService oPhoneService = new Service.MessageService(oPhoneHelper);
            oPhoneService.Greeting(strMsg);

            ISendable oWeChatHelper = new WeChatMessage();
            Service.MessageService oWeChatService = new Service.MessageService(oWeChatHelper);
            oWeChatService.Greeting(strMsg);
            Console.ReadKey();
        }
    }

設計分析:在MessageService服務類中,我們定義了ISendable的介面變數m_oSendHelper,通過這個介面變數,我們就能很方便的通過擴充套件去應對需求的變化,而不必修改原來的程式碼。比如,我們現在再增加一種新的推送方式,對於我們的MessageService服務類來說,不用做任何修改,只需要擴充套件新的推送訊息的工具類即可。從需要抽象的角度來說,開閉原則和依賴倒置原則也有一定的相似性,不過博主覺得,開閉原則更加偏向的是使用抽象來避免修改原始碼,主張通過擴充套件去應對需求變更,而依賴倒置更加偏向的是層和層之間的解耦。當然,我們也不必分得那麼細,往往,一個好的設計肯定是遵循了多個設計原則的。

上面的設計,很好的解決了MessageService服務類中的問題,但是對於呼叫方(比如上文中的Main函式裡面),很顯然是違背了依賴倒置原則的,因為它既依賴介面層ISendable,又依賴介面實現層EmailMessage、PhoneMessage等。這肯定是不合適的。我們引入MEF,稍作修改。

namespace Utility
{
    //傳送郵件的類
    [Export("Email", typeof(ISendable))]
    public class EmailMessage:ISendable
    {
        //裡面是大量的SMTP傳送郵件的邏輯

        //傳送郵件的方法
        public void SendMessage(string strMsg)
        {
            Console.WriteLine("Email節日問候:" + strMsg);
        }
    }

    //傳送簡訊的類
    [Export("Phone", typeof(ISendable))]
    public class PhoneMessage:ISendable
    { 
        //手機端傳送簡訊的業務邏輯

        //傳送簡訊的方法
        public void SendMessage(string strMsg)
        {
            Console.WriteLine("簡訊節日問候:" + strMsg);
        }
    }

    //傳送微信的類
    [Export("WeChat", typeof(ISendable))]
    public class WeChatMessage:ISendable
    {
        //微信訊息推送業務邏輯

        //傳送微信訊息的方法
        public void SendMessage(string strMsg)
        {
            Console.WriteLine("簡訊節日問候:" + strMsg);
        }
    }
}

Main函式裡面

    class Program
    {
        [Import("Email",typeof(ISendable))]
        public ISendable oEmailHelper { get; set; }

        [Import("Phone", typeof(ISendable))]
        public ISendable oPhoneHelper { get; set; }

        [Import("WeChat", typeof(ISendable))]
        public ISendable oWeChatHelper { get; set; }

        static void Main(string[] args)
        {
            //使用MEF裝配元件
            var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            var container = new CompositionContainer(catalog);
            var oProgram = new Program();
            container.ComposeParts(oProgram);

            var strMsg = "祝大家中秋節快樂。";
            Service.MessageService oEmaliService = new Service.MessageService(oProgram.oEmailHelper);
            oEmaliService.Greeting(strMsg);

            Service.MessageService oPhoneService = new Service.MessageService(oProgram.oPhoneHelper);
            oPhoneService.Greeting(strMsg);

            Service.MessageService oWeChatService = new Service.MessageService(oProgram.oWeChatHelper);
            oWeChatService.Greeting(strMsg);
            Console.ReadKey();
        }
    }

如果你使用Unity,直接用配置檔案注入的方式更加簡單。

三、總結

至此開閉原則的示例就基本完了。文中觀點有不對的地方,歡迎指出,博主在此多謝了。如果園友們覺得本文對你有幫助,請幫忙推薦,博主將繼續努力~~

相關推薦

C#軟體設計——設計模式原則原則OCP

前言:這篇繼續來看看開閉原則。廢話少說,直接入正題。 軟體設計原則系列文章索引 一、原理介紹 1、官方定義 開閉原則,英文縮寫OCP,全稱Open Closed Principle。 原始定義:Software entities (classes, modules, functions) sho

[轉]設計模式六大原則[6]原則

說了 一點 模塊 活性 問題 單一職責原則 就是 認識 適應 定義:一個軟件實體如類、模塊和函數應該對擴展開放,對修改關閉。 問題由來:在軟件的生命周期內,因為變化、升級和維護等原因需要對軟件原有代碼進行修改時,可能會給舊代碼中引入錯誤,也可能會使我們不得不對整個功能進行

軟體設計原則原則

    開閉原則是所有設計原則的基礎,它非常重要。用一句話來形容開閉原則就是:軟體系統的實現應該對擴充套件開放,對修改關閉。開閉原則的中心思想就是“我們用抽象構建框架,用實現擴充套件細節“。   為了更好的解釋這個抽象的概念,我們一起來看一個例子,其實是很簡單的一個

面向對象設計原則原則OCP

name 返回 展開 打開 設計原則 data turn acl int 開閉原則(OCP)定義:對擴展開發,對修改關閉。好處: 適應性和靈活性。 穩定性和延續性。 可復用性與可維護性。 解釋說明:開閉原則指的是兩方面:對功能擴展開發,對修改進

七大原則之一原則OCP

背景 在產品的開發過程中,需求變更不足為奇。面對更變的需求基於開閉原則的思想是對程式的改動是通過增加程式碼新程式碼 來實現新的需求,而不是更改已經實現就需求的程式碼。因為舊的程式碼可能再別的需求有呼叫,越是底層的類的修改影響別的類就會越多。 定義 開閉原則(Th

C#軟體設計——設計模式原則單一職責原則SRP

前言:上篇C#軟體設計——小話設計模式原則之:依賴倒置原則DIP簡單介紹了下依賴倒置的由來以及使用,中間插了兩篇WebApi的文章,這篇還是迴歸正題,繼續來寫寫設計模式另一個重要的原則:單一職責原則。 軟體設計原則系列文章索引 一、原理介紹 1、官方定義 單一職責原則,英文縮寫SRP,全稱Sing

C#軟體設計——設計模式原則介面隔離原則ISP

前言:有朋友問我,設計模式原則這些東西在園子裡都討論爛了,一搜一大把的資料,還花這麼大力氣去整這個幹嘛。博主不得不承認,園子裡確實很多這方面的文章,並且不乏出色的博文。博主的想法是,既然要完善知識體系,就不能半途而廢。今天就來看看設計模式原則的另一個:介面隔離原則。 軟體設計原則系列文章索引 一、原理

C#軟體設計——設計模式原則依賴倒置原則DIP

前言:很久之前就想動筆總結下關於軟體設計的一些原則,或者說是設計模式的一些原則,奈何被各種bootstrap元件所吸引,一直抽不開身。群裡面有朋友問博主是否改行做前端了,呵呵,其實博主是想做“全戰”,即各方便都有戰鬥力。關於設計模式,作為程式猿的我們肯定都不陌生。博主的理解,所謂設計模式就是前人總結下來的一些

設計模式原則單一職責原則SRP(C#篇)

正文 前言:上篇C#軟體設計——小話設計模式原則之:依賴倒置原則DIP簡單介紹了下依賴倒置的由來以及使用,中間插了兩篇WebApi的文章,這篇還是迴歸正題,繼續來寫寫設計模式另一個重要的原則:單一職責原則。 軟體設計原則系列文章索引 回到頂部 一、原理介紹 回到頂部

【學習筆記】慕課網—Java設計模式精講 第3章 軟體設計七大原則-3-2 原則

/** * 軟體設計七大原則-開閉原則 * @author cnRicky * @date 2018.11.7 */ 開閉原則 定義:一個軟體實體如類、模組和函式應該對擴充套件開放,對修改關閉 強調的是用抽象構建框架,用實現擴充套件細節 優點:提高軟體系統的可複用性及可維護性 開閉原則

設計模式六大原則(6)原則

思考 外部 編程人員 恰恰 單一職責 何事 適應 擴展 分享 開閉原則 定義:一個軟件實體如類、模塊和函數應該對擴展開放,對修改關閉。 問題由來:在軟件的生命周期內,因為變化、升級和維護等原因需要對軟件原有代碼進行修改時,可能會給舊代碼中引入錯誤,也可能會使我們不得不對

設計模式六大設計原則(六)原則

開發十年,就只剩下這套架構體系了! >>>   

設計模式軟體開發原則(1)原則和依賴倒置原則

開閉原則 定義 所謂開閉原則就是一個軟體實體如類、模組和函式應該對擴充套件開放、對修改關閉。 強呼叫抽象構建框架,實現實現拓展細節。 有優點是提高軟體的複用性和易維護展性。是面向物件的最基本原則。 依賴倒置原則 定義 高層模組不應該依賴底層模組,二者都應該依賴其抽象。 抽象不應該依賴細節:細節應該

設計模式六大原則裏氏替換原則

number -h ole 擁有 method about rect sse 程序 1 裏氏替換原則定義 Liskov於1987年提出了一個關於繼承的原則“Inheritance should ensure that any property proved about su

11設計模式六大原則——原則

職責 art 並不是 錯誤 接口 屬於 倒置 編程 探討 定義:一個軟件實體如類、模塊和函數應該對擴展開放,對修改關閉。 問題由來:在軟件的生命周期內,因為變化、升級和維護等原因需要對軟件原有代碼進行修改時,可能會給舊代碼中引入錯誤,也可能會使我們不得不對整個功能進行重構,

設計模式學習筆記(二) 設計基本原則【單一職責原則

code 分享 開發者 實際應用 需要 ret ext file類 tor 單一職責原則(SRP: Single Responsibility Principle) 名詞解釋: 1) 職責:是指類變化的原因。 2) 職責擴散:就是因為某種原因,職責P被分化為粒度更細的職責P

設計模式六大原則(一)單一職責原則

控制 line 避免 多人 由來 pan 兩個類 思想 功能 單一職責定義: 不要存在多於一個導致類變更的原因,通俗的說,即一個類只負責一項職責。 問題由來: 類T負責兩個不同的職責:職責P1,職責P2。當由於職責P1需求發生改變而需要修改類T時,有可能會導致原

【大話設計模式讀書筆記——原則

等等 使用方式 價值 log 變更 重用 中間 多種實現 過程 開閉原則 在面向對象編程領域中,開閉原則規定“軟件中的對象(類,模塊,函數等等)應該對於擴展是開放的,但是對於修改是封閉的”[1],這意味著一個實體是允許在不改變它的源代碼的前提下變更它的行為。該特性在產品化

設計模式(八)面向對象設計原則合成復用原則

擴展 會有 作者 如果 composite info 繼承 裏氏代換原則 oracle數據 引用自:http://blog.csdn.net/lovelion 作者:劉偉 合成復用原則(Composite Reuse Principle, CRP):盡量使用對象組合,

學習設計模式 - 六大基本原則原則

裏氏替換 成對 五個 jsp 導致 業務邏輯 關聯 影響 引入   設計模式總共有六大基本原則,統稱為SOLID (穩定)原則,分別是S-單一職責原則(Single Responsibility Principle), O-開閉原則(Open closed Principl