SOLID五大原则(二)开放关闭原则OCP

应对复杂业务利器之开闭原则,掌握并遵守该原则是应对复杂业务的强有效手段

先吹牛逼
开闭原则,英文缩写OCP,全称Open Closed Principle。
原始定义:Software entities (classes, modules, functions) should be open for extension but closed for modification。
字面翻译:软件实体(包括类、模块、功能等)应该对扩展开放,但是对修改关闭。
其实,对修改关闭,我的理解是:尽可能少的修改原有的代码。

开闭原则的意义

实践出真知,我们模拟一个业务场景:
公司决定,在员工的生日当天,以短信的形式给寿星发送“996福报快乐!”祝福语。

下面以两种方式编写代码实现业务功能


1.不遵循开闭原则,简单粗暴法
首先建了一个发送短信工具类,其中有一个发送短信方法SendMessage(string msg)

    public class MessageUtil
    {
        public static void SendMessage(string msg)
        {
            Console.WriteLine(msg);
        }
    }

其中,我们的应用层调用

    public class MessageService
    {
        public void Greeting(string msg)
        {
            MessageUtil.SendMessage(msg);
        }
    }

其中,业务调用处

        MessageService _service = new MessageService();
        /// <summary>
        /// 给大家过节问候
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click(object sender, EventArgs e)
        {
            //节日到了,以短信形式发送祝福
            _service.Greeting("祝大家996福报快乐");
        }

到目前看来还是挺好的,后续业务增加,节日祝福要增加邮件发送,好吧,说干就干
新增一个邮件工具类,这个没啥可多说的

    public class EmailUtil
    {
        public static void SendEmail(string address, string contont)
        {
            //dosth
        }
    }

看原来的MessageService被修改为

    public class MessageService
    {
        MessageType _type;
        public MessageService(MessageType type)
        {
            _type = type;
        }

        public enum MessageType
        {
            Email,
            Phone
        }

        public void Greeting(string msg)
        {
            switch (_type)
            {
                case MessageType.Email:
                    EmailUtil.SendEmail("alibaba@996.com", msg);
                    break;
                case MessageType.Phone:
                    MessageUtil.SendMessage(msg);
                    break;
            }
        }
    }

增加了消息类型的枚举,然后Greeting方法,新增了判断,这个时候原有的Greeting方法已经被修改了,哪怕是本来短信发送模块的代码并没有出任何问题,可此时,还是修改了该方法;维护过别人代码的人都知道,一个项目尤其是经过很多人手的项目,去修改一段原有的代码,风险是非常大的,你永远不知道前任会在哪个地方给你留下什么坑。

业务调用处修改为

        MessageService _servicePhone = new MessageService(MessageType.Phone);
        MessageService _serviceEmail = new MessageService(MessageType.Email);

        /// <summary>
        /// 给大家过节问候
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click(object sender, EventArgs e)
        {
            //节日到了,以短信形式发送祝福
            _servicePhone.Greeting("祝大家996福报快乐");
            _serviceEmail.Greeting("祝大家996福报快乐");
        }

分析:仅仅是增加了一项额外的节日问候的方式,我就新增了枚举类型、直接修改了原短信问候方法Greeting(),以后的实际需求往往是,还会新增邮件问候、微信问候、微博问候。。。需求是无止境的,每一次新增问候,如果按照这种方式,那势必每次新增需求都要>修改Greeting()方法,风险过大,虽然是新增的需求,按理说只应该测试新增的模块,但是由于你修改了原Greeting()方法,测试小伙伴需要回归测试节日问候这一整个大的模块,想想就很头疼

以上的编码方式,我可能有故意弄的很麻烦,可能实际代码中并不常见,这里只是为了与下面要讲的第二种方式形成鲜明的对比,从而说明开闭原则到底能为我们带来怎样的好处。

下面看看另一种设计

2、在最初的需求开始时,就考虑后续需求的变化情况,未雨绸缪。开闭原则

  • 为发送服务,建立一个通用接口服务ISendService,所有发送服务都必须实现该接口
    /// <summary>
    /// 发送消息接口
    /// 所有类型的发送服务都必须实现该接口
    /// </summary>
    public interface ISendService
    {
        public void SendMessage(string msg);
    }
  • 新增短信服务类,并实现ISendService
    /// <summary>
    /// 短信服务
    /// </summary>
    public class MessageService : ISendService
    {
        public void SendMessage(string msg)
        {
            MessageUtil.SendMessage(msg);
        }
    }
  • 在业务调用处调用发送问候
        ISendService _messageService = new MessageService();

        /// <summary>
        /// 节日问候
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click(object sender, EventArgs e)
        {
            _messageService.SendMessage("996是我的福报,谢谢!");
        }
  • 接下来看,在如果保证不对短信服务做任何修改的情况下,新增邮件发送问候需求!
    新增邮件服务类
    /// <summary>
    /// 邮件服务
    /// </summary>
    public class EmailService : ISendService
    {
        public void SendMessage(string msg)
        {
            EmailUtil.SendEmail("alibaba@996.com", msg);
        }
    }
  • 业务调用处,修改:
    新增一个邮件服务实例,完成对业务的扩展
        ISendService _messageService = new MessageService();
        ISendService _emailService = new EmailService();

        /// <summary>
        /// 节日问候
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click(object sender, EventArgs e)
        {
            _messageService.SendMessage("996是我的福报,谢谢!");
            _emailService.SendMessage("996是我的福报,谢谢!");
        }

分析

新增邮件问候需求,是通过额外新增一个实现类EmailService来实现邮件发送,而不是在原有的MessageService上做新增或修改,这就是开闭原则,对原指的是MessageService,封闭未作修改,对扩展指的是ISendService,开放接口;以后无论是新增多少个发送服务,每次都是一样的流程,新增具体服务类,实现扩展接口ISendService

以上的伪代码只是为了体现OCP原则,其实这里违背了五大原则中另外一个依赖倒置原则,因为这里我既依赖了ISendService又依赖了MessageService和EmailService,在介绍依赖倒置的时候,我会引入MEF来摆脱对Service的依赖

posted @ 2021-11-05 09:57  大苹果coding  阅读(50)  评论(0)    收藏  举报