SOLID 原则是五个面向对象程序设计基本原则的首字母简写,应用 SOLID 原则可以帮助我们开发出更容易维护、更灵活的软件。从而,在软件规模逐渐变大的时候,降低软件的复杂度,节省开发维护成本。
SOLID 原则由如下五个原则组成:
- 单一职责原则(Single Responsibility)
- 开闭原则(Open/Closed 原则)
- 里氏替换原则(Liskov Substitution)
- 接口隔离原则(Interface Segregation)
- 依赖倒置原则(Dependency Inversion)
看名字有些原则可能不太好理解,下面我们通过一些代码示例来逐一说明。
单一职责原则(Single Responsibility Principle)
通过名字就可以知道,这个原则的含义是一个类应该只有一种职责。换句话说,我们只能有一个原因来改变这个类。
应用了单一职责的类,有如下几个优点:
- 易测试。只有单一职责的类需要的测试 case 非常少,非常容易测试。
- 低耦合。一个类的功能越少,需要的外部依赖越少。
- 易管理。功能单一的类比功能复杂的类更容易进行组织。
举个例子,我们有一个 Book 类:
class Book {
private name: string;
private author: string;
private text: string;
}
2
3
4
5
在这个类中,我们保存了书名、作者和书的文本三个字段。
现在我们添加一些操作文本的函数。
class Book {
private name: string;
private author: string;
private text: string;
replaceWordInText(word: string): string {
return text.replaceAll(word, text);
}
isWordInText(word: string): boolean {
return text.indexOf(word) !== -1;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
现在假如说我们要把书的内容输出到控制台,我们可以给 Book 类加一个 print 方法:
// ....
printTextToConsole() {
// 输出内容函数实现
}
2
3
4
这个时候,新添加的方法就破坏了单一职责原则。
我们应该再定义一个类,BookPrinter 来专门输出书的内容:
class BookPrinter {
printTextToConsole(text: string) {
//输出到控制台
}
printTextToAnotherMedium(text: string) {
// 输出到其他媒体类型
}
}
2
3
4
5
6
7
8
9
通过 BookPrinter 类,我们不断实现了输出书的内容到控制台的功能,我们还可以支持将书的内容输出到其他媒体类型。不管是输出到邮件、日志还是什么其他地方,我们都有一个单独的类来处理这个问题。
开闭原则(Open/Closed Principle)
开闭原则可以描述为”对扩展开放,对修改关闭“。也就是说,一个类只应该对扩展开放,对修改应该是关闭的。
我们通过一个例子来说明。假设我们有一个生产电脑的类:
class MacOSComputer {}
class WindowsComputer {}
class ComputerFactory {
computerTypes = ["macos", "windows"];
createComputer(type) {
switch (type) {
case "macos":
return new MacOSComputer();
case "windows":
return new WindowsComputer();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
现在,假如我们需要创建一个 linux 系统的电脑,我们应该怎么办呢?最直接的办法是修改 ComputerFactory 这个类,来支持新电脑的创建。但是这么做我们可能会影响到其他两种类型电脑的创建。结合开闭原则,我们可以做一些改造。
abstract class ComputerFactory {
createComputer() {}
}
class MacOSFactory extends ComputerFactory {
createComputer() {
return new MacOSComputer();
}
}
class WindowsFactory extends ComputerFactory {
createComputer() {
return new WindowsComputer();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这个时候,如果我们新增一个 Linux 类型的电脑,就非常的方便。
class LinuxComputer {}
class LinuxFactory extends ComputerFactory {
createComputer() {
return new LinuxComputer();
}
}
2
3
4
5
6
7
里氏替换原则(Liskov Substitution Principle)
里氏替换原则则简单的说,就是如果类 A 是类 B 的子类,那么我们可以在系统内将 B 替换成 A,且不改变软件行为。
我们还是通过例子来说明。
interface Car {
turnOnEngine(): void;
accelerate(): void;
}
2
3
4
我们定义了一个 Car 接口,提供了一些函数需要实现类来实现。
class MotorCar implements Car {
private engine: Engine;
turnOnEngine() {
this.engine.on();
}
accelerate() {
this.engine.powerOn(1000);
}
}
2
3
4
5
6
7
8
9
10
11
假如现在需要实现一个电动车:
class ElectricCar implements Car {
turnOnEngine() {
throw new Error("No engine");
}
accelerate() {
//
}
}
2
3
4
5
6
7
8
9
这时候,如果我们将新实现的电动车放到系统中的话,就会改变系统的行为,导致出错。
我们需要重新定义一个没有引擎的 Car 接口:
interface Car {
accelerate(): void;
}
2
3
接口隔离原则(Interface Segregation Principle)
接口隔离原则要求我们将大的接口拆分成小的接口,这样可以保证实现类能聚焦在它需要关心的接口上面。
我们以动物饲养员为例。
interface BearKeeper {
washTheBear(): void;
feedTheBear(): void;
petTheBear(): void;
}
2
3
4
5
假如现在有一个熊饲养员只关心 washTheBear
和 petTheBear
,那么在现在的接口设计中,还需要被迫实现 petTheBear
。
我们可以把这个大的接口进行拆分。
interface BearCleaner {
washTheBear(): void;
}
interface BearFeeder {
feedTheBear(): void;
}
interface BearPetter {
petTheBear(): void;
}
2
3
4
5
6
7
8
9
10
11
那么这时候不同的熊饲养员就可以自由的实现关心的接口了。
class BearCarer implements BearCleaner, BearFeeder {
washTheBear() {}
feedTheBear() {}
}
2
3
4
在前面单一职责的例子中,我们还可以将 BookPrinter 进行拆分。
依赖倒置原则(Dependency Inversion Principle)
依赖倒置原则主要用于模块间的解耦。通常说,应用了依赖倒置原则的设计,高层模块将不会再直接依赖低层模块,而是依赖于抽象。
举个例子。我们有一个 Computer 类:
class Computer {
private keyboard: StandardKeyboard;
constructor() {
this.keyboard = new StandardKeyboard();
}
}
2
3
4
5
6
7
在 Computer 的构造函数中,我们将 StandardKeyboard 类和 Computer 类耦合在了一起。现在加入我们要新增一种键盘,那 Computer 的构造函数将会难以处理。
使用依赖倒置的方法,我们可以抽象出一个 Keyboard 接口来进行解耦:
interface Keyboard {}
class Computer {
private keyboard: Keyboard;
constructor(keyboard: Keyboard) {
this.keyboard = keyboard;
}
}
2
3
4
5
6
7
8
9
这时候重新实现 StandardKeyboard 类:
class StandardKeyboard implements Keyboard {}
重构后的代码,实现了 StandardKeyboard 和 Computer 的解耦,我们可以根据需要自由实现 Keyboard 接口。
小结
SOLID 原则是面向对象程序设计中非常经典的设计原则,同时也非常简单。理解和掌握这些原则,不仅对我们日常的开发维护工作帮助很大,也能提高自身的软件设计能力。
参考文献
关注微信公众号,获取最新推送~
加微信,深入交流~