SOLID의 원칙을 스스로 이해하다
개요
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() {}
}
참고 자료
O: 개방형 폐쇄 원칙
개요
클래스 모듈 함수는 확장을 위해 열려 있고 수정을 위해 닫혀 있어야 합니다.(= 변경 사항 없이 시스템 확장 용이)
시스템을 구성 요소로 나누어 구성 요소의 의존 관계를 등급 구조로 만든다.(= 상위 어셈블리가 하위 어셈블리 변경의 영향을 받지 않도록 함)
개방 폐쇄 원칙을 위반한 구체적인 사례
employeeInfo
의 names
데이터 구조가 바뀌었을 때 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
참고 자료
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");
}
}
참고 자료
인터페이스 분리 원칙
콘셉트
고객은 자신이 사용하지 않는 방법에 대한 의존을 강요해서는 안 된다.(= 불필요한 의존 관계 제거)
인터페이스 분리 원칙 위반의 구체적인 예
다음 코드
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")
}
}
참고 자료
D: 의존적 반전의 원칙
콘셉트
상부 모듈은 낮은 모듈에 의존해서는 안 되고 둘 다 추상에 의존해야 한다.(= 상위 모듈이 하위 모듈 변경의 영향을 받지 않도록 함)
추상화(Interfaces/Abstraction 클래스)는 실제 포장의 세부 사항(Class)에 의존하지 않고 실제 포장의 세부 사항은 추상에 의존해야 한다.
의존성 반전 원칙에 어긋나는 구체적인 예
아래 코드
cry
는 Animal
에 의존하는 상태로 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")
}
}
참고 자료
참고 자료
Reference
이 문제에 관하여(SOLID의 원칙을 스스로 이해하다), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/k_sato/articles/98f4b15747e191텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)