저장을 습관화
객체 지향 설계 원칙 - S.O.L.I.D 본문
1. S(Single Responsibility Principle, 단일 책임 원칙)
SOLID 중에서 가장 중요한 사항이다.
하나의 클래스는 다른 클래스의 역할과 권한을 침범해서는 안되며,
하나의 클래스가 해야하는 역할과 권한은 해당 클래스만 가질 수 있도록 보장되어야 한다.
잘못된 예시)
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. O(Open-Closed Principle, 개방 폐쇄 원칙)
클래스는 확장에 대해서는 열려 있어야 하되, 수정에 대해서는 닫혀 있어야 한다.
클래스의 기존 코드를 변경하지 않고도 기능을 확장할 수 있어야한다.
인터페이스나 상속을 생각해보자
자식 클래스는 부모 클래스의 기본 코드를 변경하지 않고도 기능을 확장하였다.
3. L(Liskov Substitution Principle, 리스코프 치환 원칙)
서브타입은 그 기반이 되는 슈퍼타입을 대체할 수 있어야 한다.
자식 클래스는 부모 클래스의 기능을 수정하지 않고도 부모 클래스와 호환되어야 하며
논리적으로 엄격하게 관계가 정립 되어야 한다.
잘못된 예시)
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. I(Interface Segregation Principle, 인터페이스 분리 원칙)
클래스는 자신이 사용하지 않는 인터페이스의 영향을 받지 않아야 한다.
클래스에게 무의미한 메소드의 구현을 막을 수 있게끔
인터페이스를 너무 크게 정의하기보단 필요한 만큼한 적절히 정의하고
클래스는 필요에 맞는 인터페이스를 구현하도록 유도해야한다.
5. D(Dependency Inversion Principle, 의존성 역전 원칙)
Java-Spring이나 Node.js-Nest.js와 같이 웹 서버 프레임워크 내에서 많이 나오는 원칙이며,
하위 수준 모듈(구현 클래스)보다 상위 수준 모듈(인터페이스)에 의존해야한다는 의미이다.
예를 들어 클래스 'DataBase'가 있다.
'DataBase'의 원천은 'LocalStorage'가 될 수도 있고, 'CloudStorage'가 될 수도 있다.
이 때 'DataBase'의 원천을 'LocalStorage' 타입이나 'CloudStorage' 타입으로 한정하지 않고,
그보다 상위 수준인 'Storages' 타입으로 한정하여야 한다.
예시)
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 {
// MyLocalStorage, MyCloudStorage 같은 하위 수준 모듈에 의존하지 않고,
// 상위 수준 모듈인 MyStorage 타입을 의존한다.
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 - class-transformer 데이터 타입 변환 (0) | 2023.09.12 |
---|---|
TypeScript 연습 - 도서관 프로그램 만들기 (0) | 2023.08.01 |
객체 지향 프로그래밍 - 인터페이스 (0) | 2023.08.01 |
객체 지향 프로그래밍 - 추상 클래스 (0) | 2023.08.01 |
객체 지향 프로그래밍 - 상속 (0) | 2023.08.01 |