扩展在面向对象编程领域中,开闭原则 (The Open/Closed Principle, OCP) 规定“软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的。
这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。该特性在产品化的环境中是特别有价值的,在这种环境中,改变源代码需要代码审查,单元测试以及诸如此类的用以确保产品使用品质的过程。遵循这种原则的代码在扩展时并不发生改变,因此无需上述的过程。
开闭原则的命名被应用在两种方式上。这两种方式都使用了继承来解决明显的困境,但是它们的目的,技术以及结果是不同的。
梅耶开闭原则
伯特兰·迈耶一般被认为是最早提出开闭原则这一术语的人,[来源请求]在他1988年发行的《面向对象软件构造》中给出。这一想法认为一旦完成一个类的实现只应该因错误而修改,新的或者改变的特性应该通过新建不同的类实现。新建的类可以通过继承的方式来重用原类的代码。衍生的子类可以或不可以拥有和原类相同的接口。
梅耶的定义提倡实现继承。具体实现可以通过继承方式来重用,但是接口规格不必如此。已存在的实现对于修改是封闭的,但是新的实现不必实现原有的接口。
多态开闭原则
在20世纪90年代,开闭原则被广泛的重新定义由于抽象化接口的使用,在这中间实现可以被改变,多种实现可以被创建,并且多态的替换不同的实现。
相比梅耶的使用方式,多态开闭原则的定义倡导对抽象基类的继承。接口规约可以通过继承来重用,但是实现不必重用。已存在的接口对于修改是封闭的,并且新的实现必须,至少,实现那个接口。
思想罗伯特·C·马丁1996年发表的文章《开闭原则》是使用这种方法的启发式著作。在2001年,Craig
Larman把开闭原则关联到了Alistair Cockburn的名为“受护的变量”(Protected
Variations)的模式以及David Parnas关于信息隐藏的讨论。
抽象构建框架,实现扩展细节。
开闭原则 的关键是抽象化,抽象类/接口定义抽象层,具体类进行扩展。
举例:
解决了什么问题工作中:9小时弹性工作,9小时是框架不变,可变的是具体上班和下班的时间,只要保证9个小时即可。 这是开闭原则在生活中的一种应用。
作为一名程序媛,大家有没有相似的经历,来了一个需求就要改一次代码,这种方式我们已经见怪不怪了。每人每次都只改了一点点,但是,经过长期积累,再来一个新的需求,改动量就要很大了。而在这个过程中,每个人都很无辜,因为每个人都只是遵照惯例在修改。但结果是,所有人都受到了伤害,代码越来越难以维护。
既然“修改”会带来这么多问题,那我们可以不修改吗?开放封闭原则就提供了这样的一个新方向。
代码应用开闭原则是最重要的一个原则,其他几个原则是它的体现或延伸,开闭原则的优点:提高程序的可复用性,可维护性。
需求
“我” 转行当java 课程讲师啦,现在需要向用户展示课程信息(ID,课程名,课程价格等)
代码实现如下:(以下代码有些地方不规范,仅供参考)
public interface ICourse {
Integer getId();
String getName();
Double getPrice();
}
public class JavaCourse implements ICourse {
private Integer id;
private String name;
private Double price;
public JavaCourse(Integer id, String name, Double price) {
this.id = id;
this.name = name;
this.price = price;
}
@Override
public Integer getId() {
return this.id;
}
@Override
public String getName() {
return this.name;
}
@Override
public Double getPrice() {
return this.price;
}
}
public class Test {
public static void main(String[] args) {
ICourse javaCourse = new JavaCourse(96, "Java", 34d);
System.out.println("课程ID:" + javaCourse.getId() + " 课程名称: " + javaCourse.getName() + " 课程价格: " + javaCourse.getPrice());
}
}
类图
需求变更
快双十一了,要进行课程的打折活动,如打5折,需要向用户展示打折后的课程价格 和 课程原价。
常规的方式是 在 接口 ICourse中新加 打折方法 的声明,在JavaCourse 类中新加打折方法的实现,最后在 客户端 test测试类 添加 打折方法 相关的内容。
那这种方式符合开闭原则么,显然是不符合的,需求的变更,使用这种方式 对原有的接口和类都进行了修改,而开闭原则是 对扩展开放,对修改关闭,那应该怎么做呢?
原有接口 ICourse 和 JavaCourse 类 不变,新增JavaDisCountCourse 类,同时在客户端添加 打折相关的内容即可。
JavaDisCountCourse类
public class JavaDisCountCourse extends JavaCourse {
// 构造方法不动
public JavaDisCountCourse(Integer id, String name, Double price) {
super(id, name, price);
}
// 获取原价
public Double getOriginPrice() {
return super.getPrice();
}
// 重写获取价格方法,好处:复用
@Override
public Double getPrice() {
// 还可根据需求在这加其他条件
return super.getPrice() * 0.5;
}
}
public class Test {
public static void main(String[] args) {
ICourse javaCourse = new JavaCourse(96, "Java", 34d);
System.out.println("课程ID:" + javaCourse.getId() + " 课程名称: " + javaCourse.getName() + " 课程价格: " + javaCourse.getPrice());
ICourse discountJavaCourse = new JavaDisCountCourse(97, "discountJava", 100d);
Double originPrices = ((JavaDisCountCourse) discountJavaCourse).getOriginPrice();
System.out.println("打折课程ID:" + discountJavaCourse.getId() + " 打折课程名称: " + discountJavaCourse.getName() + " 打折课程价格: " + discountJavaCourse.getPrice() + " 原价: " + originPrices);
}
}
改后的类图
那问题又来了,客户端 test 因为需求的改变修改了代码,不符合开闭原则,怎么解决呢,配置文件…
小结以不变应万变,设计模式的七大原则 正是 这 ”不变“。
设计模式,架构,框架
设计模式概览



