1、6开闭原则
定义
一个软件实体,如类、模块和函数应该对扩展开放(通过扩展来实现变化),对修改关闭(不是通过修改已有的代码来实现变化)。(开闭原则是java世界里最基础的设计原则。3W原则:What-是什么,Why-为什么,How怎么做。)
注意:开闭原则对扩展开放,对修改关闭,并不意味着不做任何修改,低层模块的变更,必然要有高层模块进行耦合,否则就是一个孤立无意义的代码片段。
案例
案例1-1
import java.text.NumberFormat;
import java.util.ArrayList;
public class BookStore1 {
private final static ArrayList<IBook> bookList = new ArrayList<>();
static{
bookList.add(new NovelBook("天龙八部",3200,"金庸"));
bookList.add(new NovelBook("巴黎圣母院",5600,"雨果"));
bookList.add(new NovelBook("热爱生命",8888,"杰克伦敦"));
}
public static void main(String[] args) {
NumberFormat numberFormat = NumberFormat.getCurrencyInstance();
numberFormat.setMaximumFractionDigits(2);
for (IBook book:bookList){
System.out.println("name:" + book.getName()
+ "\tauthor:" + book.getAuthor()
+ "\tprice:" + numberFormat.format(book.getPrice() / 100.0));
}
}
}
interface IBook{
String getName();
int getPrice();
String getAuthor();
}
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;
}
}
name:天龙八部 author:金庸 price:¥32.00
name:巴黎圣母院 author:雨果 price:¥56.00
name:热爱生命 author:杰克伦敦 price:¥88.88
案例1-2(优化)
import java.text.NumberFormat;
import java.util.ArrayList;
public class BookStore2 {
private final static ArrayList<IBook> bookList = new ArrayList<>();
static{
bookList.add(new OffNovelBook("天龙八部",3200,"金庸"));
bookList.add(new OffNovelBook("巴黎圣母院",5600,"雨果"));
bookList.add(new OffNovelBook("热爱生命",8888,"杰克伦敦"));
bookList.add(new OffNovelBook("大问题",6666,"罗伯特所罗门"));
}
public static void main(String[] args) {
NumberFormat numberFormat = NumberFormat.getCurrencyInstance();
numberFormat.setMaximumFractionDigits(2);
for (IBook book:bookList){
System.out.println("name:" + book.getName()
+ "\tauthor:" + book.getAuthor()
+ "\tprice:" + numberFormat.format(book.getPrice() / 100.0));
}
}
}
interface IBook{
String getName();
int getPrice();
String getAuthor();
}
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;
}
}
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;
}
}
name:天龙八部 author:金庸 price:¥25.60
name:巴黎圣母院 author:雨果 price:¥50.40
name:热爱生命 author:杰克伦敦 price:¥79.99
name:大问题 author:罗伯特所罗门 price:¥59.99
小结
软件生命周期
一个软件产品只要在生命周期内,都会发生变化,既然变化是一个既定的事实,我们就应该在设计时尽量适应这些变化,以提高项目的稳定性和灵活性,真正实现“拥抱变化”。开闭原则告诉我们应尽量通过扩展实体的行为来实现变化,而不是通过修改已有的代码来完成变化,它是为软件实体的未来事件而制定的对现行开发设计进行约束的一个原则。
软件实体包含的部分
项目或软件产品中按照一定的逻辑规则划分的模块。
抽象和类。
方法。
把变化归类为三种类型
逻辑变化(只变化一个逻辑,而不涉及其他模块)、
子模块变化(一个模块变化,会对其他的模块产生影响,特别是一个低层次的模块变化必然引起高层模块的变化,因此在通过扩展完成变化时,高层次的模块修改是必然的)、
可见视图变化(提供给客户使用的页面,比如视图的变化是否会引起后台一系列表的变化)
对修改历史代码的想法
放弃修改历史的想法吧,一个项目的基本路径应该是这样的:项目开发、重构、测试
、投产、运维,其中的重构可以对原有的设计和代码进行修改,运维尽量减少对原有代码的修改,保持历史代码的纯洁性,提高系统的稳定性。
注意
开闭原则对扩展开放,对修改关闭,并不意味着不做任何修改,低层模块的变更,必然要有高层模块进行耦合,否则就是一个孤立无意义的代码片段。
为什么采用开闭原则
任何开发语言都会提及开闭原则(甚至可以延伸到生活中)。
开闭原则是最基础的一个原则,前五个章节介绍的都是开闭原则的具体形态,也就是说前五个原则就是指导设计的工具和方法,而开闭原则才是其精神领袖。依照java语言的称谓,开闭原则是抽象类,其他五大原则是具体的实现类。
开闭原则对测试的影响
逻辑的正确性、苛刻的条件(高压力、异常、错误)、变化的提出、扩展来实现变化。
开闭原则可以提高复用性
在面向对象的设计中,所有的逻辑都是从原则逻辑组合而来的,而不是在一个类中独立实现一个业务逻辑。只有这样代码才可以复用,粒度越小,被复用的可能性就越大。
开闭原则可以提高可维护性
扩展一个类而不是修改一个类。
面向对象开发的要求
在设计之初考虑到所有可能变化的因素,然后留下接口,等待”可能”转变为”现实”。
如何使用开闭原则
要慢慢去体会和掌握
抽象约束
描述
抽象是对一组事物的通用描述,没有具体的实现,也就表示它可以有非常多的可能性,可以跟随需求的变化而变化。因此,通过接口或抽象类可以约束一组可能变化的行为,并且能够实现对扩展开放。要实现对扩展开放,首要的前提条件就是抽象约束。
其包含的三层含义:
第一:通过接口或抽象类约束扩展,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的public方法。(面向的是什么领域,也就是它的范围。)
第二:参数类型、引用对象尽量使用接口或者抽象类,而不是实现类。
第三:抽象层尽量保持稳定,一旦确定即不允许修改。
案例
import java.text.NumberFormat;
import java.util.ArrayList;
public class BookStore3 {
private final static ArrayList<IBook> bookList = new ArrayList<>();
static{
bookList.add(new NovelBook("天龙八部",3200,"金庸"));
bookList.add(new NovelBook("巴黎圣母院",5600,"雨果"));
bookList.add(new NovelBook("热爱生命",8888,"杰克伦敦"));
bookList.add(new ComputerBook("thinking in java",66666,"Bruce Eckel","program language"));
}
public static void main(String[] args) {
NumberFormat numberFormat = NumberFormat.getCurrencyInstance();
numberFormat.setMaximumFractionDigits(2);
for (IBook book:bookList){
System.out.println("name:" + book.getName()
+ "\tauthor:" + book.getAuthor()
+ "\tprice:" + numberFormat.format(book.getPrice() / 100.0));
}
}
}
interface IBook{
String getName();
int getPrice();
String getAuthor();
}
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;
}
}
interface IComputerBook extends IBook{
String getScope();
}
class ComputerBook implements IComputerBook{
private String name;
private int price;
private String author;
private String scope;
public ComputerBook(String name, int price, String author, String scope) {
this.name = name;
this.price = price;
this.author = author;
this.scope = scope;
}
@Override
public String getName() {
return name;
}
@Override
public String getScope() {
return scope;
}
@Override
public int getPrice() {
return price;
}
@Override
public String getAuthor() {
return author;
}
}
name:天龙八部 author:金庸 price:¥32.00
name:巴黎圣母院 author:雨果 price:¥56.00
name:热爱生命 author:杰克伦敦 price:¥88.88
name:thinking in java author:Bruce Eckel price:¥666.66
元数据(metadata)控制模块行为
描述
尽量使用元数据来控制程序的行为,减少重复开发。什么是元数据?用来描述环境和数据的数据,通俗地说就是配置参数,参数可以从文件中获得,也可以从数据库中获得。
案例
案例1
元数据控制模块行为案例1:登录IP的访问限制、是否需要数据库密码
案例2
元数据控制模块行为达到极致的案例2-控制反转(使用最多的是Spring容器)
<bean id="father" class="xx.xx.xx.Father" />
<bean id="xx" class="xx.xx.xx.XX">
<property name="biz" ref="father"></property>
</bean>
<bean id="son" class="yy.yy.yy.Son" />
<bean id="yy" class="yy.yy.yy.YY">
<property name="biz" ref="son"></property>
</bean>
制定项目章程
对项目来说,约定优于配置。团队需要较长时间的磨合,当团队熟悉此规则后,效率更高,扩展性也不会减少。
封装变化
对变化的封装包含两层含义:
第一,将相同的变化封装到一个接口或抽象类中;
第二,第二,将不同的变化封装到不同的接口或抽象类中,不应该有两个不同的变化出现在同一个接口或抽象类中。封装变化,也就是受保护的变化,找出预计有变化或不稳定的点,我们为这些变化点创建稳定的接口,从而封装可能发生的变化。
总结
遵循设计模式前面5大原则,以及使用23种设计模式的目的就是遵循开闭原则。
如果前面5项原则遵守的不好,则说明开闭原则遵守的不好。
开闭原则无非就是想表达这样一层意思:用抽象构建框架,用实现扩展细节。因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。
再回想一下前面说的5项原则,恰恰是告诉我们用抽象构建框架,用实现扩展细节的注意事项而已:
单一职责原则告诉我们实现类要职责单一;
里氏替换原则告诉我们不要破坏继承体系;
依赖倒置原则告诉我们要面向接口编程;
接口隔离原则告诉我们在设计接口的时候要精简单一;
迪米特法则告诉我们要降低耦合。
而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。
对这六个原则的遵守并不是是和否的问题,而是多和少(遵守程度的多少)的问题。