下面的文章解釋了正確使用 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 完整的示例代碼。
文章列表
不含病毒。www.avast.com |
留言列表