본문 바로가기

TypeScript

[TypeScript] 객체 지향 설계원칙 - S.O.L.I.D

객체지향 설계를 할때는 S.O.L.I.D 원칙을 따라서 설계하는것이 필수이다.

 

1. SRP 원칙  매우 중요

 

 - 클래스는 하나의 책임만 가져야 한다는 매우 기본적인 원칙이다.

- 5개의 원칙 중 가장 기본적이고 중요한 원칙

- 예를 들어, 유저 서비스라는 클래스가 있다면 유저서비스에서는 유저 관련된 액션만 해야 함

 

잘못된 사례

: 유저 서비스에 생뚱맞게 이메일 전송 로직이 있음. 서비스 내에서 다른 서비스를 참조하는것은 지양해야 함.

class UserService {
  constructor(private db: Database) {}

  getUser(id: number): User {
    // 사용자 조회 로직
    return this.db.findUser(id);
  }

  saveUser(user: User): void {
    // 사용자 저장 로직
    this.db.saveUser(user);
  }

  sendWelcomeEmail(user: User): void {
    // 갑분 이메일 전송 로직이 여기 왜?
    const emailService = new EmailService();
    emailService.sendWelcomeEmail(user);
  }
}

 

올바른 사례

: 이메일서비스와 유저서비스가 분리되어있음.

class UserService {
  constructor(private db: Database) {}

  getUser(id: number): User {
    // 사용자 조회 로직
    return this.db.findUser(id);
  }

  saveUser(user: User): void {
    // 사용자 저장 로직
    this.db.saveUser(user);
  }
}

class EmailService {
  // 이메일 관련된 기능은 이메일 서비스에서 총괄하는게 맞습니다.
  // 다른 서비스에서 이메일 관련된 기능을 쓴다는 것은 영역을 침범하는 것이에요!
  sendWelcomeEmail(user: User): void {
    // 이메일 전송 로직
    console.log(`Sending welcome email to ${user.email}`);
  }
}

 


2. OCP 개방폐쇄원칙 -> 인터페이스 혹은 상속을 잘 써야 함.

 

- 클래스는 확장에 대해서는 열려 있어야 하고, 수정에 대해서는 닫혀 있어야 함.

- 클래스는 기존 코드를 변경하지 않고도 기능을 확장할 수 있어야 함.

- 즉, 인터페이스나 상속을 통해 이를 해결할 수 있음 


3. LSP 원칙

 

- 서브타입은 기반이 되는 슈퍼타입을  대체할 수 있어야 한다는 원칙.

- 자식 클래스는 부모 클래스의 기능을 수정하지 않고도 부모 클래스와 호환되어야 함.

- 논리적으로 엄격하게 관계정립이 되어야 함.

 

잘못된 사례

: 펭귄도 조류지만 날수가 없는데 fly를 상속받음

class Bird {
  fly(): void {
    console.log("펄럭펄럭~");
  }
}

class Penguin extends Bird {
  // 으잉? 펭귄이 날 수 있나요? 펭귄이 펄럭펄럭~ 한다는 것은 명백한 위반이죠.
}

 

올바른 사례

abstract class Bird {
  abstract move(): void;
}

class FlyingBird extends Bird {
  move() {
    console.log("펄럭펄럭~");
  }
}

class NonFlyingBird extends Bird {
   move() {
    console.log("뚜벅뚜벅!");
  }
}

class Penguin extends NonFlyingBird {} // 이제 위배되는 것은 아무것도 없네요!

 


4. ISP 원칙 -> 인터페이스 분리 원칙

 

- 클래스는 자신이 사용하지 않는 인터페이스의 영향을 받지 않아야 한다.

- 해당 클래스에게 무의미한 메소드의 구현을 막자는 의미로 인터페이스를 너무 크게 정의하기보단 필요한 만큼만 정의하고 클래스는 입맛에 맞게 필요한 인터페이스를 구현하도록 유도


5. DIP 원칙 -> 의존성 역전 원칙

 

- DIP는 Java 의 Spring 프레임워크나 Node.js 의 Next.js 프레임워크와 같이 웹서버 프레임워크 내에 많이 나오는 원칙이다.

- 이 원칙은 하위 수준 모듈(구현 클래스) 보다 상위 수준 모듈(인터페이스) 에 의존해야 한다는 의미.

- 데이터 베이스 라는 클래스가 있다고 가정하면, 데이터베이스의 원천은 로컬 스토리지가 될 수 있고, 클라우드 스토리지가 될 수 있음. 이 대, 데이터 베이스의 원천을 로컬 스토리지 타입 혹은 클라우드 스토리지 타입으로 한정하는 것이 아닌 그보다 상위 수준인 스토리지 타입으로 한정하는 것.

 

interface MyStorage {
  save(data: string): void;
}

class MyLocalStorage implements MyStorage {
  save(data: string): void {
    console.log(`로컬에 저장: ${data}`);
  }
}

class MyCloudStorage implements MyStorage {
  save(data: string): void {
    console.log(`클라우드에 저장: ${data}`);
  }
}

class Database {
  // 상위 수준 모듈인 MyStorage 타입을 의존! 
  // 여기서 MyLocalStorage, MyCloudStorage 같은 하위 수준 모듈에 의존하지 않는게 핵심!
  constructor(private storage: MyStorage) {}

  saveData(data: string): void {
    this.storage.save(data);
  }
}

const myLocalStorage = new MyLocalStorage();
const myCloudStorage = new MyCloudStorage();

const myLocalDatabase = new Database(myLocalStorage);
const myCloudDatabase = new Database(myCloudStorage);

myLocalDatabase.saveData("로컬 데이터");
myCloudDatabase.saveData("클라우드 데이터");

 

 

 

'TypeScript' 카테고리의 다른 글

[TypeScript] 인터페이스  (0) 2023.07.26
[TypeScript] 추상 클래스  (0) 2023.07.26
[TypeScript] 상속  (0) 2023.07.26
[TypeScript] 클래스  (0) 2023.07.26
[TypeScript] 프로젝트 세팅  (0) 2023.07.26