114  
查询码:00000552
设计原则 之 开放封闭原则
来源:https://blog.csdn.net/wayne_lee_lwc/article/details/105638713
作者: 朱凡 于 2021年02月04日 发布在分类 / FM组 / FM_App 下,并于 2021年02月04日 编辑
原则 设计 封闭 修改 开放 扩展 模式 数据 一个 结构

设计原则 之 开放封闭原则


分析一波

开放封闭原则,它规定了软件设计和维护时应该遵守的一种规则,即对扩展开放、对修改关闭。

扩展和修改不难理解,扩展指的是对原有的结构进行拓展,增加新的类让其继承父类、实现接口,利用多态、继承等特性进而实现新的功能。而修改在这里指的是更改那些已经投入使用的类尤其是抽象类和接口,包括增删改类方法及其实现步骤或参数,还有类的属性等。

面对功能的实现与可能的增改。对扩展开放意味着,通过扩展代码、增加新的类进行实现是允许和推荐的;而对修改关闭意味着,要尽可能地避免对源代码的修改。

合在一起说,就是在设计与维护时应尽可能的通过扩展而非修改原有结构来实现新的功能。

为了达到可便于修改这一要求,抽象是必不可少的,一个恰到好处的抽象总可以囊括即将到来的变化,当需求变化来临时只需要针对具体的变化扩展一个抽象的具体实现类,由于多态的特性很好的解决问题,同时也避免对原有代码的修改。可以说,对扩展开放”的关键是”抽象”,而对象的多态则保证了这种扩展的开放性(更具体的说,应该是里氏替换原则)。

来个粟子

这里可以引用上一篇讲述里氏替换原则的例子来说明这个原则:

最初的需求:设计类表示动物鸟,具有移动方法,实例化鸟类对象并调用这个方法。

实现:

class Bird {  String name; Bird(String name){ this.name = name;} public void move() {  System.out.println(name + " is flying~~~"); } } ... class Main {  public static void main(String[] args) {  Bird swallow= new Bird("swallow"); swallow.move(); } } 
更改需求,增加一个狗类,同样具有移动方法,实例化一个狗类对象,调用这个方法。

分析:最简单的想法是,像鸟类一样写一个狗类对象,更改主方法中的调用。但是这样的操作就违反了我们的开闭原则,我们对源码进行了修改,如果以后增加其他的动物类,那么主方法中语句就需要不断地修改(而若是这个类已经被大量使用,那么修改的规模和难度可就十分吓人了)。
因此这样的设计是需要进行优化的。应该将鸟类和狗类以及其他的可能出现的相似的类抽象成为动物类,拥有共同的行为和属性如移动和名称,这时再添加新的动物,派生新的子类即可。实例化时使用动物的引用也可以避免大量的修改。
修改后的代码:

abstract class Animal {  protected String name; abstract public void move(); } class Bird extends Animal {  Bird(String name){ this.name = name;} public void move() {  System.out.println(name + " is flying~~~"); } } ... class Main {  static public void main(String[] args) {  Animal animal = new Bird("swallow"); animal.move(); } } 

增加一个狗类并实例化对象

class Dog extends Animal {  Dog(String name){ this.name = name;} public void move() {  System.out.println(name + " is running~~~"); } } ... class Main {  static public void main(String[] args) {  Animal animal1 = new Dog("Husky"); Animal animal2 = new Bird("swallow"); animal1.move(); animal2.move(); } } 

此时,如果再度增加需求,要求狗类拥有捕食行为以某个动物为捕食对象。

  • 错误的做法就是直接在狗类中添加捕食方法,这样的操作不利于添加新的具有捕食行为的动物类。
  • 更加错误的做法是在动物类中添加捕食行为,一个抽象父类应该是稳定的,对父类添加抽象方法,必定会导致所有的子类的对其功能的实现,这又是大面积的修改,并且考虑到并非所有动物都有捕食行为,这样的操作又是违反接口隔离原则的。
  • 因此合适的做法应该是扩展原有代码,增加一个猎手接口作为标签,内置一个捕猎的方法。并使可以捕猎的动物类实现这个接口

代码:

... interface Hunter//定义猎手接口 {  public void hunt(Animal prey); } class Dog extends Animal implements Hunter//继承动物类并实现猎手接口 {  Dog(String name){ this.name = name;} public void move() {  System.out.println(name + " is running~~~"); } public void hunt(Animal prey)//具体的捕猎行为 {  System.out.println(name + " has hunt " + prey.name); } } ... class Main {  static public void main(String[] args) {  Animal animal = new Bird("swallow"); animal.move(); Hunter hunter = new Dog("Husky"); hunter.hunt(animal); } } 

说在最后

  • 在开闭原则中可以看到很多其他原则和设计模式的影子,这不是偶然。
    开闭原则可以说是最根本的原则,其余的设计原则,可以看做是在这一大原则下分述某个具体的方向,且每个设计模式的的设计必须要遵循六大基本原则。

  • 另外,开放封闭原则很好地保证了代码的可复用性和可维护性。但需要注意的是,设计要做到完全对修改封闭,这几乎是不可能完成的,因为需求是未知的,抽象又是根据需求和内容来设计的。因此,如要做出很好满足开闭原则的设计,需要设计者的经验和洞察力,这个过程必定伴随着不断地试错和学习。

最后,博主也是一个正在努力学习的萌新,因此对于这样的大原则一定会有拿捏不准的和遗漏的地方,如有疏漏,还请指出,我们共同学习。


系列文章参考资料:

  • 图解Java设计模式 。视频很全也很棒,墙裂推荐。视频就在站内。
  • 《设计模式》 设计模式的经典。
  • 《大话设计模式》非常有名的小白友好型设计模式著作。

设计模式系列文章:
面向对象 之 不能不知道的类间关系(上)泛化、实现、依赖 C++与Java
面向对象 之 不能不知道的类间关系(下)关联、聚合、组合 C++与Java
JAVA单例模式分析及其实现:饿汉式、懒汉式、双重检查、静态内部类
设计模式 之 迪米特法则
设计模式 之 里氏替换原则
设计模式 之 设计原则(1) 单一职责原则 接口隔离原则 依赖倒转原则




 推荐知识

 历史版本

修改日期 修改人 备注
2021-02-04 13:31:23[当前版本] 朱凡 创建版本

  目录
    知识分享平台 -V 4.8.7 -wcp