设计模式之开闭原则

什么是开闭原则?

开放封闭原则称为OCP原则(Open Closed Principle)是所有面向对象原则的核心。

"开闭原则"是面向对象编程中最基础和最重要的设计原则之一。

软件设计本身所追求的目标就是封装变化、降低耦合,而开放封闭原则正是对这一目标的最直接体现。其他的设计原则,很多时候是为实现这一目标服务的。

为什么要用开闭原则?

如果在进行功能扩展的时候,添加额外的类是没问题的,但因为功能扩展而修改之前运行正常的程序,这是忌讳的,不被允许的。因为一旦修改之前运行正常的程序,就会导致项目整体要进行全方位的重新测试。这是相当麻烦的过程。导致问题的主要原因是:代码和代码之间的耦合度太高。

先来看开闭原则的定义:

Software entities like classes,modules and functions should be open for extension but closed for modifications 一个软件实体, 如类, 模块, 函数等应该对扩展开放, 对修改封闭.

这也是开放封闭原则的核心思想:对扩展开放,对修改封闭.

这是什么含义呢?

  • 对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况,也就是指我们系统中的模块、类、方法对它们的提供者(开发者)应该是开放的,提供者可以对系统进行扩展(新增)新的功能。
  • 对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对已有代码进行任何修改,也就是指系统中的模块、类、方法对它们的使用者(调用者)应该是关闭的。使用者使用这些功能时,不会因为提供方新增了功能而导致使用者(调用者)也进行相应修改。

如何实现开放封闭原则呢?

"需求总是变化"、"世界上没有一个软件是不变的"。这里投射出的意思是:需求总是变化的, 可是对于软件设计者来说, 如何才能做到不对原有系统修改的前提下, 实现灵活的扩展. 这就是开闭原则要实现的.

我们在设计系统的时候, 不可能设想一次性把需求确定后, 后面就不改变了.这不科学也不现实的. 既然需求是一定会变化的, 那么我们要如何优雅的面对这种变化呢? 如何设计可以使软件相对容易修改, 不至于需求一变, 就要把整个程序推到重来?

依赖与抽象

实现开放封闭的核心思想就是面对抽象编程,而不是面对具体编程,因为抽象相对稳定。 让类依赖于固定的抽象,所以对修改是封闭的;而通过面向对象的继承和多态机制,可以实现对抽象体的继承,通过覆写其方法来改变固有行为,实现新的扩展方法,所以对于扩展就是开放的。这是实施开放封闭原则的基本思路。

如何落地开闭原则

如果当前的设计不符合开放封闭原则,则必须进行重构。常用的设计模式主要有模板方法(Template Method)设计模式策略(Strategy)设计模式。而封装变化,是实现这一原则的重要手段,将经常发生变化的部分封装为一个类。

开闭原则的重要性

开闭原则是最基础的一个原则,其他五个原则都是它的具体形态。开闭原则是其精神领袖,其他五个原则是指导设计的工具和方法。

1. 开闭原则对测试的影响

单元测试通过一个方法一般需要三种方法:正常数据测试,边界数据测试,异常抛出测试,特别重要的可能需要十几个测试方法。如果修改一个实现方法,可能就要修改多个测试方法,这也可能会出现纰漏。所以需要通过扩展来实现业务需求变化。

2. 开闭原则可以提高复用性

在面向对象的设计中,所有的逻辑都是从原子逻辑组合而来的,而不是在一个类中独立实现一个业务逻辑。如何提高复用性?缩小逻辑力度,知道一个逻辑不可在拆分为止。

3. 开闭原则可以提高可维护性

一款软件投产后,维护人员的工作不仅仅是对数据进行维护,还可能要对程序进行扩展,维护人员最乐意的是就是对一个类进行扩展,而不是修改一个类。

4. 面向对象开发的要求

万物皆对象,我们需要把所有的事物都抽象成对象,然后针对对象进行操作,但是万物皆运动,有运动就有变化,有变化就要有策略去应对变化。怎样快速应对呢?这就需要在设计之初考虑到所有可能变化的因素,然后留下接口,等待"可能"转变为"现实"。

如何使用开闭原则

1、抽象约束

抽象对一组事物的通用描述,没有具体的实现,也就表示它可以有非常多的可能性,可以跟随需求的变化而变化。因此,通过接口或抽象类可以约束一组可能变化的行为,并且能够实现对扩展开放。

2、元数据控件模块行为

3、制定项目章程

4、封装变化

将相同的变化封装到一个接口或抽象类中,将不同的变化封装到不同的接口或抽象类中,不应该有两个不同变化出现在同一个接口或抽象类中。

注意事项

开闭原则对扩展开放,对修改关闭,并不意味这不做任何修改,低层模块的的变更,必然要有高层模块进行耦合,否则就是一个孤立无意义的代码片段。

变化可以归纳为3种:

1. 逻辑变化

只变化一个逻辑,而不改变其他模块。可以修改原有类中的方式来完成,前提条件是所有依赖或关联类都按照相同的逻辑处理。

2. 子模块变化

一个模块变化,必然会对其他的模块产生影响,特别是一个低层次的模块变化必然引起高层次模块的变化。因此,在通过扩展完成变化是,高层次的模块修改是必然的。

3. 可见视图变化

可见视图是提供给客户使用的界面,如JSP程序,Swing界面等,该部分的变化一般会引起连锁反应。最司空见惯的就是业务耦合变化。比如,按原有需求需要展示6列,然后突然有一天需要增加1列,而且这一列需要跨N张表,处理M个逻辑才能展现出来,这样的变化是比较恐怖的,但还是可以通过扩展来完成变化,这就需要看我们原有的设计是否灵活了。

案例1:Windows 的桌面主题设计。

分析:Windows 的主题是桌面背景图片、窗口颜色和声音等元素的组合。用户可以根据自己的喜爱更换自己的桌面主题,也可以从网上下载新的主题。这些主题有共同的特点,可以为其定义一个抽象类(Abstract Subject),而每个具体的主题(Specific Subject)是其子类。用户窗体可以根据需要选择或者增加新的主题,而不需要修改原代码,所以它是满足开闭原则的,其类图如图 所示。

案例2:比如现在国庆节,搞图书打折活动,有以下几种做法:

1.新增打折接口

2.修改原油NewBook类

3.新增打折书籍类

按照开放扩展关闭修改原则,应该是第三种方案,这样原有代码不会受到修改。

java 复制代码
//书籍接口
public interface IBook {
    String getBookName();
    String getAuthor();
    int getPrice();
}
//书籍类
public class NewBook implements IBook {
    private String bookName;
    private String author;
    private int price; 
    public NewBook(String bookName, String author, int price){
        this.bookName = bookName;
        this.author = author;
        this.price = price;
    }
    @Override
    public String getBookName() {
        return this.bookName;
    }
    @Override
    public String getAuthor() {
        return this.author;
    }
    @Override
    public int getPrice() {
        return this.price;
    }
}
//打折书籍类
public class OffBook extends NewBook {
    public OffBook(String bookName, String author, int price) {
        super(bookName, author, price);
    }
    public Double getPrice2(){
        return this.getPrice()*0.7;
    }
}
相关推荐
卡尔特斯1 小时前
Android Kotlin 项目代理配置【详细步骤(可选)】
android·java·kotlin
白鲸开源1 小时前
Ubuntu 22 下 DolphinScheduler 3.x 伪集群部署实录
java·ubuntu·开源
ytadpole1 小时前
Java 25 新特性 更简洁、更高效、更现代
java·后端
纪莫2 小时前
A公司一面:类加载的过程是怎么样的? 双亲委派的优点和缺点? 产生fullGC的情况有哪些? spring的动态代理有哪些?区别是什么? 如何排查CPU使用率过高?
java·java面试⑧股
JavaGuide2 小时前
JDK 25(长期支持版) 发布,新特性解读!
java·后端
用户3721574261352 小时前
Java 轻松批量替换 Word 文档文字内容
java
白鲸开源2 小时前
教你数分钟内创建并运行一个 DolphinScheduler Workflow!
java
晨米酱3 小时前
JavaScript 中"对象即函数"设计模式
前端·设计模式
Java中文社群3 小时前
有点意思!Java8后最有用新特性排行榜!
java·后端·面试
代码匠心3 小时前
从零开始学Flink:数据源
java·大数据·后端·flink