6开闭原则

开闭原则

开闭原则的英文名称是Open-Closed Principle,简称OCP。

1开闭原则的定义

开闭原则的英文原文是:
SoftWare entities should be open for extension,but closed for modification.
意思是:一个软件实体应当对扩展开放,对修改关闭。

这个原则说的是,在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展,即应当可以在不必修改源代码的情况下改变这个模块的行为。
在面向对象的编程中,开闭原则是最基础的原则,起到总的指导作用,其他有原则(单一职责、里氏替换、依赖倒置、接口隔离、迪米特法则)都是开闭原则的具体形态,即其他原则都是开闭原则的手段和工具。开闭原则的重要性可以通过以下几种方式体现:
  • 开闭原则提高复用性。在面向对象的设计中,所有的逻辑都是从原子逻辑组合而来的,而不是在一个类中独立实现一个业务逻辑,代码粒度越小,被复用的可能性就越大,避免相同的逻辑重复增加。开闭原则的设计保证系统是在一个高层次上实现了复用。
  • 开闭原则提高灵活性。所有的软件系统都有一个共同的性质,即对系统的需求都会随着时间的推移而发生变化。在软件系统面临新的需求时,系统的设计必须是稳定的。开闭原则可以通过扩展已有的软件系统,提供新的行为,能快速应对变化,以满足对软件新的需求,使变化中的软件系统有一定的适应性和灵活性。
  • 开闭原则易于测试。测试是软件开发过程中必不可少的环节。测试代码不仅要保证逻辑的正确性,还要保证苛刻条件(高压力、异常、错误)下不产生"有毒代码"(Poisonous Code),因此当有变化提出时,原有健壮的代码尽量不需要修改,而是通过扩展来实现。否则,就需要把原有的测试过程回笼一遍,需要进行单元测试、功能测试、集成测试,甚至是验收测试。开闭原则的使用,保证软件是通过扩展来实现业务的逻辑变化,而不是修改。因此,对于新增加的类,只需要新增相应的测试类,编写对相应的测试方法,只要保证新增的类是正确的就可以了。

2开闭原则的应用

案例:书店售书案例

IBook的接口
package com.eric.设计模式原则.开闭原则;

/**
 * @author Eric
 * @ProjectName my_design_23
 * @description 书籍接口IBook
 * @CreateTime 2020-11-23 19:44:34
 */

public interface IBook {
    //获取书名
    public String getName();
    //获取数的价格
    public double getPrice();
    //获取书的作者
    public String getAuthor();
}

书的类 继承了 IBook接口
package com.eric.设计模式原则.开闭原则;

/**
 * @author Eric
 * @ProjectName my_design_23
 * @description 书的实体类
 * @CreateTime 2020-11-23 19:46:31
 */
public class NovelBook implements IBook {
    private String name;
    private double price;
    private String author;

    public NovelBook(String name, double price, String author) {
        this.name = name;
        this.price = price;
        this.author = author;
    }

    public String getName() {
        return name;
    }

    public double getPrice() {
        return price;
    }

    public String getAuthor() {
        return author;
    }
}

书店类
package com.eric.设计模式原则.开闭原则;

import java.util.ArrayList;
import java.util.List;

/**
 * @author Eric
 * @ProjectName my_design_23
 * @description 书店类
 * @CreateTime 2020-11-23 19:48:31
 */
public class BookStore {
    //图书列表
    private List<IBook> bookList = new ArrayList<IBook>();
    //构造函数
    public BookStore(){
        //对图书列表进行初始化
        bookList.add(new NovelBook("数论",33.59,"哈塞"));
        bookList.add(new NovelBook("图论",99.89,"欧拉"));
        bookList.add(new NovelBook("博弈论",49.79,"冯·诺依曼与奧斯卡·摩根斯特恩"));
        bookList.add(new NovelBook("管理学",77.35,"泰勒、法约尔、梅奥"));
    }

    //展示图书
    public void showBooks(){
        System.out.println("---------------书店售书列表--------------");
        System.out.println("书名\t\t价格\t\t作者");
        for (IBook book : bookList) {
            System.out.println(book.getName()+"\t\t¥"+book.getPrice()+"元\t\t"+book.getAuthor());
        }
    }

    public static void main(String[] args) {
        BookStore bookStore = new BookStore();
        bookStore.showBooks();
    }
}
运行结果:

改进
为了扩大图书的销售量,书店要按照9折销售图书,这就需要图书对现有的售书系统进行修改。遵照"开闭原则"中的对修改关闭原则,此时不能直接修改IBook接口 和NovelBook类,而是通过增加一个子类OffNovelBook来完成。
通过OffNovelBook类继承NovelBook实现。

OffNovelBook.java
package com.eric.设计模式原则.开闭原则;

/**
 * @author Eric
 * @ProjectName my_design_23
 * @description 扩展后的图书类
 * @CreateTime 2020-11-23 20:08:27
 */
public class OffNovelBook extends NovelBook{
    //构造函数
    public OffNovelBook(String name, double price, String author) {
        super(name, price, author);
    }
    //重写getPrice()方法
    public double getPrice(){
        //图书的价格打9折
        return super.getPrice()*0.9;
    }
}
在BookStore.java中只需要将原来的new NovelBook改为new OffNovelBook即可。
修改后的BookStore.java为:
package com.eric.设计模式原则.开闭原则;

import java.util.ArrayList;
import java.util.List;

/**
 * @author Eric
 * @ProjectName my_design_23
 * @description 书店类
 * @CreateTime 2020-11-23 19:48:31
 */
public class BookStore {
    //图书列表
    private List<IBook> bookList = new ArrayList<IBook>();
    //构造函数
    public BookStore(){
        //对图书列表进行初始化
        bookList.add(new OffNovelBook("数论",33.59,"哈塞"));
        bookList.add(new OffNovelBook("图论",99.89,"欧拉"));
        bookList.add(new OffNovelBook("博弈论",49.79,"冯·诺依曼与奧斯卡·摩根斯特恩"));
        bookList.add(new OffNovelBook("管理学",77.35,"泰勒、法约尔、梅奥"));
    }

    //展示图书
    public void showBooks(){
        System.out.println("---------------书店售书列表--------------");
        System.out.println("书名\t\t价格\t\t作者");
        for (IBook book : bookList) {
            System.out.println(book.getName()+"\t\t¥"+book.getPrice()+"元\t\t"+book.getAuthor());
        }
    }

    public static void main(String[] args) {
        BookStore bookStore = new BookStore();
        bookStore.showBooks();
    }
}
测试结果:

就这样通过增加一个OffNovelBook类,修改BookStore中少量的代码,就可以实现图书价格的9折销售,而其他部分没有任何变动,体现了开闭原则的应用。
    注意 开闭原则对扩展开放,对修改关闭,并不意味着不作任何修改,低层模块的变更,必然要有高层模块进行耦合,否则就是一个孤立无意义的代码片段。

开闭原则解决问题的关键在于抽象化,把系统所有可能的行为抽象成一个抽象底层,这个抽象底层规定出所有的具体实现必须提供的方法特征,给系统定义出一个一劳永逸、不再更改的抽象设计,此设计允许有无穷无尽的行为在实现层被实现。在Java中,可以定义一个或多个抽象Java类或接口,规定出所有的具体类必须提供的方法特征作为系统设计的抽象层。作为系统设计的抽象层,要预见所有可能的扩展,因此在任何扩展情况下系统的抽象底层不需要修改,从而满足了开闭原则的第二条:对修改关闭。同时,由于从抽象层导出一个或多个新的具体类可以改变系统的行为,因此系统的设计对扩展是开放的,这满足了开闭原则的第一条。

在实际开发过程中,一直都是提倡需求导向的。这就要求在设计的时候,要非常清楚的了解用户需求,判断需求中包含的可能的变化,从而明确在什么情况下使用开闭原则。

在实际开发过程的设计开始阶段,就要罗列出系统所有可能的行为,并把这些行为加入到抽象底层根本就是不可能的,并且是效益非常低的事情。另外,在设计开始阶段,对所有的可变因素进行预计和封装也不太现实,很难做得到。所以,开闭原则描绘的远景只是一种理想情况或是极端状态,现实世界是很难被完全实现的,只能在某些组件,某种程度上符合开闭原则的 要求。

开闭原则是面向对象设计的终极目标,其他设计原则都可以看做是开闭原则的实现方法。




posted @ 2020-11-23 21:27  喵酱张-Eric  阅读(115)  评论(0编辑  收藏  举报