TypeScript 中的 SOLID 原则
下面的文章解释了正确使用 TypeScrip的 SOLID原则。
原文地址:https://samueleresca.net/2016/08/solid-principles-using-typescript/
作者:Samuele Resca
翻译:杨晓东(Savorboard)
前言
SOLID 是由 Robert C. Martin 在面向对象设计的(OOD)中提出的五个原则,你可以在这里更一步了解关于@UncleBob,这五个原则(SOLID)就是:
- 单一职责原则(Single Responsibility Principle):当需要修改某个类的时候原因有且只有一个
- 开放封闭原则(Open Closed Principle):软件实体应该是可扩展,而不能可修改的
- 里氏替换原则(Liskov Substitution Principle):子类的实例应该能够替换任何其超类的实例
- 接口分离原则(Interface Segregation Principle):使用多个专门的接口比使用单一的总接口总要好
- 依赖倒置原则(Dependency Inversion Principle):依赖于抽象不应该依赖于细节
这些原则使得程序员可以轻松地开发易于维护和扩展的软件。它们还使开发人员的代码能够容易地避免坏气味,轻松重构代码,并且也是敏捷或自适应软件开发的一部分。
单一责任原则(SRP)
SRP要求类只能有一个更改的原因。遵循这个原则来执行一些特定的相关任务。在考虑SRP时,你不需要将你的思维限制到类。你可以将这个原则应用到方法或者模块,确保他们仅仅只是做一件事情并且只有一个理由可以修改它们。
例子 - 错误的方式
这个 Task
类定义了一些于模型相关的属性,但是它也在一个基本的数据操作上定义了一些保存实体的数据访问的方法。
UML
代码
// 这个类没有遵循 SRP 原则
class Task {
private db: Database;
constructor(private title: string, private deadline: Date) {
this.db = Database.connect("admin:password@fakedb", ["tasks"]);
}
getTitle() {
return this.title + "(" + this.deadline + ")";
}
save() {
this.db.tasks.save({ title: this.title, date: this.deadline });
}
}
例子 - 正确的方式
UML
代码
class Task {
constructor(private title: string, private deadline: Date) {
}
getTitle() {
return this.title + "(" + this.deadline + ")";
}
}
class TaskRepository {
private db: Database;
constructor() {
this.db = Database.connect("admin:password@fakedb", ["tasks"]);
}
save(task: Task) {
this.db.tasks.save(JSON.stringify(task));
}
}
开放封闭原则(OCP)
软件实体应该对扩展开放,对修改关闭。
改变现有类的风险是,你会引入一个无意的行为变化。解决方案是创建另一个类,覆盖原始类的行为。通过OCP原则,一个组件应尽可能包含可维护并且可重复使用的代码。
例子 - 正确的方式
CreditCard
类描述了一个计算 monthlyDiscount()
的方法。这个 monthlyDiscount()
依赖了具体的Card类型,也就是:Silver 或者 Gold。如果要改变月度折扣计算(monthlyDiscount)那么应该建立另外一个类,重写monthlyDiscount()
方法。目前这个的解决方案是新建两个类:每个类型一个类。
UML
代码
class CreditCard {
private Code: String;
private Expiration: Date;
protected MonthlyCost: number;
constructor(code: String, Expiration: Date, MonthlyCost: number) {
this.Code = code;
this.Expiration = Expiration;
this.MonthlyCost = MonthlyCost;
}
getCode(): String {
return this.Code;
}
getExpiration(): Date {
return this.Expiration;
}
monthlyDiscount(): number {
return this.MonthlyCost * 0.02;
}
}
class GoldCreditCard extends CreditCard {
monthlyDiscount(): number {
return this.MonthlyCost * 0.05;
}
}
class SilverCreditCard extends CreditCard {
monthlyDiscount(): number {
return this.MonthlyCost * 0.03;
}
}
里氏替换原则(LSP)
子类不应该破坏父类的类型定义
这一原则的概念是由 Barbara Liskov 在1987年大会上发表,随后与 Jannette Wing 一起在1994年发表论文。
就这么简单,一个子类应当有一种方式覆写它的父类的方法,但是从客户的角度来看没有破坏它的功能。
例子
在下面的例子中,ItalyPostalAddress
, UKPostalAddress
和 USAPostalAddress
继承了一个公共的基类:PostalAddress
。
AddressWriter
类有一个引用指向了 PostalAddress
这个基类:也就是说 参数可以被三个不同的之类替换。
代码
abstract class PostalAddress {
Addressee: string;
Country: string
PostalCode: string;
City: string;
Street: string
House: number;
/*
* @returns Formatted full address
*/
abstract WriteAddress(): string;
}
class ItalyPostalAddress extends PostalAddress {
WriteAddress(): string {
return "Formatted Address Italy" + this.City;
}
}
class UKPostalAddress extends PostalAddress {
WriteAddress(): string {
return "Formatted Address UK" + this.City;
}
}
class USAPostalAddress extends PostalAddress {
WriteAddress(): string {
return "Formatted Address USA" + this.City;
}
}
class AddressWriter {
PrintPostalAddress(writer: PostalAddress): string {
return writer.WriteAddress();
}
}
接口分离原则(ISP)
有一个很常见的现象就是,在描述一个类的时候,基本上一个接口就把它覆盖完了,就是一个接口就描述了一整个类。ISP 原则指出,我们应该写一系列更加小并且具体的接口,交给该类来实现。而每个接口只提供单一的行为。
示例 - 错误的方式
下面的 Printer
接口,它有一个实现的类 SimplePrinter
,该接口具有 Copy 和 Print 的功能。
interface Printer {
copyDocument();
printDocument(document: Document);
stapleDocument(document: Document, tray: Number);
}
class SimplePrinter implements Printer {
public copyDocument() {
//...
}
public printDocument(document: Document) {
//...
}
public stapleDocument(document: Document, tray: Number) {
//...
}
}
例子 - 正确的方式
下面的示例显示了将方法分组到更加具体的接口和可以被替代的方法,它描述了一些契约,他们可以被一个单独的 SimplePrinter 类,或 SimpleCopier 类,或 SuperPrinter 类实现。
interface Printer {
printDocument(document: Document);
}
interface Stapler {
stapleDocument(document: Document, tray: number);
}
interface Copier {
copyDocument();
}
class SimplePrinter implements Printer {
public printDocument(document: Document) {
//...
}
}
class SuperPrinter implements Printer, Stapler, Copier {
public copyDocument() {
//...
}
public printDocument(document: Document) {
//...
}
public stapleDocument(document: Document, tray: number) {
//...
}
}
依赖倒置原则(DIP)
DIP 简单的说就超类不应该依赖于低级的组件,而应该依赖于抽象。
例子 - 错误的方式
高级的 WindowSwitch
依赖于底层低级的 CarWindow
类。
UML
代码
class CarWindow {
open() {
//...
}
close() {
//...
}
}
class WindowSwitch {
private isOn = false;
constructor(private window: CarWindow) {
}
onPress() {
if (this.isOn) {
this.window.close();
this.isOn = false;
} else {
this.window.open();
this.isOn = true;
}
}
}
总结
TypeScript 可以将所有的OOP原则和实践带入到你的软件中,使用 SOLID 原则来指导你的设计模式吧。
GitHub 完整的示例代码。
TypeScript 中的 SOLID 原则的更多相关文章
- 设计模式之SOLID原则
介绍 设计模式中的SOLID原则,分别是单一原则.开闭原则.里氏替换原则.接口隔离原则.依赖倒置原则.前辈们总结出来的,遵循五大原则可以使程序解决紧耦合,更加健壮. SRP 单一责任原则 OCP 开放 ...
- 浅谈 SOLID 原则的具体使用
SOLID 是面向对象设计5大重要原则的首字母缩写,当我们设计类和模块时,遵守 SOLID 原则可以让软件更加健壮和稳定.那么,什么是 SOLID 原则呢?本篇文章我将谈谈 SOLID 原则在软件开发 ...
- 【转】面向对象设计的SOLID原则
S.O.L.I.D是面向对象设计和编程(OOD&OOP)中几个重要编码原则(Programming Priciple)的首字母缩写. SRP The Single Responsibility ...
- 面向对象设计的SOLID原则
S.O.L.I.D是面向对象设计和编程(OOD&OOP)中几个重要编码原则(Programming Priciple)的首字母缩写. SRP The Single Responsibility ...
- SOLID 原则
世纪的前几年里,“ Uncle Bob”Robert Martin 引入了用OOP 开发软件的五条原 则,其目的是设计出更易于维护的高质量系统.无论是设计新应用程序,还是重构现有基 本代码,这些 S ...
- 面向对象涉及SOLID原则
S = Single Responsibility Principle 单一职责原则 O = Opened Closed Principle 开放闭合原则 L = Liscov Substituti ...
- 类设计的SOLID原则
SOLID原则是面向对象范式的核心 单一职责原则(Single Responsible Principle, SRP):对于一个类,应该仅有一个引起它变化的原因.其基础是内聚,表示类完成单一功能的程度 ...
- 面向对象的SOLID原则白话篇
面向对象的SOLID原则 简介 缩写 全称 中文 S The Single Responsibility Principle 单一责任原则 O The Open Closed Principle 开放 ...
- SOLID原则(OOD&OOP)
SOLID原则是面向对象编程和面向对象设计的头五大原则.学习及应用这五大原则可以构建一个易于维护和扩展的应用程序,我们一起看看到底是那五大原则. S--单一责任原则(SRP) --Single Res ...
随机推荐
- Premiere Pro CC问题集,不断更新
1.Premiere Pro CC不好用? 是的.原因如下: 1.1 Adobe公司不注重用户体验,不注重工作流程,导致这款软件的用户体验很差,设计也很烂.对比Adobe公司当年用户体验最好的软件 F ...
- 疯狂html5演讲(两):HTML5简经常使用的元素和属性(一个):html5保留经常使用的元素
html5取出一小部分的元素和属性:主要删除的各种元素和属性与文档相关的风格.例<font>.width等待,html5建议规范css样式表来控制html文档样式. 1.基本元素 < ...
- 数以百万计美元的融资YO是什么东东?
给自己做个广告哈,新栏目"面试"已经推出,回复"面试"就可以获取. 这两天最火的应用是什么.非yo莫属,堪称史上最简单的社交应用,仅仅能向好友发送一个yo. 出 ...
- C# 在本地创建文件夹及子文件夹
string dict = @"d:\估价报告\"; if (!Directory.Exists(dict)) { Directory.CreateDirectory(dict); ...
- 24个JavaScript初学者最佳实践
这里面说到的一个就是使用循环新建一个字符串时,用到了join(),这个比较高效,常常会随着push(); 绑定某个动作时,可以把要执行的绑定内容定义为一个函数,然后再执行.这样做的好处有很多.第一是可 ...
- 目前比较流行的Python科学计算发行版
经常有身边的学友问到用什么Python发行版比较好? 其实目前比较流行的Python科学计算发行版,主要有这么几个: Python(x,y) GUI基于PyQt,曾经是功能最全也是最强大的,而且是Wi ...
- [每日一题] OCP1z0-047 :2013-08-01 正则表达式--- REGEXP_REPLACE 函数
这题又是考正则表达式,我们先根据题意,操作如下: hr@OCM> col "PHONE NUMBER" for a50 hr@OCM> SELECT phone_num ...
- 关于PHP 缓冲区
最权威的资料:http://php.net/manual/en/function.flush.php 里面有全世界的开发者的留言.常见问题都有讨论. 再说一下PHP 缓冲区相关的. web服务器 如 ...
- c#中关于大对象数组的一些心得
在之前的一个课题中,曾经需要用到2W行*3W列的float类型矩阵(大约2.4G),由于无法创建大于2G的对象,当时采用了一些取巧的办法回避了,并没有拿出精力来研究一下这个问题.今天和公司的张哥(大牛 ...
- iOS基础 - 数据库CoreData
一.iOS应用数据存取的常用方式 XML属性列表 —— PList NSKeyedArchiver 归档 Preference(偏好设置) SQLite3 Core Data 二.Core Data简 ...