设计原则
概念
软件设计模式(Software Design Pattern),又称设计模式,它描述了在软件设计过程中的一些不断重复发生的问题以及该问题的解决方案。也就是说,他是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。其目的是为了提高代码的可重用性、代码的可读性和代码的可靠性。
设计模式的六大设计原则(前两个详细介绍,后面的大同小异):
- 开闭原则
- 单一职责原则
- 里氏代换原则
- 依赖倒置原则
- 接口隔离原则
- 迪米特原则
1.开闭原则
定义:软件实体应当对扩展开放,对修改关闭。
案例:
/**
* @Classname IBook
* @Description 书籍接口
* @Date 2020/10/10 21:58
* @Created by ccc-j
* @email ccc-ju@outlook.com
*/
public interface IBook {
String getName();
int getPrice();
String getAuthor();
}
/**
* @Classname NoveBooks
* @Description 书籍
* @Date 2020/10/10 21:59
* @Created by ccc-j
* @email ccc-ju@outlook.com
*/
public class NoveBooks implements IBook {
private String name;
private int price;
private String author;
public NoveBooks() {
}
public NoveBooks(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;
}
}
/**
* @Classname BookStore
* @Description 书店
* @Date 2020/10/10 22:01
* @Created by ccc-j
* @email ccc-ju@outlook.com
*/
public class BookStore {
private static ArrayList<IBook> bookList = new ArrayList<>();
static {
bookList.add(new NoveBook("红楼梦", 9900, "曹雪芹"));
bookList.add(new NoveBook("侠客行", 8900, "金庸"));
bookList.add(new NoveBook("原则", 6900, "瑞达利欧"));
bookList.add(new NoveBook("海贼王1", 4900, "尾田荣一郎"));
}
public static void main(String[] args) {
System.out.println("卖书记录如下-------------------------");
for (IBook book : bookList){
System.out.println("书籍名称:" + book.getName() + "\t\t作者:" + book.getAuthor() + "\t\t价格:¥" + book.getPrice() / 100 + "元");
}
}
}
此时有一个需求,需要对70块钱以上的书进行打9折出售,70以下的打8折出售,在不修改源代码的情况下,增加一个OffNoveBook子类,重写getPrice()方法
/**
* @Classname OffNoveBook
* @Description 扩展的子类
* @Date 2020/10/10 22:11
* @Created by ccc-j
* @email ccc-ju@outlook.com
*/
public class OffNoveBook extends NoveBooks {
public OffNoveBook() {
}
public OffNoveBook(String name, int price, String author) {
super(name, price, author);
}
@Override
public int getPrice() {
int sellPrice = super.getPrice();
int offPrice = 0;
if(sellPrice > 7000){
offPrice = sellPrice * 90 / 100;
}else{
offPrice = sellPrice * 80 / 100;
}
return offPrice;
}
}
/**
* @Classname BookStore
* @Description 书店卖书
* @Date 2020/10/10 22:01
* @Created by ccc-j
* @email ccc-ju@outlook.com
*/
public class BookStore {
private static ArrayList<IBook> bookList = new ArrayList<>();
static {
bookList.add(new OffNoveBook("红楼梦", 9900, "曹雪芹"));
bookList.add(new OffNoveBook("侠客行", 8900, "金庸"));
bookList.add(new OffNoveBook("原则", 6900, "瑞达利欧"));
bookList.add(new OffNoveBook("海贼王1", 4900, "尾田荣一郎"));
}
public static void main(String[] args) {
System.out.println("卖书记录如下-------------------------");
for (IBook book : bookList){
System.out.println("书籍名称:" + book.getName() + "\t\t作者:" + book.getAuthor() + "\t\t价格:¥" + book.getPrice() / 100 + "元");
}
}
}
开闭原则的作用:开闭原则是面向对象程序设计的终极目标,它使软件实体拥有一定的适应性和灵活性的同时具备稳定和延续性,作用如下
- 对软件测试的影响:软件遵守开闭原则的话,软件测试时,只需要对扩展的代码进行测试就可以了,因为原有的测试代码仍然能够正常运行。
- 可以提高代码的复用性:粒度越小,被复用的可能性越大;在面向对象的程序设计中,根据原子和抽象编程可以提高代码的可复用性。
- 可以提高软件的可维护性:遵守开闭原则的软件,其稳定性高和延续性强,从而易于扩展和维护
2.单一职责原则(Single Responsibility principle, SRP)
这是一个备受争议的原则,对于职责的界限划分是一个比较模糊的概念,而且因人而异。
定义:单一职责原则又称单一功能原则。职责是指类的变化的原因,单一职责原则规定一个类应该有且仅有一个引起它变化的原因,否则应该被拆分(There should never be more than one reason for a class to change)。
该原则提出对象不应该承担太多职责,如果一个对下你个承担了太多职责,至少存在两个缺点:
- 一个职责的变化可能会削弱或者抑制这个类实现其它职责的能力;
- 当客户端需要该对象的某一个职责时,不得不将其他不需要的职责全部包含进来,从而造成冗余代码或代码的浪费。
案例
以下是一个手机的类,手机拥有:打电话、聊天、挂电话,分别是两个功能:打电话和挂电话分别对应的是协议的管理,聊天是数据内容的传送。但是接口里显然是干了两件事情,违反了单一职责的原则
/**
* @Classname IPhone
* @Description TODO
* @Date 2020/10/10 22:30
* @Created by ccc-j
* @email ccc-ju@outlook.com
*/
public interface IPhone {
void dial(String phoneNumber);
void chat(Object obj);
void hangup();
}
单一职责的优点:
- 降低类的复杂度,一个类或者一个接口只负责一个职责,其逻辑肯定要比负责多项职责简单;
- 提高类的可读性,复杂性降低,自然其可读性会提高;
- 提高系统的可维护性,可读性提高,那自然更容易维护;
- 变更引起的风险更低。变更是必然的,如果单一职责原则遵守好,当变更一个功能时,可以显著地降低对其它功能的影响。
单一职责原则是最简单又是最难运用的原则,需要设计人员发现类的不同职责并将其分离,再封装到不同的类或模块中。而发现类的多重职责需要设计人员具有较强的分析设计能力和相关重构经验。
但是:原则是死的,人是活的。所以有些时候我们可以为了效率,牺牲一定的原则性。
3.里氏代换原则(Liskov Substitution Principle, LSP)
简单理解为:子类可以替换父类
定义:
- 如果每一个类型S的对象O1,都有一个类型T的对象O2,在定义的所有程序P中将所有的对象O2都替换为O1,而程序P的行为没有发生变化,那么S是T的子类。
- 所有引用基类的地方必须能透明地使用其子类对象。
作用:
- 里氏替换原则是实现开闭原则的重要方式之一
- 它克服了继承中重写父类造成的可复用性变差的缺点
- 它是动作正确性的保证。即类的扩展不会给已有的系统引入新的错误,降低代码出错的可能性。
里氏替换原则通俗来讲是:子类可以扩展父类的功能,但不能改变父类原有的功能,也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
4.依赖倒置原则(Dependence Inversion Principle, DIP)
定义:
- 高层模块不应该依赖低层模块,两者都应该依赖其抽象
- 抽象不应该依赖细节
- 细节应该依赖抽象
核心思想:面向接口编程,不要面向实现编程
实现方法:
- 每个类尽量提供接口或抽象类,或者两者都具备
- 变量的声明类型尽量是接口或是抽象类
- 任何类都不应该从具体类派生
- 尽量不要复写基类的方法
- 使用继承时结合里氏代换原则
5.接口隔离原则(Interface Segregation Principle, ISP)
定义:要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
接口隔离原则和单一职责原则都是为了提高类的内聚性、降低它们的耦合性,体现了封装的思想,但是两者不同的:
- 单一职责原则侧重的是职责,而接口隔离原则注重的是对接口依赖的隔离
- 单一职责原则主要是约束类,它针对的是程序中的实现和细节;接口隔离原则主要约束接口,主要针对抽象和程序整体框架的构建
6.迪米特原则(Law of Demeter, LoD)
定义:要求一个对象应该对其他对象有最少的了解
迪米特法则还是在讲如何减少耦合的问题,类之间的耦合越弱,越有利于复用,一个处在弱耦合的类被修改,不会对有关系的类造成波及,也就是说,信息的隐藏促进了软件的复用。