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

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

軟件設計原則系列文章索引編程

1、原理介紹

一、官方定義

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

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

字面翻譯:軟件實體(包括類、模塊、功能等)應該對擴展開放,可是對修改關閉。
網絡

二、本身理解

2.一、原理解釋

  • 對擴展開放。模塊對擴展開放,就意味着需求變化時,能夠對模塊擴展,使其具備知足那些改變的新行爲。換句話說,模塊經過擴展的方式去應對需求的變化。
  • 對修改關閉。模塊對修改關閉,表示當需求變化時,關閉對模塊源代碼的修改,固然這裏的「關閉」應該是儘量不修改的意思,也就是說,應該儘可能在不修改源代碼的基礎上面擴展組件。

2.二、爲何要「開」和「閉」

通常狀況,咱們接到需求變動的通知,一般方式可能就是修改模塊的源代碼,然而修改已經存在的源代碼是存在很大風險的,尤爲是項目上線運行一段時間後,開發人員發生變化,這種風險可能就更大。因此,爲了不這種風險,在面對需求變動時,咱們通常不修改源代碼,即所謂的對修改關閉。不容許修改源代碼,咱們如何應對需求變動呢?答案就是咱們下面要說的對擴展開放。函數

經過擴展去應對需求變化,就要求咱們必需要面向接口編程,或者說面向抽象編程。全部參數類型、引用傳遞的對象必須使用抽象(接口或者抽象類)的方式定義,不能使用實現類的方式定義;經過抽象去界定擴展,好比咱們定義了一個接口A的參數,那麼咱們的擴展只能是接口A的實現類。總的來講,開閉原則提升系統的可維護性和代碼的重用性。工具

2、場景示例

一、對實現類編程,你死得很慘

下面就結合以前博主在園子裏面看到的一個使用場景來一步步呈現使用實現類編程的弊端。post

場景說明:立刻中秋節了, **公司但願研發部門研發一套工具,實現給公司全部員工發送祝福郵件。測試

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

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

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

隨着公司的不斷髮展,不少產品、平臺都融入了微信的功能,因而乎公司領導又但願在保證原有功能的基礎上增長微信的推送方式。這個時候研發部的同事們就怨聲載道了,這樣一年改一次,什麼時候是個頭?而且隨着時間的推移,研發部員工可能發生過屢次變換,如今維護這個系統的員工早已不是當初的開發者,在別人的代碼上面改功能,作過開發的應該都知道,簡直苦不堪言,由於你不知作別人哪裏會給你埋一個「坑」。而且在現有代碼上面改,也存在很大的風險,即便作好以後全部的功能都必須從新通過嚴格的測試。

事情發展到這裏,就能夠看出使用實現類去編程,你會由於需求變動而死得很慘,這個時候咱們就能看出遵照開閉原則的重要性了,若是這個系統設計之初就能考慮這個原則,全部的可變變量使用抽象去定義,可能效果大相徑庭。

二、對抽象編程,就是這麼靈活

若是項目設計之初咱們定義一個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,直接用配置文件注入的方式更加簡單。

3、總結

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

相關文章
相關標籤/搜索