下面的文章解释了正确使用 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, UKPostalAddressUSAPostalAddress 继承了一个公共的基类: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 原则的更多相关文章

  1. 设计模式之SOLID原则

    介绍 设计模式中的SOLID原则,分别是单一原则.开闭原则.里氏替换原则.接口隔离原则.依赖倒置原则.前辈们总结出来的,遵循五大原则可以使程序解决紧耦合,更加健壮. SRP 单一责任原则 OCP 开放 ...

  2. 浅谈 SOLID 原则的具体使用

    SOLID 是面向对象设计5大重要原则的首字母缩写,当我们设计类和模块时,遵守 SOLID 原则可以让软件更加健壮和稳定.那么,什么是 SOLID 原则呢?本篇文章我将谈谈 SOLID 原则在软件开发 ...

  3. 【转】面向对象设计的SOLID原则

    S.O.L.I.D是面向对象设计和编程(OOD&OOP)中几个重要编码原则(Programming Priciple)的首字母缩写. SRP The Single Responsibility ...

  4. 面向对象设计的SOLID原则

    S.O.L.I.D是面向对象设计和编程(OOD&OOP)中几个重要编码原则(Programming Priciple)的首字母缩写. SRP The Single Responsibility ...

  5. SOLID 原则

     世纪的前几年里,“ Uncle Bob”Robert Martin 引入了用OOP 开发软件的五条原 则,其目的是设计出更易于维护的高质量系统.无论是设计新应用程序,还是重构现有基 本代码,这些 S ...

  6. 面向对象涉及SOLID原则

    S = Single Responsibility Principle 单一职责原则 O = Opened Closed Principle 开放闭合原则  L = Liscov Substituti ...

  7. 类设计的SOLID原则

    SOLID原则是面向对象范式的核心 单一职责原则(Single Responsible Principle, SRP):对于一个类,应该仅有一个引起它变化的原因.其基础是内聚,表示类完成单一功能的程度 ...

  8. 面向对象的SOLID原则白话篇

    面向对象的SOLID原则 简介 缩写 全称 中文 S The Single Responsibility Principle 单一责任原则 O The Open Closed Principle 开放 ...

  9. SOLID原则(OOD&OOP)

    SOLID原则是面向对象编程和面向对象设计的头五大原则.学习及应用这五大原则可以构建一个易于维护和扩展的应用程序,我们一起看看到底是那五大原则. S--单一责任原则(SRP) --Single Res ...

随机推荐

  1. 用lucene.net根据关键字检索本地word文档

    目前在做一个winform小软件,其中有一个功能是能根据关键字检索本地保存的word文档.第一次是用com读取word方式(见上一篇文章),先遍历文件夹下的word文档,读取每个文档时循环关键字查找, ...

  2. 大约PF_RING/Intel 82599/透明VPN一些事

    接近崩溃的边缘,如今,在医院这篇文章地方的想法,小病,我宁愿不吃药瓶.一台笔记本电脑,但无法上网,我不称职.想知道的东西.唯一可用3G,不开的热点.由于没人给我报销流程.这个周末,我只有一天,由于下雨 ...

  3. less 命令

    每天一个linux命令(13):less 命令 less 工 具也是对文件或其它输出进行分页显示的工具,应该说是linux正统查看文件内容的工具,功能极其强大.less 的用法比起 more 更加的有 ...

  4. linux_shell 特殊符号的介绍

    linux_shell 特殊符号的介绍 2011-12-17 17:54:07 分类: 原文地址:linux_shell 特殊符号的介绍 作者:xu_liuzhen linux_shell 特殊符号的 ...

  5. 微软Visual Studio "14" CTP 2 发布

    微软Visual Studio "14" CTP 2 发布 对于在微软阵营下进行工作的团队来说,拥有最新版本的Visual Studio是提高效率最佳的选择,没有之一. 在本文中, ...

  6. MVC5搜索/查询 流程功能的实现

    接着上次的篇幅,我们这篇手动来写一个查询的流程代码!  搜索/查询 流程功能的实现 那现在要做搜索(查询)功能我们第一步应该做什么呢!第一次是不是我们应该去Controller(控制器)里去搞一个搜索 ...

  7. Linux 系统命令总结

    自己收集到的Linux系统命令大全! 1,查看apache2的连接状态: netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S ...

  8. 封装两个简单的Jquery组件

    Jquery给我们提供了很大的方便,我们把他看成是一个公共库,以致在这个公共库上延伸出了很多Jquery插件:在项目过程中,有些插件总是不那么令人满意: 主要说两个项目用途: 1.  遮罩层,跟一般的 ...

  9. Js 数组(一):基础应用

    (一) Js 数据类型 分为基本数据类型以及引用类型 基本数据类型有 null,undefined,Boolen,Number,String,还有一种复杂数据类型 Object. var var1 = ...

  10. 流媒体:V4L2视频获取

    从深圳回来已经20多天了,除了完善毕业设计程序和论文,其他时间都去玩游戏了.真的是最后的一段时间能够无忧无虑在校园里挥霍自己的青春了.今天完成的答辩,比想象的要简单,一直以来想把我现在的这个流媒体的东 ...