六大设计原则,开闭原则

时间:2021-09-03 15:24:49

开闭原则定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
这句话的意思是说,一个软件实体应该通过扩展来实现变化,而不是通过写该已有的代码来实现变化。同样的,我们在现实开发中,最乐意做的事情就是扩展一个类,而不是修改一个类,不管原有的代码写的多么优秀还是多么糟糕,让开发读懂原有的代码,而后再修改是一件非常痛苦的事情。

我们来看这样一个例子,T先生开了一家书店,专门卖小说类的书籍,然后他写下了这样的一些代码。
书籍接口

public interface IBook {
//获得书籍名称
public String getName();
//获得书籍价格
public int getPrice();
//获得书籍作者
public String getAuthor();
}

小说类

public class NovelBook {
//名称
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;
}

public String getName() {
return name;
}

public int getPrice() {
return price;
}

public String getAuthor() {
return author;
}
}

书店售书类

public class BookStore {
private final static List<IBook> bookList = new ArrayList<IBook>();
//static静态模块初始化数据,实际项目中一般由持久层完成
static {
bookList.add(new NovelBook("活着", 2000, "余华"));
bookList.add(new NovelBook("许三观卖血记", 2000, "余华"));
bookList.add(new NovelBook("一个人的记忆", 3000, "史铁生"));
bookList.add(new NovelBook("平凡的世界", 3500, "路遥"));
}

public static void main(String[] args) {
NumberFormat format = NumberFormat.getCurrencyInstance();
format.setMaximumFractionDigits(2);
System.out.println("--------书店卖出去的书籍记录如下--------");
for (IBook book:
bookList) {
System.out.println("书籍名称: " + book.getName() + "\t书籍作者: " +
book.getAuthor() + "\t书籍价格" + format.format(book.getPrice()/100.0) + "元");
}
}
}

但是T先生小说书卖的并不畅销,于是他想出了打折促销,那便是意味着代码变化要来了。那么怎么修改,我们代码的所需承受的代价最小,这是我们要思考的。
1.T先生首先想到了修改IBook接口,往里面加上一个getOffPrice()的促销方法,但是不想不知道,一想吓一跳,只要动了接口,就要动全部的实现接口的方法,哇,真恐怖。方案否定。
2.然后T先生想到我可以修改NovelBook,修改修改getPrice()方法,加上打折的逻辑,这样就只需要修改这样一个地方啦。但是这样的设计在实际开发中是不提倡的。原因有两点,第一我们的方法中没有了获得书籍原价的方法,第二点,我们在开发中不仅仅只有开发人员,还有测试人员,这样的修改,会导致测试人员需要修改他写的对这个类的测试,可能会改的面目全非,这是测试人员所不愿意看到的。
3.终于,T先生想到了开闭原则,“一个软件实体应该对扩展开放,对修改关闭”,其实反过头来看前面两个方案,他们都是修改了原来的代码。那应该怎么扩展呢?来看一下T先生写的这个类。

public class OffNovelBook extends NovelBook {
public OffNovelBook(String name, int price, String author) {
super(name, price, author);
}

@Override
public int getPrice() {
return super.getPrice() * 80 / 100;
}
}

T先生继承(扩展)了NovelBook类,并复写了getPrice()方法。
然后BookStore类修改为

public class BookStore {
private final static List<IBook> bookList = new ArrayList<IBook>();
//static静态模块初始化数据,实际项目中一般由持久层完成
static {
//修改的地方
bookList.add(new OffNovelBook("活着", 2000, "余华"));
bookList.add(new OffNovelBook("许三观卖血记", 2000, "余华"));
bookList.add(new OffNovelBook("一个人的记忆", 3000, "史铁生"));
bookList.add(new OffNovelBook("平凡的世界", 4500, "路遥"));
}

public static void main(String[] args) {
NumberFormat format = NumberFormat.getCurrencyInstance();
format.setMaximumFractionDigits(2);
System.out.println("--------书店卖出去的书籍记录如下--------");
for (IBook book:
bookList) {
System.out.println("书籍名称: " + book.getName() + "\t书籍作者: " +
book.getAuthor() + "\t书籍价格" + format.format(book.getPrice()/100.0) + "元");
}
}
}

开闭原则也提出了一个重要的约束,叫抽象约束。
1. 通过接口或抽象类约束扩展,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的Public方法;
2. 参数类型、引用对象尽量使用接口或者抽象类,而不是实现类;
3. 抽象层尽量保持稳定,一旦确定就不允许改变;

T先生最近拓展了业务,他开始卖计算机类书籍。计算机类书籍除了书名,价格,作者之外还应该有个字段来分类是数据库类书籍,还是编程语言类书籍等等。T先生熟练掌握了开闭原则,不慌不忙的扩展了这些代码
T先生首先扩展了IBook接口

public interface IcomputerBook extends IBook {
//计算机书籍是有一个范围
public String getScope();
}

然后在实现这个IComputerBook接口

public 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 getScope() {

return scope;
}

@Override
public String getAuthor() {
return author;
}

@Override
public int getPrice() {
return price;
}

@Override
public String getName() {
return name;
}
}

最后我们在BookStore类中只需要修改一个地方就好

public class BookStore {
private final static List<IBook> bookList = new ArrayList<IBook>();
//static静态模块初始化数据,实际项目中一般由持久层完成
static {
bookList.add(new OffNovelBook("活着", 2000, "余华"));
bookList.add(new OffNovelBook("许三观卖血记", 2000, "余华"));
bookList.add(new OffNovelBook("一个人的记忆", 3000, "史铁生"));
bookList.add(new OffNovelBook("平凡的世界", 4500, "路遥"));
//修改的地方
bookList.add(new ComputerBook("第一行代码", 7500, "郭霖", "编程语言"));
}

public static void main(String[] args) {
NumberFormat format = NumberFormat.getCurrencyInstance();
format.setMaximumFractionDigits(2);
System.out.println("--------书店卖出去的书籍记录如下--------");
for (IBook book:
bookList) {
System.out.println("书籍名称: " + book.getName() + "\t书籍作者: " +
book.getAuthor() + "\t书籍价格" + format.format(book.getPrice()/100.0) + "元");
}
}
}