开闭原则
开闭原则是java世界里最基础的设计原则,他指导我们如何建立一个稳定、灵活的系统,先来看开闭原则的定义:
Software entities like classes,modules and functions should be open for extension but close for modifications.(一个软件的实体如类,模块和函数应该对扩展开放,对修改关闭)
我们做一件事,或者选择一个方向,一般要经历三个步骤:what——是什么,why——为什么,How——怎么做(简称3W)(这是完成一个目标的基本过程,可以在各个方面去实践这个过程)。对于开闭原则,我们也采用这个三步来分析,即什么是开闭原则,为什么要使用开闭原则,怎么使用开闭原则。
-
开闭原则的庐山真面目
开闭原则的定义非常明确的告诉我们:软件实体应该对扩展开放,对修改关闭,其含义是说一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。
那什么又是软件实体呢?软件的实体包括以下几个部分:
- 项目或软件产品中按照一定的逻辑规则划分的模块;
- 抽象和类;
-
方法。
一个软件产品只要在生命期内,都会发生变化,既然变化是一个既定的事实,我们就应该在设计的时候尽量适应这些变化,以提高项目的稳定性和灵活性,真正实现拥抱变化。开闭原则告诉我们尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有代码来完成变化,他是为软件实体的未来事件而制定的对现行开发设计进行约束的一个原则。
下面以书店销售书籍为例,其类图6-1:
6-1
代码如下:
public interface IBook {
String getName();
int getPrice();
String getAuthor();
}
public class NovelBook implements IBook {
private String name;
private int price;
private String author;
public NovelBook(String name, int price, String author) {
this.name = name;
this.price = price;
this.author = author;
}
@Override
public String getName() {
return this.name;
}
@Override
public int getPrice() {
return this.price;
}
@Override
public String getAuthor() {
return this.author;
}
}
public class BookStore {
private final static ArrayList<IBook> bookList = new ArrayList();
static {
bookList.add(new NovelBook("天龙八部",3200,"金庸"));
bookList.add(new NovelBook("巴黎圣母院",5600,"雨果"));
bookList.add(new NovelBook("悲惨世界",3500,"雨果"));
bookList.add(new NovelBook("金瓶梅",3500,"兰陵笑笑生"));
}
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)+"元");
}
}
}
注意:这个地方价格定义为int类型并不是错误,在非金融类的项目中对货币的处理时,一般取2位精度,通常的设计方法是在运算过程中扩大100倍,在需要展示时在缩小100倍,减少精度带来的误差。(这个在计算的时候好像就比较麻烦一点,也不能保证在乘除运算的时候结果一定是整除的,缺点好像还是很明显的,当然仅仅是展示的话没什么问题)
那么这样的设计怎么去处理需求变化的情况呢,书籍的价格可能会随着做活动或是其他情况会有折扣调整之类的,现在以打折销售举例:所有40元以上的书籍九折销售,其他的8折销售,有一下三种方法可以处理这种场景:
修改接口
在IBook上新增加一个getOffPrice(),专门用于进行打折处理,所有的实现类实现该方法,但是修改的后果就是,实现该接口的类需要修改,BookStore中的main方法也修改,同时IBook作为接口应该是稳定可靠的,不应该经常变化,否则接口作为契约的作用就失去效能了(我们往往用接口或是抽象去做依赖是为了设计的结构更方便扩展和保持稳定性,但是接口设计好后不应该在去修改,除非万不得已,否者改动接口的结果会导致更多的地方需要改动)修改实现类
修改NovelBook中的方法,直接在getPrice()中实现打折处理,原书中认为在么些情况下这是一个好办法(可能实际中我们很多情况就是这样处理的,但是我个人感觉这个还是有问题,这个打折活动可能有时间限制,这个也可以解决,可以通过加逻辑判断是否走打折逻辑,但是根据这个开闭原则,对修改关闭,这个肯定不是合理的做法)-
通过扩展实现变化
增加一个子类offNovelBook,覆写getPrice方法,高层次的模块也就是static静态模块区)通过offNovelBook类产生新的对象,完成业务对系统的最小化开发,类图6-2:
6-2
这就是通过OffNovelBook去继承NovelBook然后覆写OffNovelBook非getPrice方法,然后修改BookStore的静态代码块的地方,将实现类改为OffNovelBook,那么获取的价格就是打折的价格呢(这个原则给我们处理业务变更的情况下提供了一种解决问题的思路,这块具体的运用看实际怎么做,接下来的23中设计模式是根据这六大原则总结的实现)
注意:开闭原则对扩展开放,对修改关闭,并不意味着不做任何修改,低层模块的变化,必然要有高层模块进行耦合,否则就是一个孤立无意义的代码片段。
这一原则原书有更详细的的解释,这个原则很虚包含很广,具体的理解通过后续的设计模式的设计思维去加深 。
内容来自《设计模式之禅》