S(SRP. 단일 책임 원칙) ⭐현업에서도 매우 중요
클래스는 하나의 책임만 가져야 한다
🚫(잘못된 사례)
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}`);
}
}
O(OCP. 개방 폐쇄 원칙)
클래스는 확장에 대해서는 열려 있어야 하고 수정에 대해서는 닫혀 있어야 한다는 원칙
인터페이스나 상속을 통해서 클래스의 기존 코드를 변경하지 않고도 기능을 확장할 수 있어야 합니다.
- 인터페이스: 구조를 참고해서 확장
- 상속: 베이스 클래스의 기본 기능을 흡수하되, 필요한건 오버라이딩을 통해 재정의
L(LSP. 리스코프 치환 원칙)
서브타입은 기반이 되는 슈퍼타입을 대체할 수 있어야 한다는 원칙입니다.
다시 말해, 자식 클래스는 부모 클래스의 기능을 수정하지 않고도 부모 클래스와 호환되어야 합니다. (O와 일맥상통하는 부분이 많음)
즉, 논리적으로 엄격하게 관계가 정립이 되어야 함
🚫(잘못된 사례)
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("뚜벅뚜벅!");
}
}
// 펭귄이 Bird를 상속받는게 아니라 NonFlyingBird를 상속해서 위배되는 것이 없도록!
class Penguin extends NonFlyingBird {} // 이제 위배되는 것은 아무것도 없네요!
I(ISP. 인터페이스 분리 원칙)
클래스는 자신이 사용하지 않는 인터페이스의 영향을 받지 않아야 한다.
즉, 해당 클래스에게 무의미한 메소드의 구현을 막자는 의미!
따라서, 인터페이스를 너무 크게 정의하기보단 필요한 만큼만 정의하고 클래스는 입맛에 맞게 필요한 인터페이스들을 구현하도록 유도!
D(DIP. 의존성 역전 원칙)
웹 서버 프레임워크 내에서 많이 나오는 원칙인데,
이 원칙은 하위 수준 모듈(구현 클래스)보다 상위 수준 모듈(인터페이스)에 의존을 해야한다는 의미.
- 예를 들어서, 데이터베이스라는 클래스가 있다고 가정을 해보겠습니다.
- 데이터베이스의 원천은 로컬 스토리지가 될 수도 있고 클라우드 스토리지가 될 수도 있어요.
- 이 때, 데이터베이스의 원천을 로컬 스토리지 타입 혹은 클라우드 스토리지 타입으로 한정하는 것이 아닙니다.
- 그보다 상위 수준인 스토리지 타입으로 한정을 하는 것이 맞아요!
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' 카테고리의 다른 글
추가 학습 자료 (0) | 2023.07.27 |
---|---|
[실습] 도서관 프로그램 (0) | 2023.07.27 |
객체 지향 프로그래밍 (0) | 2023.07.27 |
[실습] 카페 주문 프로그램 (0) | 2023.07.27 |
고급 타입 (0) | 2023.07.27 |