Typescript 中的 SOLID 原则

Kuma Li
5 min readApr 23, 2022

--

翻译自:https://samueleresca.net/solid-principles-using-typescript/

Photo by Freeman Zhou on Unsplash

The following article explains SOLID principles using Typescript.
本文将解释 Typescript 中的 SOLID 原则

Here are more articles about Typescript:
更多的 Typescript 相关文章请看这里

Introducing Typescript
Typescript 介绍

Introducing Typescript: Language features
Typescript 介绍:语言特性

SOLID is an acronym for the first five object-oriented design(OOD) principles by Robert C. Martin, popularly known as @UncleBob.
SOLID 是由 Robert C.Martin (在推特上以鲍勃叔叔为人熟知)所提出的5条面对对象编程的第一性原则的缩写。

The five SOLID principles are:
这五条原则是:

  • Single responsibility principle: a class should have one, and only one, reason to change;
    单一职责原则:修改一个类的理由有且只有一个。
  • Open-closed principle: it should be possible to extend the behavoir of a class without modifying it;
    开放封闭原则:要可以扩展类的表现而不修改类。
  • Liskov Substitution principle: subclasses should be substitutable for their superclasses;
    里氏替换原则:子类的实例应该可以替代他们的父类的实例。
  • Interface segregation principle: many small, client-specific interfaces are better than one general purpose interface;
    接口分离原则:将一个总的通用接口分散成小的,专门的接口会更好。
  • Dependency inversion principle: depends on abstractions not concretions;
    依赖倒置原则:依赖于抽象,而非具体细节。

These principles, make it easy for a programmer to develop software that are easy to maintain and extend. They also make it easy for developers to avoid code smells, easily refactor code, and are also a part of the agile or adaptive software development.
这些原则将使一个程序员在开发软件时更加容易地进行维护和扩展。他们也使得程序猿们避免低质量的代码,能很容易地重构代码,而且也是敏捷或者自适应软件开发的一部分。

The Single Responsibility Principle (SRP)

单一职责原则

The SRP requires that a class should have only one reason to change. A class that follows this principle performs just few related tasks. You don’t need to limit your thinking to classes when considering the SRP. You can apply the principle to methods or modules, ensuring that they do just one thing and therefore have just one reason to change.
该原则要保证一个类只因为一个理由被修改。一个遵循此原则的类会显得只有少量关联的任务。在考虑该原则时你不仅仅将思维局限于类上。你也可以在方法或者模块上去实现这个原则,保证他们只做一件事,因为只有一个理由被修改。

Example — wrong way

例子 — 错误的方式

The class Task defines properties related to the model, but it also defines the data access method to save the entity on a generic data source:
这个 Task类将属性定义关联到了模型上,但他同时定义了数据访问方法来保存实体到一个通用的数据源:

UML

Code

https://gist.githubusercontent.com/samueleresca/c9b2a748ad5109f91e513487b2561e52/raw/b19884c73661c8440ddb4c70c155a1a0a738c572/Task.ts

Example — right way

例子 — 正确的方式

The Task class can be divided between Task class, that takes care of model description and TaskRepository that is responsabile for storing the data.
这个 Task类可以分成负责模型描述的 Task 类和负责数据存储的 TaskRepository 类。

UML

Code

https://gist.githubusercontent.com/samueleresca/24e2318f10266a83a56668dd1818d7c9/raw/f4d4e9e6a32a6f6ac5342e2ac29ff905934c4264/Task.ts

The Open-closed Princple (OCP)

开放封闭原则

Software entities should be open for extension but closed for modification.

软件实体应该可以扩展而不是修改。

The risk of changing an existing class is that you will introduce an inadvertent change in behaviour. The solution is create another class that overrides the behaviour of the original class. By following the OCP, a component is more likely to contain maintainable and re-usable code.
修改一个现有类的风险是你将会在不经意间引入变化的行为。解决这个问题的方案是创造另一个类覆盖原始类的行为。遵循这条原则,一个组件应该有用可维护和可复用的代码。

Example — right way

例子 — 正确的方法

The CreditCard class describes a method to calculate the monthlyDiscount(). The monthlyDiscount() depends on the type of Card, which can be : Silver or Gold. To change the monthly discount calc, you should create another class which overrides the monthlyDiscount() Method.
这个 CreditCard 类描述了一个方法 monthlyDiscount, 这个方法依赖在卡片类型上:银卡或金卡。要改变这个月度折扣的计算,你需要创建另一个类来覆盖当前的这个 monthlyDiscount() 方法。

The solution is to create two new classes: one for each type of card.
这个方案是为2种类型的卡创建2个新的类。

UML

Code

https://gist.githubusercontent.com/samueleresca/bd7f9d3d6c412d9412387d4e0f109e27/raw/7600c9ec29dfcc52347f3d662011117ddc2a3793/CreditCard.ts

The Liskov Substitution Principle (LSP)

里氏替换原则

Child classes should never break the parent class’ type definitions.

子类永不破坏父类的类型定义

The concept of this principle was introduced by Barbara Liskov in a 1987 conference keynote and later published in a paper together with Jannette Wing in 1994.
这个概念是由 Barbara Liskov 在1987年的一个会议上提出的,之后在1994年与 Jannette Wing 联合发表于一篇论文中。

As simple as that, a subclass should override the parent class methods in a way that does not break functionality from a client’s point of view.
就是如此容易,一个子类可以覆盖父类的方法,但是从客户端的角度并不破坏父类的功能。

Example

例子

In the following example ItalyPostalAddress, UKPostalAddress and USAPostalAddress extend one common class: PostalAddress.
在下面的列子种 ItalyPostalAddress, UKPostalAddressUSAPostalAddress 都扩展自一个通用的类: PostalAddress

The AddressWriter class refers PostalAddress: the writer parameter can be of three different sub-types.
这个 AddressWriter 类引用了 PostalAddress 类: writer 参数可以设定为3个不同的子类型。

UML

Code

https://gist.githubusercontent.com/samueleresca/e175ef72d4e43188f974e7cf663e256b/raw/2e59481dca884393ef2484bdc8e985cd9b83af67/PostalAddress.ts

The Interface Segregation Principle (ISP)

接口分离原则

It is quite common to find that an interface is in essence just a description of an entire class. The ISP states that we should write a series of smaller and more specific interfaces that are implemented by the class. Each interface provides an single behavior.
有一个非常常见的现象是用一个接口来描述整个类。接口分离原则指出我们需要写一系列小而详细的接口被类来实现。每个接口只提供一个单一的行为。

Example — wrong way

例子 — 错误的方式

The following Printer interface makes it impossible to implement a printer that can print and copy, but not staple:
下面的 Printer 接口使一台打印机可以打印和复印,但不能订书。

https://gist.github.com/samueleresca/9bdfed51b73da6b6c02e211461d47444/raw/e4796d1bf73fe7a6c2757787f121d76aed87d026/Printers.ts

Example — right way

例子 — 正确的方式

The following example shows an alternative approach that groups methods into more specific interfaces. It describe a number of contracts that could be implemented individually by a simple printer or simple copier or by a super printer:
以下例子展示了另一种方式,将方法分组为更加详细的接口。他描述了一些契约,可以被任意 SimplePrinterSimpleCopier或者 SuperPrinter所实现。

https://gist.github.com/samueleresca/c57690979ea3a8e765c099c96f0c8c3a/raw/551dd7b9f03e6aadfe9e67cd1d65fb62f9c56b03/Printers.ts

The Dependency inversion principle (DIP)

依赖倒置原则

The DIP simply states that high-level classes shouldn’t depend on low-level components, but instead depend on an abstraction.
依赖导致选择指出高阶类不应当依赖于低阶组件,而是依赖一个抽象。

Example — wrong way

例子 — 错误的方式

The high-level WindowSwitch depends on the lower-level CarWindow class:
高阶 WindowSwitch 依赖于低阶的 CarWindow 类:

UML

Code

https://gist.github.com/samueleresca/571d8f2df672e53f7c88caae28b6b040/raw/da5d5aedd3fcef8b4e95d3d7b3e7174e9505cb12/WindowSwitcher.ts

Example — right way

例子 — 正确的方式

To follow the DIP, the class WindowSwitch should references an interface (IWindow) that is implemented by the object CarWindow:
根据依赖倒置原则, WindowSwitch 类应当引用一个实现了 IWindow 接口的 CarWindow 对象。

UML

Code

https://gist.github.com/samueleresca/b2be73e5fbce1049a6071d86a2d0564d/raw/80e74dd3e783a9491970e7af86ad0bb78c65a6af/WindowSwitcher.ts

Final thoughts

写在最后

Typescript make it possible to bring all of the principles and practices of OOP into your software, using SOLID principles to guide your design patterns.
Typescript 让我们有可能将所有面对对象编程的原则和实践带入软件开发,使用 SOLID 原则来指导我们的设计模式吧。

Here’s the GitHub repository containing the full examples.
这里是所有例子的 Github Repo

--

--