首先,我们看下开放-封闭原则(Open-Closed Principle,简称OCP)的概念:

    • 是指软件实体(类、模块、函数等)应该可以扩展,但是不可修改。
    • 任何新功能(functionality)应该通过添加新class、属性或方法来实现,而不是通过改变现有的代码。

  实现准则:

    • 在继承子类中实现新的功能
    • 允许客户端(clients)访问带有抽象接口的原始基类

为什么要遵循开放-封闭原则?

    • 如果不遵循OCP原则,一个类或函数总是允许添加新的逻辑,那么我们不能不重新测试新的逻辑依赖的整个类
    • 如果不遵循OCP原则很可能破坏单一职责原则,因为随着后期功能的添加,一个类或函数可能完成多项任务,从这个角度来讲,单一职责原则和开放-封闭原则是高度相互依赖的
    • 如果不遵循OCP原则,在现有类上添加功能,后期该类的体积将越来越大,导致维护该类的困难增加

  遵循开放-封闭原则使得设计在面对需求的改变时可以保持相对稳定,因为我们并没有改动原有的代码,而原有代码已被使用和测试过,是稳定的。我们仅仅是在重用原始代码的基础上通过类基础的方式添加了新的代码。

  开放-封闭原则要求在设计之初,尽量让原始基类足够好,写好之后就不要去修改了,如果有新需求,增加一些类就行了,原始基类代码能不动就不动。事实上对原始基类的绝对修改关闭是不可能的,既然不可能完全封闭,设计人员必须对于他设计的模块应该对哪些变化封闭做出选择。他必须先猜测出最有可能发生变化的地方,然后通过构造抽象来隔离那些变化。

  事实上,事先猜测可能的变化也是有困难的,但我们可以在发生小变化时及时想办法应对发生更大变化的可能,也即等到变化发生就考虑创建抽象来隔离以后发生的同类变化。当然,并不是什么时候应对变化都是容易的。我们希望在开发工作展开不久就知道可能发生的变化。查明可能发生变化所等待的时间越长,要创建正确的抽象就越困难,也就是说当变化部分的代码已经在多个地方用到了,再考虑抽象和分离,代价就变大了。

  下面我们通过一个实例来说明开放-封闭原则。

   public class Employee
{
public int ID { get; set; }
public string Name { get; set; } public Employee()
{
}
public Employee(int id, string name)
{
this.ID = id;
this.Name = name;
}
public double CalculateBonus(double salary)
{
return salary * 0.1;
}
}

  以上是一个普通的Employee类,里面封装了ID和Name字段和一个计算奖金的方法,目前运行良好。当我们接到一个新需求,需要区分正式工和合同工并对正式工的奖金计算改用薪水乘以0.2 时,我们应该通过何种方式实现呢?。我们先来看看不用开放-封闭原则如何实现。

 public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
public string EmployeeType { get; set; }
public Employee()
{
}
public Employee(int id, string name,string employeeType)
{
this.ID = id;
this.Name = name;
this.EmployeeType = employeeType;
}
public double CalculateBonus(double salary)
{
if (this.EmployeeType == "Permanent")
return salary * 0.2;
else
return salary * 0.1;
}
}

  从上面代码可以看出,不使用开放-封闭原则时,我们直接在原始Employee类中添加了一个EmployeeType字段,修改了Employee构造器,并在CalculateBonus方法中新增了判断员工类型的逻辑。这种实现方式的缺点见本篇开头部分所述。

  接下来看如何使用开放-封闭原则实现。

 public abstract class Employee
{
public int ID { get; set; }
public string Name { get; set; } public Employee()
{
}
public Employee(int id, string name)
{
this.ID = id;
this.Name = name;
}
public abstract double CalculateBonus(double salary);
}
public class PermanentEmployee : Employee
{
public PermanentEmployee()
{
}
public PermanentEmployee(int id, string name) : base(id, name)
{
}
public override double CalculateBonus(double salary)
{
return salary * 0.2;
}
}
public class TemporaryEmployee : Employee
{
public TemporaryEmployee()
{
}
public TemporaryEmployee(int id, string name) : base(id, name)
{
}
public override double CalculateBonus(double salary)
{
return salary * 0.1;
}
}

  从以上代码可以看到,遵循开放-封闭原则的实现通过后期创建新class的方式添加新功能,并未在原始Employee基类的实现上做改动,仅仅是将可能变化的CalculateBonus方法通过添加abatract关键字来将具体的实现转移到后期添加的继承子类中,这样就将变化隔离出来了。

  开放-封闭原则是面向对象设计的核心所在。遵循这个原则可以带来面向对象技术所声称的巨大好处,也就是可维护、可扩展、可复用、灵活性好。开放人员应该仅对程序中呈现出频繁变化的部分构造抽象,然而,对于应用程序中的每个部分都刻意进行抽象同样不是一个好主意。拒绝不成熟的抽象和抽象本身一样重要。

面向对象SOLID设计原则之Open-Closed原则的更多相关文章

  1. 第2章 面向对象的设计原则(SOLID):6_开闭原则

    6. 开闭原则(Open Closed Principle,OCP) 6.1 定义 (1)一个类应该对扩展开放,对修改关闭.要求通过扩展来实现变化,而且是在不修改己有的代码情况下进行扩展,也不必改动己 ...

  2. 第2章 面向对象的设计原则(SOLID):4_接口隔离原则(ISP)

    4. 接口隔离原则(Interface Segregation Principle,ISP) 4.1 定义 (1)使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口.类间的 ...

  3. SOLID 设计原则

    SOLID 原则基本概念: 程序设计领域, SOLID (单一功能.开闭原则.里氏替换.接口隔离以及依赖反转)是由罗伯特·C·马丁在21世纪早期 引入的记忆术首字母缩略字,指代了面向对象编程和面向对象 ...

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

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

  5. 面向对象SOLID原则的自我理解

    S.O.L.I.D 是面向对象设计(OOD)和面向对象编程(OOP)中的几个重要编码原则(Programming Priciple)的首字母缩写.面向对象设计的原则SRP The Single Res ...

  6. 面向对象设计(OOD)七大原则

    这篇文章我会不停的维护它,它将会越来越长,但它是关于我在面向对象中的一些学习的思考心得.希望对自己对各位都能实用处.     开篇前,说明一下写这篇文章的原因.原因是由于设计模式.由于设计模式里的各种 ...

  7. GOF 的23种JAVA常用设计模式总结 03 面向对象七大设计原则

    在软件开发中,为了提高软件系统的可维护性和可复用性,增加软件的可扩展性和灵活性,程序员要尽量根据 7 条原则来开发程序,从而提高软件开发效率.节约软件开发成本和维护成本. 各位代码界的大佬们总结出的七 ...

  8. 《设计模式之美》 <03>面向对象、设计原则、设计模式、编程规范、重构,这五者有何关系?

    面向对象 现在,主流的编程范式或者是编程风格有三种,它们分别是面向过程.面向对象和函数式编程.面向对象这种编程风格又是这其中最主流的.现在比较流行的编程语言大部分都是面向对象编程语言.大部分项目也都是 ...

  9. Java 面向对象的设计原则

    一. 1.面向对象思想的核心: 封装.继承.多态.   2.面向对象编程的追求: 高内聚低耦合的解决方案: 代码的模块化设计: 3.什么是设计模式: 针对反复出现的问题的经典解决方案,是对特定条件下( ...

随机推荐

  1. 精《Linux内核精髓:精通Linux内核必会的75个绝技》一HACK #5 使用checkpatch.pl检查补丁的格式

    HACK #5 使用checkpatch.pl检查补丁的格式 本节介绍发布前检查补丁格式的方法.Linux内核是由多个开发者进行开发的.因此,为了保持补丁评估与源代码的可读性,按照统一的规则进行编写是 ...

  2. pycharm中使用redis模块入门

    数据缓存系统:1:mongodb:是直接持久化,直接存储于硬盘的缓存系统2:redis: 半持久化,存储于内存和硬盘3:memcache:数据只能存储在内存里的缓存系统 redis是一个key-val ...

  3. Selenium Webdriver——Xpath轴定位(preceding)

     1.preceding-sibling 选取当前节点之前的所有同级节点 text=出发之前的同级节点: 2.preceding 选取当前节点开始标签之前的所有节点 text=出发节点标签之前的所有i ...

  4. SpringFox swagger2 and SpringFox swagger2 UI 接口文档生成与查看

    依赖: <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 --> <dependency ...

  5. 「小程序JAVA实战」小程序的分享和下载功能(69)

    转自:https://idig8.com/2018/09/25/xiaochengxujavashizhanxiaochengxudefenxianghexiazaigongneng68/ 在小程序上 ...

  6. Yii框架操作数据库的几种方式与mysql_escape_string

    一.Yii操作数据库的几种选择 1,PDO方式. $sql = "";//原生态sql语句 xx::model()->dbConnection->createComma ...

  7. latex如何插入空白行

    1.~\\:一行空白2.\\[行距]:可加入任意间距的空白行 [xpt]

  8. 动量Momentum梯度下降算法

    梯度下降是机器学习中用来使模型逼近真实分布的最小偏差的优化方法. 在普通的随机梯度下降和批梯度下降当中,参数的更新是按照如下公式进行的: W = W - αdW b = b - αdb 其中α是学习率 ...

  9. 使用IntelliJ IDEA,gradle开发Java web应用步骤

    最近 正在学习gradle构建工具的使用,看了一堆的文档,有点一知半解,索性动作实践一把,在以后的自己的项目中尝试使用看看.目前手头用的是IntelliJ IDEA 14,搭建了一天终于明白怎么集成g ...

  10. Associate File Type with Qt In Mac Os and Win

    Win Registry Question One day, my boss want me to finish one function which let the users can double ...