- 单一职责原则(Single Responsibility Principle)
- 开闭原则(Open Closed Principle)
- 里式替换原则(Liskov substitution principle)
- 接口隔离原则(Interface segregation principle)
依赖反转原则(Dependency inversion principle)
单一职责原则(SRP)
“A class or module should have a single responsibility”
如何判断类的职责是否足够单一:类中的代码行数、函数或者属性过多
- 类依赖的其他类过多,或者依赖类的其他类过多
- 私有方法过多
- 比较难给类起一个合适的名字
- 类中大量的方法都是集中操作类中的某几个属性
对扩展开放、修改关闭(OCP)
“software entities (modules, classes, functions, etc.) should be open for extension, but closed for modification”
the core idea of the above principle is that we should be able to add new functionalities without changing the existing code.开闭原则讲的就是代码的扩展性问题
class Rectangle {
public width: number;
public height: number;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
}
class Circle {
public radius: number;
constructor(radius: number) {
this.radius = radius;
}
}
function calculateAreasOfMultipleShapes(
shapes: Array<Rectangle | Circle>
) {
return shapes.reduce(
(calculatedArea, shape) => {
if (shape instanceof Rectangle) {
return calculatedArea + shape.width * shape.height;
}
if (shape instanceof Circle) {
return calculatedArea + shape.radius * Math.PI;
}
},
0
);
}
符合 开闭原则 的代码
interface Shape {
getArea(): number;
}
class Rectangle implements Shape {
public width: number;
public height: number;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
public getArea() {
return this.width * this.height;
}
}
class Circle implements Shape {
public radius: number;
constructor(radius: number) {
this.radius = radius;
}
public getArea() {
return this.radius * Math.PI;
}
}
function calculateAreasOfMultipleShapes(
shapes: Shape[]
) {
return shapes.reduce(
(calculatedArea, shape) => {
return calculatedArea + shape.getArea();
},
0
);
}
里式替换(LSP)
Replacing an instance of a class with its child class should not produce any negative side effects
- 子类可以替换父类的位置,并且不影响程序
- 父类有的功能子类都有,但是子类可以在子类的基础上,添加功能
设计原则:
- Methods of a subclass that override methods of a base class must have exactly the same number of arguments
- Each argument of the override method must be the same type as the method of the base class
- The return type of the overridden method must be the same as the method of the base class
The types of exceptions thrown from the overridden method must be the same as the method of the base class
接口隔离原则(ISP)
把接口理解为一组 API 接口集合
- 如果部分接口只被部分调用者使用,需要将这部分接口隔离出来,给其单独使用
- 把接口理解为单个 API 接口或函数
- 函数的设计功能单一,不要将多个不同的功能逻辑在一个函数中实现
- 把接口理解为 OOP 中的接口概念 ```typescript interface Vehicle { make: string; numberOfWheels: number; maxSpeed?: number; getReachKm(fuel: number, kmPerLitre: number): number; }
class Car implements Vehicle { make: string; numberOfWheels: number; maxSpeed: number;
constructor(make, numberOfWheels, maxSpeed) { this.make = make; this.numberOfWheels = numberOfWheels; this.maxSpeed = maxSpeed; }
getReachKm(fuel: number, kmPerLitre: number) { return fuel * kmPerLitre; } }
const carObj = new Car(“BMW”, 4, 240);
<a name="syjtV"></a>
# 依赖反转(DIP)
1. 高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口
1. 抽象接口不应该依赖于具体实现,而具体实现则应该依赖于抽象接口
> Whenever a module relies on an abstraction(interface or abstract class) as dependency, that dependency can be swapped for another implementation, like a plugin. Therefore, decoupling the module from its dependency.
通过依赖反转,使得模块可以插件化加载。<br />![WX20210513-102811@2x.png](https://cdn.nlark.com/yuque/0/2021/png/651859/1620872928416-20fa2b7a-efdc-4968-a787-23987d6c6022.png#clientId=u31c13530-38e6-4&from=ui&id=u4ed73e11&margin=%5Bobject%20Object%5D&name=WX20210513-102811%402x.png&originHeight=878&originWidth=1504&originalType=binary&size=86787&status=done&style=none&taskId=ufb5cbc80-5b03-4b4c-8a70-251f88f5b51)
```typescript
// domain/ApiClient.ts
export interface ApiClient {
createUser: (user: User) => Promise<void>;
getUserByEmail: (email: string) => Promise<User>;
// ...
}
// infra/HttpClient.ts
import axios from "axios";
import ApiClient from "domain/ApiClient";
export function HttpClient(): ApiClient {
return {
createUser: async (user: User) => {
return axios.post(/* ... */);
},
getUserByEmail: async (email: string) => {
return axios.get(/* ... */);
},
};
}
// domain/SignupService.ts
import ApiClient from "domain/ApiClient"; // ✅ the domain depends on an abstraction of the infra
export function SignupService(client: ApiClient) {
return async (email: string, password: string) => {
const existingUser = await client.getUserByEmail(email);
if (existingUser) {
throw new Error("Email already used");
}
return client.createUser({ email, password });
};
}
// index.ts
import SignupService from "domain/signup";
import HttpClient from "infra/HttpClient";
const signup = SignupService(HttpClient());
signup("bob@bob.com", "pwd123");