设计模式之开闭原则

什么是开闭原则?

开放封闭原则称为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.新增打折书籍类

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

//书籍接口
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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值