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、总结

至此开闭原则的示例就基本完了。文中观点有不对的地方,欢迎指出,博主在此多谢了。若是园友们以为本文对你有帮助,请帮忙推荐,博主将继续努力~~