跳轉到

SOLID: The Principles of Object Oriented Design

Single Responsibility Principle

A class should have one and only one reason to change, meaning that a class should only have one job

一個 class(或一支 function)只專注在做一件事情,這裡的一件事情是要根據你的 business logic 去定義的,為什麼要職責分明?因為這又關係到到時候你寫的測項範圍能足夠收斂,讓你在測試出錯的時候,盡快找出問題。

Bad

class SalaryCalculator{
  public int calculateSalary(Employee employee) {
    //...
    int taxDeduction = calculateTax(employee);
    //...
  }

  private int calculateTax(Employee employee) {
    switch (employee.getType()) {
      case FULL_TIME:
      //...
      case CONTRACTOR:
      //...
    }
  }
}

Better

分開計算薪資跟計算稅務的邏輯

class SalaryCalculator{
  private TaxCalculator taxCalculator;

  public int calculateSalary(Employee employee) {
    //...
    int taxDeduction = taxCalculator.calculateTax(employee);
    //...
  }
}

class TaxCalculator{
  private int calculateTax(Employee employee) {
    switch (employee.getType()) {
      case FULL_TIME:
      //...
      case CONTRACTOR:
      //...
    }
  }
}

Open Closed Principle

Depend on stable abstractions and modify system’s behavior by providing different realizations

close 是指要有一個不會改變的抽象,open 是指要能夠提供彈性的實作

Bad

class SalaryCalculator{
  private TaxCalculator taxCalculator;

  public int calculateSalary(Employee employee) {
    //...
    int taxDeduction = taxCalculator.calculateTax(employee);
    //...
  }
}

class TaxCalculator{
  private int calculateTax(Employee employee) {
    switch (employee.getType()) {
      case FULL_TIME:
        ..
      case CONTRACTOR:
        ..
    }
  }
}

Better

利用簡單工廠模式策略模式來實現

interface TaxCalculator{
  int calculateTax(Employee employee);
}

class TaxCalculatorFullTime implements TaxCalculator{
  int calculateTax(Employee employee){
    // calculate full time tax
  }
}

class TaxCalculatorContractor implements TaxCalculator{
  int calculateTax(Employee employee){
    // calculate contractor tax
  }
}

class TaxCalculatorFactory{
  public TaxCalculator newTaxCalculator(EmployeeType employeeType) {
    switch (employeeType) {
      case FULL_TIME:
        return new TaxCalculatorFullTime();
      case CONTRACTOR:
        return new TaxCalculatorContractor();
      default:
        return new TaxCalculatorContractor();
    }
  }
}

class SalaryCalculator{
  private TaxCalculatorFactory taxCalculatorFactory;

  public int calculateSalary(Employee employee) {
    //...
    TaxCalculator taxCalculator = taxCalculatorFactory.newTaxCalculator(employee.getType());
    int taxDeduction = taxCalculator.calculateTax(employee);
    //...
  }
}

Liskov Substitution Principle

If you have a function, that works for a base type, it should work for a derived type

如果 parent class 的某個 method 可以 work, 商業邏輯沒錯, 那繼承的 child class 的此 method 也同樣要能 work。

Bad

class Rectangle {
  constructor(width, height) {
    this._width = width;
    this._height = height;
  }

  getWidth() {
    return this._width;
  }
  getHeight() {
    return this._height;
  }

  setWidth(value) {
    this._width = value;
  }
  setHeight(value) {
    this._height = value;
  }

  getArea() {
    return this._width * this._height;
  }
}

class Square extends Rectangle {
  constructor(size) {
    super(size, size);
  }
}

Better

class Rectangle {
  constructor(width, height) {
    this._width = width;
    this._height = height;
  }

  getWidth() {
    return this._width;
  }

  getHeight() {
    return this._height;
  }

  setWidth(value) {
    this._width = value;
  }

  setHeight(value) {
    this._height = value;
  }

  getArea() {
    return this._width * this._height;
  }
}

class Square extends Rectangle {
  constructor(size) {
    super(size, size);
  }

  setWidth(value) {
    this._width = this._height = value;
  }

  setHeight(value) {
    this._width = this._height = value;
  }
}

Interface Segregation Principle

A client should never be forced to implement an interface that it doesn’t use A client shouldn’t be forced to depend on methods they do not use

不應該實作多餘用不到的 method,可以再想想該怎麼定義(切細)你的 interface。

Bad

interface IAnimal {
  fly(): void;
  swim(): void;
}

class Bird implements IAnimal {
  fly() {
    console.log("I can fly!");
  }

  swim() {
    throw Error("I cannot swim!");
  }
}

class Frog implements IAnimal {
  fly() {
    throw Error("I cannot fly!");
  }

  swim() {
    console.log("I can swim!");
  }
}

Better

interface Flyable {
  fly(): void;
}

interface Swimable {
  swim(): void;
}

class Bird implements Flyable {
  fly() {
    console.log("I can fly!");
  }
}

class Frog implements Swimable {
  swim() {
    console.log("I can swim!");
  }
}

class SuperMan implements Flyable, Swimable {
  fly() {
    console.log("I can fly very high!");
  }

  swim() {
    console.log("I can swim very fast!");
  }
}

Dependency Inversion Principle

High-level modules should not import anything from low-level modules. They should both depend on abstractions

高階模組不直接依賴於低階模組的具體實現,而是抽象化後的 interface,為什麼要這樣做? 因為可以不必急著實作,藉由先定義好介面,以幫助你確認商業邏輯有沒有問題,也能夠幫助團隊有效分工(比如說 Architect 先定義好 interface,確認好沒問題,實作接著交給 RD)

Bad

classDiagram
    UserService ..> Logger: use
    class UserService {
        -logger: Logger
        +getAll()
    }
    class Logger {
        +log(message)
        +error(message)
    }
class UserService {
  private logger: Logger;
  constructor(logger: Logger) {
    this.logger = logger;
  }
}

Better

classDiagram
    direction DT
    UserService ..> ILogger: use
    Logger ..|> ILogger: implement
    class UserService {
        -logger: ILogger
        +getAll()
    }

    class ILogger {
        <<interface>>
        log(message)
        error(message)
    }

    class Logger {
        +log(message)
        +error(message)
    }
interface ILogger {
  log(message: string): void;
  error(message: string): void;
}

class MyLogger implements ILogger {
  log(message: string) {
    console.log(message);
  }
  error(message: string) {
    console.error(message);
  }
}

class UserService {
  private logger: ILogger;
  constructor(logger: ILogger) {
    this.logger = logger;
  }
}