定义
一个软件实体如类,模块和函数应该对扩展开放,对修改关闭。即一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。
软件实体:
项目或软件产品中按照一定的逻辑规则划分的模块
抽象和类
方法
示例
public interface IBook {
//书籍有名称
public String getName();
//书籍有售价
public int getPrice();
//书籍有作者
public 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() {
// TODO Auto-generated method stub
return this.name;
}
@Override
public int getPrice() {
// TODO Auto-generated method stub
return this.price;
}
@Override
public String getAuthor() {
// TODO Auto-generated method stub
return this.author;
}
}
/**
* 书店售书类
* @author Administrator
*
*/
public class BookStore {
private final static ArrayList<IBook> bookList = new ArrayList<IBook>();
//static 静态模块初始化,项目中一般是通过持久层完成
static {
bookList.add(new NovelBook("天龙八部",3200,"金庸"));
bookList.add(new NovelBook("巴黎圣母院",5600,"雨果"));
bookList.add(new NovelBook("悲惨世界",3500,"雨果"));
bookList.add(new NovelBook("金瓶梅",4300,"兰陵笑笑生"));
}
//模拟书店买书
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)+"元");
}
}
}
运行结果:
———书店卖出去的书籍记录如下:———–
书籍名称:天龙八部 书籍作者:金庸 书籍价格:¥32.00元
书籍名称:巴黎圣母院 书籍作者:雨果 书籍价格:¥56.00元
书籍名称:悲惨世界 书籍作者:雨果 书籍价格:¥35.00元
书籍名称:金瓶梅 书籍作者:兰陵笑笑生 书籍价格:¥43.00元
此时如果需求变化,希望40元以上的书籍九折销售,其他8折销售,那么我们如何应对呢?
1.修改接口?
如果在IBook上新增一个getOffPrice()的方法,专门用于进行打折处理,这样结果实现类NovelBook要修改,main方法也要修改。而且接口IBook应该是稳定且可靠的,不应该经常发生变化,否则接口作为契约的作用就失去了效能
2.修改实现类?
修改NovelBook类中的方法,直接在getPrice()中实现打折处理,一般项目中经常使用的就是这种方法(缺陷修复),但是如果采购书籍的人员也要看价格呢?那么他看到的就是打折后的价格了,显然也不正确。
3.通过扩展实现变化
增加一个子类OffNovelBook,覆写getPrice方法,完成业务变化对系统的最小化开发。风险小。
public class OffNovelBook extends NovelBook{
public OffNovelBook(String _name, int _price, String _author) {
super(_name, _price, _author);
// TODO Auto-generated constructor stub
}
//覆写销售价格
@Override
public int getPrice() {
//原价
int selfPrice = super.getPrice();
int offPrice = 0;
if(selfPrice > 40) {
offPrice = selfPrice * 90/100;
}else {
offPrice = selfPrice * 80/100;
}
return offPrice;
}
}
这样业务逻辑只需要修改static块中的构造书籍,其他并没有任何改动。
bookList.add(new OffNovelBook("天龙八部",3200,"金庸"));
bookList.add(new OffNovelBook("巴黎圣母院",5600,"雨果"));
bookList.add(new OffNovelBook("悲惨世界",3500,"雨果"));
bookList.add(new OffNovelBook("金瓶梅",4300,"兰陵笑笑生"));
运行结果显示:
———书店卖出去的书籍记录如下:———–
书籍名称:天龙八部 书籍作者:金庸 书籍价格:¥28.00元
书籍名称:巴黎圣母院 书籍作者:雨果 书籍价格:¥50.00元
书籍名称:悲惨世界 书籍作者:雨果 书籍价格:¥31.00元
书籍名称:金瓶梅 书籍作者:兰陵笑笑生 书籍价格:¥38.00元
总结
逻辑变化
只变化一个逻辑,不涉及其他模块,可以通过修改原有类中的方法来完成,前提条件是所有依赖或关联类都按照相同的逻辑处理。
子模块变化
一个模块变化,会对其他模块产生影响,特别是一个低层次的模块变化必然会引起高层模块的变化,因此在通过扩展完成变化时,高层次的模块修改时必然的。
可见视图变化
提供给客户使用的视图,如jsp等,该部分变化一般会引起连锁反应。如果一个展示数据列表,现在增加显示一列,这一列要跨很多张表,处理很多逻辑才能展现出来,这样的变化时比较恐怖的。还是可以通过扩展来完成,这要看原有的设计是否灵活。
为什么采用开闭原则
测试逻辑清晰
单元测试时只需要保证此新加的类正确就可以了,非常清晰。
可以提高复用性
逻辑颗粒度越小,被复用的可能性就越大。
提高可维护性
对原有代码修改,要先读懂源码,是一件很痛苦的事情。通过扩展,大大提高了维护人员的可维护性。
面向对象开发要求
设计开始就要考虑有可能变化的因素,留下接口,等待可能变为现实。
如何使用开闭原则
抽象约束
通过接口或抽象类可以约束一组可能变化的行为,并且能够对扩展开放。比如上面实例中还可以增加艺术书籍,技术书籍,水利工程书籍等等。只需要扩展接口,其他的水到渠成。
元数据控制模块行为
元数据:配置参数,可以从文件中获得,也可以从数据库中获得,用来描述环境和数据的数据。比如login方法中提供了这样的逻辑,先检查IP地址是否在允许访问的列表中,然后再决定是否需要到数据库中验证密码。
制定项目章程
比如SSH项目开发,bean配置文件非常多,如果扩展,就需要增加子类,并修改配置文件。然而如果制定了一个项目章程,规定所有Bean都自动注入等等。这就需要项目内约束。
封装变化
将相同的变化封装到一个接口或抽象类中,将不同的变化封装到不同的接口或抽象类中。