定义:
一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
开闭原则的定义已经非常明确的告诉我们:软件实体应该对扩展开放,对修改关闭,其含义是说一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。那么什么又是软件实体呢?软件实体包括包含以下几个部分:
- 项目或软件产品中按照一定的逻辑规则划分的模块。
- 抽象和类。
- 方法。
开闭原则告诉我们应尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码俩完成变化,它是为软件实体的未来事件而制定的对现行开发设计进行约束的一个原则。
书籍接口
public interface IBook {
// 书籍有名称
public String getName();
// 书籍有售价
public int getPrice();
// 书籍有作者
pulibc String getAuthor();
}
小说类
public class NoveIBook implements IBook {
// 书籍名称
private String name;
// 书籍的价格
private int price;
// 书籍的作者
private String author;
// 通过构造函数传递书籍数据
public NoveBook(String _name, int _price, String _author) {
this.name = _name;
this.price = _price;
this.author = _author;
}
// 获取作者是谁
public String getAuthor() {
return this.author;
}
//书籍叫什么名字
public String getName(){
return this.name;
}
// 获得书籍的价格
public int getPrice(){
return this.price;
}
}
注意:我们把价格定义为int类型并不是错误,在非金融类项目中对货币处理时,一般取2位精度,通常的设计方法时在运算过程中扩大100倍,在需要展示时再缩小100倍,减少精度带来的误差。
书店售书类
public class BookStore {
private final static ArrayList<IBook> bookList = new ArrayList<IBook>();
// static静态模块初始化数据,实际项目中一般是由持久层完成
static {
bookList.add(new NoveIBook("天龙八部" , 3200 , "金庸"));
bookList.add(new OffNoveIBook ("巴黎圣母院" , 5600 , "雨果"));
bookList.add(new OffNoveIBook ("悲惨世界" , 3500 , "雨果"));
bookList.add(new ComputerBook ("Think in Java" ,4300 , "Bruce Eckel" , "编程语言"));
}
// 模拟书店买书
public static void main(String[] args) {
NumberFormat formatter = NumberFormat.getCurrencyInstance();
formatter.setMaximumFractionDigits(2);
System.out.println("---------------------书店卖出去的书籍记录如下:------------------");
for(IBook book : bookList) {
System.out.println("书籍名称:" + book.getName() + "\t书籍作者:" + book.getAuthor() + "\t书籍价格:" + formatter.format(book.getPrice() / 100.0) + "元");
}
}
}
打折销售的小说类
public class OffNoveIBook extends NoveIBook {
public OffNoveIBook(String _name, int _price, String _author,) {
super(_name , _price , _author);
}
// 覆写销售价格
@Override
public int getPrice() {
// 原价
int selfPrice = super.getPrice();
int offPrice = 0;
int (selfPrice > 4000) { // 原价大于40元,则打9折
offPrice = selfPrice * 90 / 100;
} else {
offPrice = selfPrice*80 / 100;
}
return offPrice;
}
}
注意:开闭原则对扩展开放,对修改关闭,并不意味着不做任何修改,底层模块的变更,必然要有高层模块进行耦合,否则就是一个孤立无意义的代码片段。
我们可以把变化归纳为以下三种类型:
- 逻辑变化
- 只要变化一个逻辑,而不涉及其他模块,比如原有的一个算法是a*b+c,现在需要修改为a*b*c,可以通过修改原有类中的方法的方式来完成,前提条件是所有依赖或关联类都按照相同的逻辑处理。
- 子模块变化
- 一个模块变化,会对其他的模块产生影响,特别是一个低层次的模块变化必然引起高层模块的变化,因此在通过扩展完成变化时,高层次的模块修改时必然的,刚刚的书籍打折处理就是类似的处理模块,该部分的变化甚至会引起界面的变化。
- 可见视图变化
- 可见视图是提供给客户使用的界面,如JSP程序、Swing界面等,该部分的变化一般会引起连锁反应。
一个项目的基本路径应该是这样的:项目开发、重构、测试、投产、运维,其中的重构可以对原有的设计和代码进行修改,运维尽量减少对原有代码的修改,保持历史代码的纯洁性,提高系统的稳定性。
依照Java语言的称谓,开闭原则是抽象类,其他五大原则是具体实现类。
开闭原则是非常重要的,可通过以下几个方面来理解其重要重要性。
1、开闭原则对测试的影响
2、开闭原则可以提高复用性
缩小逻辑粒度,直到一个逻辑不可再拆分为止。
3、开闭原则可以提高维护性
4、面向对象开发的需求。
如何使用开闭原则:
1、抽象约束
抽象是对一组事物的通用描述,没有具体的实现,也就表示他可以有非常多的可能性,可以跟随需求的变化而变化。因此,通过接口或抽象类可以约束一组可能变化的行为,并且能够实现对扩展开放,其包含三层含义:
第一,通过接口或抽象类约束扩展,对扩展进行边界限定,不允许出现在接口或抽象类不存在的public方法;
第二,参数类型、引用对象尽量使用接口或抽象类,而不是实现类。
第三,抽象层尽量保持稳定,一旦确定即不允许修改。
计算机书籍接口
public interface IComputerBook extends IBook {
// 计算机书籍是一个范围
public String getScope();
}
计算机书籍类
public class ComputerBook implements IComputerBook {
private String name;
private String scope;
private String author;
private int price;
public ComputerBook(String _name , int _price , String _author , String _scope) {
this.name =_name;
this.price = _price;
this.author = _author;
this.scope = _scope;
}
public String getScope() {
return this.scope();
}
public String getAuthor() {
return this.author;
}
public String getName() {
return this.name;
}
public int getPrice() {
return this .price;
}
}
要实现对扩展开放,首要前提条件就是抽象约束。
2、元数据(metadata)控制模块行为
什么是元数据?用来描述环境和数据的数据,通俗的说就是配置参数,参数可以从文件中获得,也可以从数据库获得。
3、制定项目章程
对项目来说,约定优于配置。
4、封装变化
对变化的封装包含两层含义:
第一,将相同的变化封装到一个接口或抽象类中;
第二,将不同的变化封装到不同的接口或抽象类中,不应该有两个不同的变化出现在同一个接口或抽象类中。
封装变化,也就是受保护的变化,找出预计有变化或不稳定的点,我们为这些变化点创建稳定的接口,准确的讲是封装可能发生的变化,一旦预测到或“第六感”发觉有变化,就可以封装,23个设计模式都是从各个不同的角度对变化进行封装的。
最佳实践:
软件设计最大的难题就是应对需求的变化,但是纷繁复杂的需求变化又是不可预料的。我们要为不可预料的事情做好准备,这本身就是一件非常痛苦的事情,但是大师们还是给我们提出了非常好的6大设计原则以及23个设计模式来“封装”未来的变化,我们在前5章讲过如下设计原则。
- Single Responsibility Principle:单一职责原则
- Open Colsed Principle:开闭原则
- Liskov Substitution Principle:里氏替换原则
- Law of Demeter:迪米特法则
- Interface Segregation Principle:接口隔离原则
- Dependence Inversion Principle:依赖倒置原则
把这6个原则的首字母(里氏替换原则和迪米特法则的首字母重复,只取一个)联合起来就是SOLID(solid,稳定的),其代表的含义也就是把这6个原则结合使用的好处:建立稳定、灵活、健壮的设计,而开闭原则又是重中之重,是最基础的原则,是其他5大原则的精神领袖。
我们在使用开闭原则时需要注意以下几个问题
- 开闭原则也只是一个原则
- 项目规章非常重要
- 预知变化