开闭原则是Java世界里最基础的设计原则,它指导我们如何建立一个稳定的、灵活的系统,先来看开闭原则的定义:
Software entities like classes,modules and functions should be open for extension but closed for
modifications.(一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。)
我们做一件事情,或者选择一个方向,一般需要经历三个步骤:What——是什么,Why
——为什么,How——怎么做(简称3W原则,How取最后一个w)。对于开闭原则,我们也
采用这三步来分析,即什么是开闭原则,为什么要使用开闭原则,怎么使用开闭原则。
一个软件产品只要在生命期内,都会发生变化,既然变化是一个既定的事实,我们就应
该在设计时尽量适应这些变化,以提高项目的稳定性和灵活性,真正实现“拥抱变化”。开闭
原则告诉我们应尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来完
成变化,它是为软件实体的未来事件而制定的对现行开发设计进行约束的一个原则。我们举
例说明什么是开闭原则,以书店销售书籍为例,其类图如图
package OpenOffP; public interface IBook { public String getName(); public int getPrice(); public String getAuthor(); }
package OpenOffP; 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; } }
package OpenOffP; import java.text.NumberFormat; import java.util.ArrayList; public class BookStore { private final static ArrayList<IBook> bookList = new ArrayList<IBook>(); 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 numberFormat = NumberFormat.getCurrencyInstance(); numberFormat.setMaximumFractionDigits(2); System.out.println("------------------"); for (IBook book:bookList){ System.out.println("书籍名称:" + book.getName()+"\t书籍作者:" + book.getAuthor()+"\t书籍价格:"+ numberFormat.format (book.getPrice()/ 100.0)+"元"); } } }
一个简单的卖书代码。
打折啦,大甩卖。所有40元以上的书籍9折销售,其他的8
折销售。对这个项目来说,这就是一个变化,我们应该如何应对这样一个需求变化?
有如下三种方法可以解决这个问题:
● 修改接口
在IBook上新增加一个方法getOffPrice(),专门用于进行打折处理,所有的实现类实现该
方法。但是这样修改的后果就是,实现类NovelBook要修改,BookStore中的main方法也修
改,同时IBook作为接口应该是稳定且可靠的,不应该经常发生变化,否则接口作为契约的
作用就失去了效能。因此,该方案否定。
● 修改实现类
修改NovelBook类中的方法,直接在getPrice()中实现打折处理,好办法,我相信大家在
项目中经常使用的就是这样的办法,通过class文件替换的方式可以完成部分业务变化(或是
缺陷修复)。该方法在项目有明确的章程(团队内约束)或优良的架构设计时,是一个非常
优秀的方法,但是该方法还是有缺陷的。例如采购书籍人员也是要看价格的,由于该方法已
经实现了打折处理价格,因此采购人员看到的也是打折后的价格,会因信息不对称而出现决
策失误的情况。因此,该方案也不是一个最优的方案。
● 通过扩展实现变化
增加一个子类OffNovelBook,覆写getPrice方法,高层次的模块(也就是static静态模块
区)通过OffNovelBook类产生新的对象,完成业务变化对系统的最小化开发。好办法,修改
也少,风险也小,修改后的类图
package OpenOffP; public class OffNovelBook extends NovelBook{ public OffNovelBook(String name,int price,String author){ super(name,price,author); } @Override public int getPrice(){ int selfPrice = super.getPrice(); int offPrice = 0; if (selfPrice>4000){ offPrice = selfPrice*90/100; }else { offPrice = selfPrice*80/100; } return offPrice; } }
1. 抽象约束
抽象是对一组事物的通用描述,没有具体的实现,也就表示它可以有非常多的可能性,
可以跟随需求的变化而变化。因此,通过接口或抽象类可以约束一组可能变化的行为,并且
能够实现对扩展开放,其包含三层含义:第一,通过接口或抽象类约束扩展,对扩展进行边
界限定,不允许出现在接口或抽象类中不存在的public方法;第二,参数类型、引用对象尽
量使用接口或者抽象类,而不是实现类;第三,抽象层尽量保持稳定,一旦确定即不允许修
改。还是以书店为例,目前只是销售小说类书籍,单一经营毕竟是有风险的,于是书店新增
加了计算机书籍,它不仅包含书籍名称、作者、价格等信息,还有一个独特的属性:面向的
是什么领域,也就是它的范围,比如是和编程语言相关的,还是和数据库相关的,等等,修
改后的类图如图:
package OpenOffP; public interface IComputerBook extends IBook{ public String getScore(); }
package OpenOffP; public class ComputerBook implements IComputerBook{ private String name; private int price; private String author; private String score; public ComputerBook(String name,int price,String author,String score){ this.name = name; this.price = price; this.author = author; this.score = score; } @Override public String getScore() { return this.score; } @Override public String getName() { return this.name; } @Override public int getPrice() { return this.price; } @Override public String getAuthor() { return this.author; } }