开闭原则是面向对象设计的一个重要原则,其定义如下:

开闭原则(Open-Closed Principle, OCP):一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。

在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。那么势必会对软件的开发带来额外的风险和成本, 这是OCP原则要规范设计的一个主要原因,所有的设计原则都是对软件的开发,设计以及维护带来好处的,OCP也不例外。

OCP原则是面形对象软件设计的基本原则,其旨在指导如何构建稳定的,灵活的,易于维护的软件。其实这个原则也是我们面向对象设计的一个终极要求,更是一个引导,在软件设计过程中要达到OCP原则其实你需要很好的遵守其他的设计原则,换句话说如果其它的设计原则都达到了那么OCP原则自然就符合了,也就是说OCP原则是其他原则综合使用的一个考量,一个检验。

假如我们要设计一个叫做动物的类(Animal)在这个类中我们有一个方法叫Sound, Sound 方法主要用于发出动物的叫声,通常我们的设计代码如下:

  1. public class Animal
  2. {
  3. public void Sound(string animal)
  4. {
  5. switch (animal)
  6. {
  7. case "dog":
  8. System.Console.WriteLine("woof woof woof...");
  9. break;
  10. case "cat":
  11. Console.WriteLine("miaow miaow miaow...");
  12. break;
  13. }
  14. }
  15. }

客户端的调用代码如下:

  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. Animal animal=new Animal();
  6. animal.Sound("dog");
  7. Console.ReadKey();
  8. }
  9. }

调用返回的结果:

这样看起来似乎很完美,如果想要什么动物发生客户端就传入该动物的名字然后调用Sound方法就可以了。 客户今天只养了两种动物,狗和猫,如果有一天他在养一头羊,他想听到羊的叫声怎么办呢? 直接的想法是在Sound的方法中加一个case子句,写上羊的叫声如下:

  1. public class Animal
  2. {
  3. public void Sound(string animal)
  4. {
  5. switch (animal)
  6. {
  7. case "dog":
  8. System.Console.WriteLine("woof woof woof...");
  9. break;
  10. case "cat":
  11. Console.WriteLine("miaow miaow miaow...");
  12. break;
  13. case "sheep":
  14. Console.WriteLine("mee-mee mee-mee mee-mee...");
  15. break;
  16. }
  17. }
  18. }

客户端调用如下:

  1. static void Main(string[] args)
  2. {
  3. Animal animal=new Animal();
  4. animal.Sound("sheep");
  5. Console.ReadKey();
  6. }

输出:

这看起来似乎是很完美,但是我们回过头想一下,好像哪里不对劲,如果后面客户需要加更多的动物怎么办呢?,是不是这个case要写很长很长,Sound方法要每次都要修改,每次都要全部编译整个工程还要重新部署所有的代码,这中间的风险很大,很容易出现操作上的失误,或者代码修改出现bug,要命的是每次都要把整个代码重新测试一遍,给升级带来了很多的工作量,以及潜在的风险。其实再回头看看,我们这个设计是违反OCP原则的, OCP告诉我们对“修改关闭,对扩展开放“,很显然我们这里修改了代码。同时也违背了SRP原则“一个类或方法只负责干一件事情“,显然Sound 犯法的职责太多。那么我们有没有办法来重构代码,让其遵守这些原则,每次修改该最少的代码即尽可能的减少工作量呢? 答案是肯定的。

我们抽取一个接口叫IAnimal:

  1. public interface IAnimal
  2. {
  3. void Sound();
  4. }

再分别定义三个类 Dog, Cate 和Sheep 并继承IAnimal 接口:

  1. public class Dog : IAnimal
  2. {
  3. public void Sound()
  4. {
  5. Console.WriteLine("woof woof woof...");
  6. }
  7. }
  8.  
  9. public class Cat : IAnimal
  10. {
  11. public void Sound()
  12. {
  13. Console.WriteLine("miaow miaow miaow...");
  14. }
  15. }
  16.  
  17. public class Sheep:IAnimal
  18. {
  19. public void Sound()
  20. {
  21. Console.WriteLine("mee-mee mee-mee mee-mee...");
  22. }
  23. }

客户端如果想听到狗的叫声的代码调用如下:

  1. static void Main(string[] args)
  2. {
  3. IAnimal animal=new Dog();
  4. animal.Sound();
  5.  
  6. Console.ReadKey();
  7. }

输出:

这下是不是比开始好了很多,并且他还很好的满足了单一职责原则(SRP),每个类只负责一种动物发出的声音,职责单一了, 但是我们发现如果我们想听到猫的叫声还是要修改Main方法中的调用代码, 还要编译部署,风险还是有点大,工作量还是有点大,那么我们能不能不修改代码只需要改个配置来达到修改Main方法调用的结果呢?这样每次就不用编译只需要修改一下配置就好了呢? 答案是肯定的, 我们利用反射加配置就可以了。 这里我们先加一个工具类用于反射。代码如下:

  1. public class ObjectBuildFactory<T>
  2. {
  3. public static T Instance(string key)
  4. {
  5. Type obj = Type.GetType(key);
  6. if (obj == null) return default(T);
  7.  
  8. T factory = (T)obj.Assembly.CreateInstance(obj.FullName);
  9.  
  10. return factory;
  11. }
  12. }

写配置文件如下:

  1. <appSettings>
  2. <add key="Animal" value="ConsoleApp1.Dog"/>
  3. </appSettings>

调用并通过反射创建对象,调用Dog的叫声如下:

  1. static void Main(string[] args)
  2. {
  3. string key = ConfigurationManager.AppSettings["Animal"];
  4.  
  5. IAnimal animal = ObjectBuildFactory<IAnimal>.Instance(key);
  6.  
  7. animal.Sound();
  8.  
  9. Console.ReadKey();
  10. }

输出:

好了如果希望听到羊的叫声,只需要改一下我们的配置文件就可以了:

  1. <appSettings>
  2. <add key="Animal" value="ConsoleApp1.Sheep"/>
  3. </appSettings>

其它的代码不需要任何修改直接运行输出如下:

好了这回满足OCP了。

那么好了如果客户期望在增加一种动物,我们应该怎么办呢? 这下就变得非常简单了,我们需要如下两个步骤来完成:

1.增加一个类继承IAnimal接口并实现Sound方法。

2.修改配置文件。

例如我们增加一个动物鸭子代码如下:

  1. public class Duck : IAnimal
  2. {
  3. public void Sound()
  4. {
  5. Console.WriteLine("quack quack quack...");
  6. }
  7. }

配置:

  1. <appSettings>
  2. <add key="Animal" value="ConsoleApp1.Duck"/>
  3. </appSettings>

输出:

很简单达到了我们的设计目的。

总结:开闭原则(OCP)是我们在面向对象设计过程中必须注入潜意识的一个原则,在设计的过程中要时时刻刻,如影随形,一旦发现违背就要立即重构,不然代码就会变的越来越不易于理解,越来越不易于维护了。

【面向对象设计原则】之开闭原则(OCP)的更多相关文章

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

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

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

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

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

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

  4. C# 实例解释面向对象编程中的开闭原则

    在面向对象编程中,SOLID 是五个设计原则的首字母缩写,旨在使软件设计更易于理解.灵活和可维护.这些原则是由美国软件工程师和讲师罗伯特·C·马丁(Robert Cecil Martin)提出的许多原 ...

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

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

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

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

  7. 深入理解JavaScript系列(7):S.O.L.I.D五大原则之开闭原则OCP

    前言 本章我们要讲解的是S.O.L.I.D五大原则JavaScript语言实现的第2篇,开闭原则OCP(The Open/Closed Principle ). 开闭原则的描述是: Software ...

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

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

  9. Java的开发—面向对象的7大原则之开闭原则(一)

    开闭原则(Open Close Principle) 一.定义: 软件中的(类.模块.函数等等)应该对于扩展是开放的,对于修改时关闭的.意味着一个实体允许在不改变它的源代码的前提变更它的行为 这里的软 ...

  10. 为什么要提倡"Design Pattern"呢? 开闭原则 系统设计时,注意对扩展开放,对修改闭合。

    [亲身经历] 无规矩不成方圆 设计模式 - 搜狗百科 https://baike.sogou.com/v123729.htm?fromTitle=设计模式 为什么要提倡"Design Pat ...

随机推荐

  1. JS日历插件 - My97 DatePicker用法详解

    一.简介 1. 注意事项 (1)My97DatePicker目录是一个整体,不可破坏里面的目录结构,也不可对里面的文件改名,可以改目录名: (2)各目录及文件的用途: WdatePicker.js 配 ...

  2. [Swift]LeetCode93. 复原IP地址 | Restore IP Addresses

    Given a string containing only digits, restore it by returning all possible valid IP address combina ...

  3. [Swift]LeetCode798. 得分最高的最小轮调 | Smallest Rotation with Highest Score

    Given an array A, we may rotate it by a non-negative integer K so that the array becomes A[K], A[K+1 ...

  4. Python基础语法(三)

    Python基础语法(三) 1. 数值型数据结构 1.1 要点 在之前的博客也有提到,数值型数据结构在这里就不过多介绍了.在这里提及一些需要知道的知识点. int.float.complex.bool ...

  5. 关于移动端开发,vedio标签层级高遮挡蒙版的解决方案

    问题描述: 使用famework7框架搭建了一个界面,然后再界面中需要使用蒙版效果,在PC端,ios测试没有问题,在Andriod播放视屏再点击显示蒙版的效果师,视频会遮盖蒙版.修改定位,z-inde ...

  6. [Abp 源码分析]十五、自动审计记录

    0.简介 Abp 框架为我们自带了审计日志功能,审计日志可以方便地查看每次请求接口所耗的时间,能够帮助我们快速定位到某些性能有问题的接口.除此之外,审计日志信息还包含有每次调用接口时客户端请求的参数信 ...

  7. mybatis框架(6)---mybatis插入数据后获取自增主键

    mybatis插入数据后获取自增主键 首先理解这就话的意思:就是在往数据库表中插入一条数据的同时,返回该条数据在数据库表中的自增主键值. 有什么用呢,举个例子: 你编辑一条新闻,同时需要给该新闻打上标 ...

  8. Elasticsearch Search API

    当执行一个搜索时,它将这个搜索请求广播给所有的索引分片.可以通过提供路由参数来控制要搜索哪些分片.例如,当检索tweets这个索引时,路由参数可以设置为用户名: curl -X POST " ...

  9. SpringBoot入门教程(十二)DevTools热部署

    devtools模块,是为开发者服务的一个模块.主要的功能就是代码修改后一般在5秒之内就会自动重新加载至服务器,相当于restart成功.与JRebel不同的是,JRebel是一款商业插件,devto ...

  10. InterlliJ Debug方式启动:method breakpoints may dramatically show down debugging

    使用idea在DEBUG的时候出现Method breakpoints may dramatically slow down debugging, 如图: 根据语义可能是断点打在方法上面了,导致在某个 ...