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}`);
}
}
1) 클래스는 확장에 대해서는 열려 이어야 하고 수정에 대해서는 닫혀 있어야 한다.
2) 클래스의 기존 코드를 변경하지 않고도 기능을 확장할 수 있어야 한다.
1) 자식 클래스는 부모 클래스의 기능을 수정하지 않고도 부모 클래스와 호환되어야 한다.
abstract class Bird {
abstract move(): void;
}
class FlyingBird extends Bird {
move() {
console.log("펄럭펄럭~");
}
}
class NonFlyingBird extends Bird {
move() {
console.log("뚜벅뚜벅!");
}
}
class Penguin extends NonFlyingBird {} // 이제 위배되는 것은 아무것도 없네요!
I(ISP. 인터페이스 분리 원칙)
1) 클래스는 자신이 사용하지 않는 인터페이스의 영향을 받지 않아야 한다.
2) 해당 클래스에게 무의미한 메소드의 구현을 막자
D(DIP. 의존성 역전 원칙)
1) 상위 수준 모듈(인터페이스)에 의존을 해야한다.
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("클라우드 데이터");