SOLID의 원칙을 스스로 이해하다

32257 단어 solidtech

개요


SOLID의 원칙에 관하여 나는 가능한 한 간결하게 총결하였다.
S(Single Responsibility Principle): 単一責任の原則
O(Open/Closed principle): 開放閉鎖の原則
L(Liskov substitution principle): リスコフの置換原則
I(Interface segregation principle): インターフェース分離の原則
D(Dependency inversion principle): 依存性逆転の原則

S: 단일 책임 원칙


개요


모듈은 반응기에 대해 책임을 져야 한다.

단일 책임 원칙을 위반한 구체적인 사례


다음 3가지 방법은 각각 다른 반응기에 대해 책임을 지고 SRP를 위반했다.
반응기의 다른 코드를 분할해야 한다는 것이다.
class Employee {
    // 経理部門が規定する(報告先はCFO)
    calculatePay() {}

    // 人事部門が規定する(報告先はCOO)
    reportHours() {}

    // DB管理者が規定する(報告先はCTO)
    save() {}
}

해결책


공유 데이터를 한데 모아 반응기의 다른 함수를 다른 종류로 이동시킨다.
// 共有データ
class Employee {
    id: number
    name: string 
    salary: number

    constructor(id: number, name: string, salary: number) {
        this.id = id
        this.name = name
        this.salary = salary
    }
}

// 経理部門が規定する(報告先はCFO)
class PayCalculator {
    employeeData: Employee

    constructor(employee: Employee) {
        this.employeeData = employee
    }

    calculatePay() {}
}

// 人事部門が規定する(報告先はCOO)
class HourReporter {
    employeeData: Employee

    constructor(employee: Employee) {
        this.employeeData = employee
    }

    reportHours() {}
}

// DB管理者が規定する(報告先はCTO)
class EmployeeSaver {
    employeeData: Employee

    constructor(employee: Employee) {
        this.employeeData = employee
    }

    save() {}  
}

참고 자료

  • Understanding SOLID Principles in JavaScript | Hacker Noon
  • 단일 책임 원칙 폭사 무책임 다목적류 - Qita
  • O: 개방형 폐쇄 원칙


    개요


    클래스 모듈 함수는 확장을 위해 열려 있고 수정을 위해 닫혀 있어야 합니다.(= 변경 사항 없이 시스템 확장 용이)
    시스템을 구성 요소로 나누어 구성 요소의 의존 관계를 등급 구조로 만든다.(= 상위 어셈블리가 하위 어셈블리 변경의 영향을 받지 않도록 함)

    개방 폐쇄 원칙을 위반한 구체적인 사례

    employeeInfonames 데이터 구조가 바뀌었을 때 printEmployeeInfo의 실현을 바꾸어야 한다.
    interface EmployeeInfo {
        description: string
        names: string[]
    }
    
    const printEmployeeInfo = (employeeInfo: EmployeeInfo) => {
        console.log(employeeInfo.description)
        employeeInfo.names.forEach((name) => {
          console.log(name);
        })
    }
    
    const employeeInfo = {
        description: "従業員情報",
        names: ["Taro", "Jiro", "Saburo"]
    }
    
    printEmployeeInfo(employeeInfo)
    //=> Taro Jiro Saburo
    

    해결책

    employeeInfo로 하여금 names의 교체 방법을 가지게 하다.printEmployeeInfo 그 실현을 바꾸지 않고 인터페이스를 만족시키는 대상을 확장할 수 있다.
    interface EmployeeInfo {
        description: string
        names: string[]
        printNames: () => void
    }
    
    const printEmployeeInfo = (employeeInfo: EmployeeInfo) => {
        console.log(employeeInfo.description)
        employeeInfo.printNames()
    }
    
    const employeeInfo = {
        description: "従業員情報",
        names: ["Taro", "Jiro", "Saburo"],
        printNames: function() {
            this.names.forEach((name: string) => {
              console.log(name)
            })
        },
    }
    
    printEmployeeInfo(employeeInfo)
    //=> Taro Jiro Saburo
    

    참고 자료

  • Maintainable Code and the Open-Closed Principle
  • SOLID Design Principles Explained: The Open/Closed Principle with Code Examples
  • L: 다람쥐의 교체 원칙


    개요


    파생류는 반드시 원시적인 기초류와 교체할 수 있어야 한다.
  • 파생류에서 다시 쓰는 방법은 기본 클래스 방법과 같은 수·형 매개 변수
  • 를 사용해야 한다.
  • 파생 클래스에서 다시 쓰는 방법의 반환값 유형은 기본 방법의 반환값 유형과 같아야 한다
  • 파생류에서 다시 쓰는 방법의 예외는 기초 방법의 예외와 동일해야 한다
  • 위험 이전 원칙을 위반한 구체적인 예


    아래 코드에서는 EmployeeInfo LSP를 따라 다릅니다.
    그러나 Dog는 대체할 수 없기 때문에 LSP를 위반했다고 할 수 있다.
    class Animal { 
        run(speed: number) {
            return `running at ${speed} km/h`
        }
    }
    
    // OK
    class Dog extends Animal {
        bark() {
    	/* 省略 */
        }
    
        run(speed: number) {
            return `running at ${speed} km/h`
        }
    }
    
    // LSPに違反
    class Sloth extends Animal {
        run() {
           return new Error("Sorry, I'm too lazy to run");
        }
    }
    

    참고 자료

  • Liskov Substitution Principle in JavaScript and TypeScript
  • 인터페이스 분리 원칙


    콘셉트


    고객은 자신이 사용하지 않는 방법에 대한 의존을 강요해서는 안 된다.(= 불필요한 의존 관계 제거)

    인터페이스 분리 원칙 위반의 구체적인 예


    다음 코드Sloth는interfaceAnimal를 만족시키므로 문제가 없다고 할 수 있습니다.
    그러나 Dog에서 처리Animal가 없고 불필요하게 Lizard에 의존하는 것은 인터페이스 분리의 원칙에 위배된다고 할 수 있다.
    interface Animal {
        run: () => void
        eat: () => void
        cry: () => void
    }
    
    // OK 
    class Dog implements Animal {
        run() {
            console.log("RUN")
        }
    
        eat() {
            console.log("EAT")
        }
    
        cry() {
            console.log("CRY")
        }
    }
    
    // Cryに対して処理がなく、Animalに不必要に依存している
    class Lizard implements Animal {
        run() {
            console.log("RUN")
        }
    
        eat() {
            console.log("EAT")
        }
    
        cry() {
            // Don't call this method
        }
    }
    

    해결책


    위에서 말한 바와 같이 공통된 부분만 꺼내 더욱 가는 인터페이스로 분리하면 불필요한 의존을 없앨 수 있다.
    interface Animal {
        run: () => void
        eat: () => void
    }
    
    // 個別のInterface
    interface Mammal extends Animal {
        cry: () => void
    }
    
    // 個別のInterface
    interface Reptile extends Animal {}
    
    class Dog implements Mammal {
        run() {
            console.log("RUN")
        }
    
        eat() {
            console.log("EAT")
        }
    
        cry() {
            console.log("CRY")
        }
    }
    
    class Lizard implements Reptile {
        run() {
            console.log("RUN")
        }
    
        eat() {
            console.log("EAT")
        }
    }
    

    참고 자료

  • Interface Segregation Principle in JavaScript and TypeScript
  • SOLID JavaScript: The Interface Segregation Principle
  • D: 의존적 반전의 원칙


    콘셉트


    상부 모듈은 낮은 모듈에 의존해서는 안 되고 둘 다 추상에 의존해야 한다.(= 상위 모듈이 하위 모듈 변경의 영향을 받지 않도록 함)
    추상화(Interfaces/Abstraction 클래스)는 실제 포장의 세부 사항(Class)에 의존하지 않고 실제 포장의 세부 사항은 추상에 의존해야 한다.

    의존성 반전 원칙에 어긋나는 구체적인 예


    아래 코드cryAnimal에 의존하는 상태로 DIP의 원칙을 위반했다고 할 수 있다.
    DataProvider --(依存)--> DataFetchClient
    
    import DataFetchClient from "FetchHTTPClient"
    
    class DataProvider {
      httpClient: typeof DataFetchClient
    
      constructor(httpClient = DataFetchClient) {
        this.httpClient = httpClient
      }
    
      getData() {
        return this.httpClient.get("")
      }
    }
    

    해결책


    모듈은 추상에 의존한다.
    interface HttpClient {
      get(arg: string): Promise<HttpClient>
    }
    
    class DataProvider {
      httpClient: HttpClient
    
      constructor(httpClient: HttpClient) {
        this.httpClient = httpClient
      }
    
      getData() {
        return this.httpClient.get("URL")
      }
    }
    

    참고 자료

  • Dependency Inversion Principle in JavaScript and TypeScript
  • 의존 관계 역전의 원칙 (DIP)
  • 참고 자료

  • The S.O.L.I.D Principles in Pictures
  • 약자 SOLID 원칙-Qita
  • 개발자가 알아야 할 SOLID 원칙
  • Understanding SOLID Principles in JavaScript | Hacker Noon
  • 좋은 웹페이지 즐겨찾기