设计模式
六大原则
单一职责原则
Single Responsibility Principle
定义
不要存在多于一个导致类变更的原因,即一个类只负责一项职责。
描述
在最初新建一个类的时候,一般都能够保持「单一职责原则」,但是一个类建立完成之后一般不是一成不变的,往往随着应用规模的提升,一个类的功能可能会慢慢地增加,增加到一定程度之后,可能就不符合「单一职责原则」了,比如有一个例子:
最初,我们需要构建一个类,来表示一个动物需要吃的东西,如一个肉食动物需要吃肉:
class Animal {
private String animalName;
public Animal(String animalName) {
this.animalName = animalName;
}
public String food() {
return animalName + " eat meat";
}
}
我们完全可以这样定义,当我们构建了一个新的 Animal 对象的时候,如果是一个鳄鱼,那么调用food()方法就会返回鳄鱼 eat meat,但是并不是所有的动物都是肉食动物,很显然这个food()方法不能满足全部的需求,那么就要对这个类进行整改,整改的方法也是多种多样的,比如以下几种:
-
在原本的方法内部增加一部分判断
class Animal { private String animalName; public Animal(String animalName) { this.animalName = animalName; } public String food() { // 需要另外定义两个方法来判断某个动物是肉食动物还是素食动物 // isCarnivorous() 是否为肉食动物 // isVegetarian() 是否为素食动物 if (isCarnivorous(animalName)) { return animalName + "eat meat"; }else if (isVegetarian(animalName)) { return animalName + "eat vegetables"; } } } -
将 Animal 抽象出来,然后定义不同的类完善这个类。
abstract class Animal { protected String animalName; public Animal(String animalName) { this.animalName = animalName; } public abstract String food(); } class CarnivorousAnimal extends Animal { public String food() { return animalName + ' eat meat'; } } class VegetarianAnimal extends Animal { public String food() { return animalName + " ear vegetables"; } }如上,以上两种方法,都解决了上述的问题,使得对于食肉动物与素食动物,都能返回其正确的食物类型,但是对于第一种方法来说,我们只是在原先的类的基础上进行了一定的修改,并修改的方法内部的代码,很显然这并不符合单一职责原则,而且在将来如果需要添加其他种类的动物的时候,比如说杂食动物,还需要再次对
food()方法进行修改,这样也很容易导致未来的某一个时刻,出现了肉食动物吃素的情况。对于第二种方法来说,我们将 Animal 这个类抽象化,只保留了它基本的功能,但对于food()这个方法来说,它并没有给出具体的实现,而我们要使用这个类的时候,需要再构建一个类,比如 VegetarianAnimal ,素食动物类,实现的food()方法就可以不需要多余的判断直接返回eat vegetables了,而且当应用规模扩展,需要使用到杂食动物的时候,只需要新建一个 OmnivoreAnimal 继承 Animal 类并实现根据自己的情况实现food()这个方法即可,不需要对原有的代码作任何的修改,以后再增加更多也是这样的一个逻辑,开发者只需要对当前新建的内容负责,而不需要考虑新建的内容对原有内容是造成影响带来的风险。
作用
- 降低类的复杂度
- 提高类的可读性
- 变更导致的风险降低
里氏替换原则
Liskov Substitution Principle
定义
对于程序中任何一个使用 P 的对象的地方,都可以使用 C 的对象来替换,则 C 是 P 的子类,即所有引用基类的地方必须能透明地引用其子类的对象,即子类可以扩展父类的功能,但不能改变父类原有的功能。
描述
继承是面对对象语言的一个大优势,能够使类与类之间的关系丰富多样,可以随时在不改变原有的一个类的基础上为其扩展不同的功能。但是这样也有一些危险,比如当子类重写了父类的某项功能之后,在使用中子类仍然可以无缝替换父类,但是子类对父类的某项功能的改变必定会增加程序出错的风险,所以就有了「里氏替换原则」。「里氏替换原则」是麻省理工学院的一位女士「Barbara Liskov」提出的,专门用来限制这个问题。举个例子,就拿上面的例子来说吧,比如业务规模又增加了,单纯的食肉动物也不能表达客户需要,还要在这个基础上增加一个水、陆、空的食肉动物,这样的话就可以新建一个类:
class LandCarnivorousAnimal extends CarnivorousAnimal {
public String food() {
return "meat";
}
public String field() {
return "land";
}
}
可以看到,LandCarnivorousAnimal 对其父类 CarnivorousAnimal 的food() 方法进行了重写,将原本应该返回animalName + "eat meat"的返回结果变为了"meat",但是当使用的时候,很有可能会忽略了这个方法被重写的事实,使用者使用这个子类依旧按照使用父类的方法使用,就会不可避免地导致程序出错了。
作用
- 降低程序出错的概率
- 保证父类的功能不会被子类覆盖
依赖倒置原则
Denpendence Inversion Principle
定义
高层模块不依赖低层模块,二者都依赖其抽象。抽象不依赖细节,细节依赖抽象。
描述
依赖倒置原则的基本思想就是,降低类之间的耦合程度,将一个类 A 依赖的实体类 B 、C 等,尽可能地转变为依赖一个接口 I ,然后让 B 、 C 实现这个接口 I ,这样类 A 就与 B 、C 没有依赖关系了,这样如果要对这个逻辑进行修改,就只需要新建一个类实现接口 I ,看实例:
class Reader {
public String readBook(Book book) {
return "read " + book.getContent();
}
public String readPaper(Paper paper) {
return "read " + paper.getContent();
}
}
class Book {
public String getContent() {
return "this is a book";
}
}
class Paper {
public String getContent() {
return "this is a paper";
}
}
如上,这是一个实现了阅读者阅读读物的逻辑,可以看到 Reader 类直接依赖了 Book 类和 Paper 类,在这里 Reader 就是那个高层的负责业务逻辑的类,很显然 Reader 依赖的类过多了,并且如果需要增加新的读物,还需要修改 Reader 类,使其依赖更多的类才能完成这个逻辑。如果使用依赖倒置原则,使 Reader 依赖于一个抽象 IReading ,然后使 Book 和 Paper 实现 IReading ,就可以 Reader 中依赖过多的问题:
class Reader {
public String read(IReading reading) {
return "read " + reading.getContent();
}
}
interface IReading {
String getCotnent():
}
class Book implements IReading {
public String getContent() {
return "this is a book";
}
}
class Paper implements IReading {
public String getContent() {
return "this is a paper";
}
}
使用这种方法就可以减少 Reader 的依赖,使得 Reader 与 Book 和 Paper 没有直接依赖关系,它们通过接口 IReading 间接联系到了一起。
作用
- 降低程序的耦合度
- 提高程序的编译效率
接口隔离原则
Interface Segregation Principle
定义
一个类对另一个类的依赖应该建立在最小的接口上,即一个客户端不应该依赖它不需要的接口。
描述
接口隔离,即对于一个实现某个接口 I 的类 A 来说,I 中定义的需要实现的方法应该都是 A 中能够用到的,不然就会出现 A 实现了 I 中一个它不会用到的方法,这样显然会造成 A 的负担,也会增加系统的安全性问题。 所以「接口隔离原则」的目标就是,将一个功能比较复杂的接口细化成完成某个单一职责的接口,但是这会有一个问题,就是在细化的时候,要把握好这个细化的度,不能使得接口过多,这样同样会增加系统的负担,还会增加开发人员的负担。
同时,「接口隔离原则」与「单一职责原则」的区别在于,「单一职责原则」注重的是对一个类的职责的划分,针对的是程序中实现和细节,而「接口隔离原则」注重的是对接口依赖的隔离,针对的是抽象和程序的整体框架。
作用
- 提高类的内聚
- 提高程序安全性
迪米特法则
Law of Demeter
定义
一个对象应对其他对象保持最少的了解,即耦合度尽可能低。
描述
总所周知,类与类之间的联系越紧密,程序的耦合性就越高,类与类之间的影响也就越大,一个类的改变,导致其他类出错的概率也就越高。所以「迪米特法则」的目标就是,尽量降低类与类之间的耦合。
「迪米特法则」的另一个定义为:「只与直接的朋友通信」。所谓「直接的朋友」,就是指在当前类中以成员变量、方法参数、方法返回值出现的类。「只与直接朋友通信」就是指,除了上面的这些「直接的朋友」以外,当前类中不应该出现以「局部变量」的形式出现的其他的类,如果我们确实需要相应的功能,应该通过「直接的朋友」调用它的「直接的朋友」的方式,来使用相关的功能。一个简单的例子,有一个公司,它有自己的职员,还有自己领导的分公司,同时分公司也有自己的职员。
class Employee {
private String id;
public void setId(String id) {
this.id = id;
}
public String getId() {
return this.id;
}
}
class SubEmployee {
private String id;
public void setId(String id) {
this.id = id;
}
public String getId() {
return this.id;
}
}
class SubCompany {
private List<SubEmployee> employeeList;
public void getEmployee() {
return this.employeeList;
}
}
class Company {
private List<Employee> employeeList;
public void printEmployee(SubCompany company) {
for (SubEmployee e : company.getEmployee()) {
System.out.println(e.getId());
}
for (Employee e : employeeList) {
System.out.println(e.getId());
}
}
}
对于上面的类,可以看到,如果总公司需要打印包括分公司在内的所有职员的 Id ,就需要依赖类 SubEmployee ,但是类 SubEmployee 并不是类 Company 「直接的朋友」,所以这就违反了「迪米特法则」,可以对上面的代码进行一定的修改:
class SubCompany {
private List<SubEmployee> employeeList;
public void getEmployee() {
return this.employeeList;
}
public void printEmployee() {
for (SubEmployee e : employeeList) {
System.out.println(e.getId());
}
}
}
class Company {
private List<Employee> employeeList;
public void printEmployee(SubCompany company) {
company.printEmployee();
for (Employee e : employeeList) {
System.out.println(e.getId());
}
}
}
将 SubCompany 与 Company 进行一定的修改,就可以使之符合「迪米特法则」,但是可以看到,如果 SubCompany 本身不需要使用printEmployee()方法,而是仅仅为了 Company 新建了一个方法显然有些浪费,同时如果大量出现类似的情况,肯定会大幅增加程序中多余代码的量。所以使用「迪米特法则」的时候,应该再三考量,得到一个最有效的解决方案。
作用
- 降低类之间的耦合度
开闭原则
Open Close Principle
定义
一个软件实体,如类、模块和方法,应该对扩展开放,对修改封闭。
描述
在一个软件的生命周期中,需求总会在不停地改变,所以如果在修改功能的过程中,不加限制地修改原有的代码,肯定会增加原有的代码也变得不可用的概率,到时候只能对所有代码进行重构,所以「开放封闭原则」规定,只允许扩展原有代码的功能,而不对其进行修改。
参考
http://www.uml.org.cn/sjms/201211023.asp
http://wiki.jikexueyuan.com/project/java-design-pattern-principle/