前言:这篇继续来看看开闭原则。废话少说,直接入正题。

软件设计原则系列文章索引

一、原理介绍

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工具类

  1. namespace Utility
  2. {
  3. //发送邮件的类
  4. public class EmailMessage
  5. {
  6. //里面是大量的SMTP发送邮件的逻辑
  7.  
  8. //发送邮件的方法
  9. public void SendMessage(string strMsg)
  10. {
  11. Console.WriteLine("Email节日问候:" + strMsg);
  12. }
  13. }
  14. }

1.2 MessageService服务

  1. namespace Service
  2. {
  3. public class MessageService
  4. {
  5. private EmailMessage emailHelper = null;
  6. public MessageService()
  7. {
  8. emailHelper = new EmailMessage();
  9. }
  10.  
  11. //节日问候
  12. public void Greeting(string strMsg)
  13. {
  14. emailHelper.SendMessage(strMsg);
  15. }
  16. }
  17. }

1.3 业务调用模块

  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. Service.MessageService oService = new Service.MessageService();
  6. oService.Greeting("祝大家中秋节快乐。");
  7.  
  8. Console.ReadKey();
  9. }
  10. }

一切都很顺利,系统也得到公司好评。

日复一日,年复一年,随着时间的推移,公司发现邮件推送的方式也存在一些弊病,比如某些网络不发达地区不能正常地收到邮件,并且在外出差人员有时不能正常收到邮件。这个时候公司领导发现短信推送是较好的解决办法。于是乎,需求变更来了:增加短信推送节日祝福的功能,对于行政部等特殊部门保留邮件发送的方式。

研发部的同事们虽然已有微言,但是没办法,也只有咬着牙忙了,于是代码变成了这样。

1.1 工具类里面增加了发送短信的帮助类

  1. namespace Utility
  2. {
  3. //发送邮件的类
  4. public class EmailMessage
  5. {
  6. //里面是大量的SMTP发送邮件的逻辑
  7.  
  8. //发送邮件的方法
  9. public void SendMessage(string strMsg)
  10. {
  11. Console.WriteLine("Email节日问候:" + strMsg);
  12. }
  13. }
  14.  
  15. //发送短信的类
  16. public class PhoneMessage
  17. {
  18. //手机端发送短信的业务逻辑
  19.  
  20. //发送短信的方法
  21. public void SendMessage(string strMsg)
  22. {
  23. Console.WriteLine("短信节日问候:" + strMsg);
  24. }
  25. }
  26.  
  27. }

1.2 MessageService服务里面增加了一个枚举类型MessageType判断是哪种推送方式

  1. namespace Service
  2. {
  3. public enum MessageType
  4. {
  5. Email,
  6. Phone
  7. }
  8.  
  9. public class MessageService
  10. {
  11. private EmailMessage emailHelper = null;
  12. private PhoneMessage phoneHelper = null;
  13. private MessageType m_oType;
  14. public MessageService(MessageType oType)
  15. {
  16. m_oType = oType;
  17. if (oType == MessageType.Email)
  18. {
  19. emailHelper = new EmailMessage();
  20. }
  21. else if (oType == MessageType.Phone)
  22. {
  23. phoneHelper = new PhoneMessage();
  24. }
  25. }
  26.  
  27. //节日问候
  28. public void Greeting(string strMsg)
  29. {
  30. if (m_oType == MessageType.Email)
  31. {
  32. emailHelper.SendMessage(strMsg);
  33. }
  34. else if (m_oType == MessageType.Phone)
  35. {
  36. phoneHelper.SendMessage(strMsg);
  37. }
  38. }
  39. }
  40. }

1.3 业务调用模块

  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. Service.MessageService oEmaliService = new Service.MessageService(Service.MessageType.Email);
  6. oEmaliService.Greeting("祝大家中秋节快乐。");
  7.  
  8. Service.MessageService oPhoneService = new Service.MessageService(Service.MessageType.Phone);
  9. oPhoneService.Greeting("祝大家中秋节快乐。");
  10. Console.ReadKey();
  11. }
  12. }

经过一段时间的加班、赶进度。终于大功告成。

随着公司的不断发展,很多产品、平台都融入了微信的功能,于是乎公司领导又希望在保证原有功能的基础上增加微信的推送方式。这个时候研发部的同事们就怨声载道了,这样一年改一次,何时是个头?并且随着时间的推移,研发部员工可能发生过多次变换,现在维护这个系统的员工早已不是当初的开发者,在别人的代码上面改功能,做过开发的应该都知道,简直苦不堪言,因为你不知道别人哪里会给你埋一个“坑”。并且在现有代码上面改,也存在很大的风险,即使做好之后所有的功能都必须重新经过严格的测试。

事情发展到这里,就可以看出使用实现类去编程,你会因为需求变更而死得很惨,这个时候我们就能看出遵守开闭原则的重要性了,如果这个系统设计之初就能考虑这个原则,所有的可变变量使用抽象去定义,可能效果截然不同。

2、对抽象编程,就是这么灵活

如果项目设计之初我们定义一个ISendable接口,我们看看效果怎样呢?

2.1 工具类

  1. namespace IHelper
  2. {
  3. public interface ISendable
  4. {
  5. void SendMessage(string strMsg);
  6. }
  7. }
  1. namespace Utility
  2. {//发送邮件的类
  3. public class EmailMessage:ISendable
  4. {
  5. //里面是大量的SMTP发送邮件的逻辑
  6.  
  7. //发送邮件的方法
  8. public void SendMessage(string strMsg)
  9. {
  10. Console.WriteLine("Email节日问候:" + strMsg);
  11. }
  12. }
  13.  
  14. //发送短信的类
  15. public class PhoneMessage:ISendable
  16. {
  17. //手机端发送短信的业务逻辑
  18.  
  19. //发送短信的方法
  20. public void SendMessage(string strMsg)
  21. {
  22. Console.WriteLine("短信节日问候:" + strMsg);
  23. }
  24. }
  25.  
  26. //发送微信的类
  27. public class WeChatMessage:ISendable
  28. {
  29. //微信消息推送业务逻辑
  30.  
  31. //发送微信消息的方法
  32. public void SendMessage(string strMsg)
  33. {
  34. Console.WriteLine("短信节日问候:" + strMsg);
  35. }
  36. }
  37. }

2.2 MessageService服务

  1. namespace Service
  2. {
  3. public class MessageService
  4. {
  5. private ISendable m_oSendHelper = null;
  6. public MessageService(ISendable oSendHelper)
  7. {
  8. m_oSendHelper = oSendHelper;
  9. }
  10.  
  11. //节日问候
  12. public void Greeting(string strMsg)
  13. {
  14. m_oSendHelper.SendMessage(strMsg);
  15.  
  16. }
  17. }
  18. }

2.3 业务调用模块

  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. var strMsg = "祝大家中秋节快乐。";
  6. ISendable oEmailHelper = new EmailMessage();
  7. Service.MessageService oEmaliService = new Service.MessageService(oEmailHelper);
  8. oEmaliService.Greeting(strMsg);
  9.  
  10. ISendable oPhoneHelper = new PhoneMessage();
  11. Service.MessageService oPhoneService = new Service.MessageService(oPhoneHelper);
  12. oPhoneService.Greeting(strMsg);
  13.  
  14. ISendable oWeChatHelper = new WeChatMessage();
  15. Service.MessageService oWeChatService = new Service.MessageService(oWeChatHelper);
  16. oWeChatService.Greeting(strMsg);
  17. Console.ReadKey();
  18. }
  19. }

设计分析:在MessageService服务类中,我们定义了ISendable的接口变量m_oSendHelper,通过这个接口变量,我们就能很方便的通过扩展去应对需求的变化,而不必修改原来的代码。比如,我们现在再增加一种新的推送方式,对于我们的MessageService服务类来说,不用做任何修改,只需要扩展新的推送消息的工具类即可。从需要抽象的角度来说,开闭原则和依赖倒置原则也有一定的相似性,不过博主觉得,开闭原则更加偏向的是使用抽象来避免修改源代码,主张通过扩展去应对需求变更,而依赖倒置更加偏向的是层和层之间的解耦。当然,我们也不必分得那么细,往往,一个好的设计肯定是遵循了多个设计原则的。

上面的设计,很好的解决了MessageService服务类中的问题,但是对于调用方(比如上文中的Main函数里面),很显然是违背了依赖倒置原则的,因为它既依赖接口层ISendable,又依赖接口实现层EmailMessage、PhoneMessage等。这肯定是不合适的。我们引入MEF,稍作修改。

  1. namespace Utility
  2. {
  3. //发送邮件的类
  4. [Export("Email", typeof(ISendable))]
  5. public class EmailMessage:ISendable
  6. {
  7. //里面是大量的SMTP发送邮件的逻辑
  8.  
  9. //发送邮件的方法
  10. public void SendMessage(string strMsg)
  11. {
  12. Console.WriteLine("Email节日问候:" + strMsg);
  13. }
  14. }
  15.  
  16. //发送短信的类
  17. [Export("Phone", typeof(ISendable))]
  18. public class PhoneMessage:ISendable
  19. {
  20. //手机端发送短信的业务逻辑
  21.  
  22. //发送短信的方法
  23. public void SendMessage(string strMsg)
  24. {
  25. Console.WriteLine("短信节日问候:" + strMsg);
  26. }
  27. }
  28.  
  29. //发送微信的类
  30. [Export("WeChat", typeof(ISendable))]
  31. public class WeChatMessage:ISendable
  32. {
  33. //微信消息推送业务逻辑
  34.  
  35. //发送微信消息的方法
  36. public void SendMessage(string strMsg)
  37. {
  38. Console.WriteLine("短信节日问候:" + strMsg);
  39. }
  40. }
  41. }

Main函数里面

  1. class Program
  2. {
  3. [Import("Email",typeof(ISendable))]
  4. public ISendable oEmailHelper { get; set; }
  5.  
  6. [Import("Phone", typeof(ISendable))]
  7. public ISendable oPhoneHelper { get; set; }
  8.  
  9. [Import("WeChat", typeof(ISendable))]
  10. public ISendable oWeChatHelper { get; set; }
  11.  
  12. static void Main(string[] args)
  13. {
  14. //使用MEF装配组件
  15. var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
  16. var container = new CompositionContainer(catalog);
  17. var oProgram = new Program();
  18. container.ComposeParts(oProgram);
  19.  
  20. var strMsg = "祝大家中秋节快乐。";
  21. Service.MessageService oEmaliService = new Service.MessageService(oProgram.oEmailHelper);
  22. oEmaliService.Greeting(strMsg);
  23.  
  24. Service.MessageService oPhoneService = new Service.MessageService(oProgram.oPhoneHelper);
  25. oPhoneService.Greeting(strMsg);
  26.  
  27. Service.MessageService oWeChatService = new Service.MessageService(oProgram.oWeChatHelper);
  28. oWeChatService.Greeting(strMsg);
  29. Console.ReadKey();
  30. }
  31. }

如果你使用Unity,直接用配置文件注入的方式更加简单。

三、总结

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

C#软件设计——小话设计模式原则之:开闭原则OCP的更多相关文章

  1. C#软件设计——小话设计模式原则之:依赖倒置原则DIP

    前言:很久之前就想动笔总结下关于软件设计的一些原则,或者说是设计模式的一些原则,奈何被各种bootstrap组件所吸引,一直抽不开身.群里面有朋友问博主是否改行做前端了,呵呵,其实博主是想做“全战”, ...

  2. C#软件设计——小话设计模式原则之:单一职责原则SRP

    前言:上篇C#软件设计——小话设计模式原则之:依赖倒置原则DIP简单介绍了下依赖倒置的由来以及使用,中间插了两篇WebApi的文章,这篇还是回归正题,继续来写写设计模式另一个重要的原则:单一职责原则. ...

  3. C#软件设计——小话设计模式原则之:接口隔离原则ISP

    前言:有朋友问我,设计模式原则这些东西在园子里都讨论烂了,一搜一大把的资料,还花这么大力气去整这个干嘛.博主不得不承认,园子里确实很多这方面的文章,并且不乏出色的博文.博主的想法是,既然要完善知识体系 ...

  4. Java设计模式(1:软件架构设计七大原则及开闭原则详解)

    前言 在日常工作中,我们使用Java语言进行业务开发的时候,或多或少的都会涉及到设计模式,而运用好设计模式对于我而言,又是一个比较大的难题.为了解决.克服这个难题,笔主特别开了这个博客来记录自己学习的 ...

  5. 设计原则:开-闭原则(Open-Closed Principle, OCP)

    开-闭原则就是软件实体应当对扩展开放,对修改关闭.(Software entities should be open for extension,but closed for modification ...

  6. 设计原则:开闭原则(OCP)

    1.什么是开闭原则 开闭原则的英文是Open Closed Principle,缩写就是OCP.其定义如下: 软件实体(模块.类.方法等)应该"对扩展开放.对修改关闭". 从定义上 ...

  7. 7.10 其他面向对象设计原则1: 开-闭原则OCP

    其他面向对象设计原则1: 开-闭原则OCP  Open-Closed Principle (OCP)5.1 设计变坏的前兆 Signs of Rotting Design  僵硬性 Rigidit ...

  8. js 的七大原则--单一原则、开闭原则、替换原则(一)

    一.前言: js 的七大设计原则: 1.单一原则 2.开闭原则 3.里氏替换原则 4.依赖倒转原则 5.接口隔离原则 6.合成复用原则 7.迪米尔法则 二.单一原则 1.定义:单一原则就是一个对象或者 ...

  9. 最简单直接地理解Java软件设计原则之开闭原则

    写在前面 本文属于Java软件设计原则系列文章的其中一篇,后续会继续分享其他的原则.想以最简单的方式,最直观的demo去彻底理解设计原则.文章属于个人整理.也欢迎大家提出不同的想法. 首先是一些理论性 ...

随机推荐

  1. NHibernate代码监视

    今天在使用NH连接MySQL的时候,突然想起来MySQL好像并没有类似于SQL SERVER Profiler的功能,那以后调试不是很操蛋吗?搞了半天,发现并没有办法,只好拐个弯解决问题:将NH中的生 ...

  2. 网络分析之networkx(转载)

    图的类型 Graph类是无向图的基类,无向图能有自己的属性或参数,不包含重边,允许有回路,节点可以是任何hash的python对象,节点和边可以保存key/value属性对.该类的构造函数为Graph ...

  3. SharePoint 2013 使用JavaScript对象模型配置智能提示

    前言 默认在VS2012/2013中编写SharePoint JavaScript 客户端对象模型,都没有智能感知的功能,用起来非常麻烦:其实,我们可以手动配置一下,让JavaScript可以进行智能 ...

  4. Android Material Design 兼容库的使用

    Android Material Design 兼容库的使用 mecury 前言:近来学习了Android Material Design 兼容库,为了把这个弄懂,才有了这篇博客,这里先推荐两篇博客: ...

  5. (三)Maven仓库介绍与本地仓库配置

    1.Maven本地仓库/远程仓库的基本介绍 示意图: 本地仓库是指存在于我们本机的仓库,在我们加入依赖时候,首先会跑到我们的本地仓库去找,如果找不到则会跑到远程仓库中去找.对于依赖的包大家可以从这个地 ...

  6. Play Framework 完整实现一个APP(七)

    1.添加验证码 Application Controller添加captcha() public static void captcha() { Images.Captcha captcha = Im ...

  7. Yii2 使用a标签发送post请求

    <?= Html::a('text', 'url', [ 'data' => [ 'method' => 'post', 'params' => [ 'params_key' ...

  8. Windows Server 2012 虚拟化实战:存储(一)

    在计算机世界我们随处可以见的一种方法,那就是抽象.1946年冯诺依曼提出了计算机的基本结构包含:计算器,存储器和I/O设备.这可能是对计算机这一新生事物最重要的一次抽象,它直接影响了今后几十年计算机软 ...

  9. 动手实践虚拟网络 - 每天5分钟玩转 OpenStack(10)

    本节将演示如何在实验环境中实现下图所示的虚拟网络 配置 Linux Bridge br0 编辑 /etc/network/interfaces,配置 br0. 下面用 vimdiff 展示了对 /et ...

  10. 客户端连接RMS服务,报:服务暂时不可用,请确保已连接到此服务器…….

    原因在于客户端office没有安装rms服务模块,或安装的office有缺陷,请重新安装可用的office版本.