객체 지향 설계를 할 때 S.O.L.I.D 원칙을 따라 설계를 하는 것이 필수다.
S ( SRP. 단일 책임 원칙 )
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}`);
}
}
O ( OCP. 개발 폐쇄 원칙 ) ▶ 인터페이스 혹은 상속을 잘 쓰자!
- 클래스는 확장에 대해서는 열려 있어야 하며, 수정에 대해서는 닫혀 있어야 한다는 원칙
- 클래스의 기존 코드를 변경하지 않고도 기능을 확장할 수 있어야 한다.
- 즉, 인터페이스나 상속을 통해 이를 해결할 수가 있다. ( 부모 클래스의 기존 코드 변경을 하지 않고 기능을 확장하는데 아무런 문제가 없기 때문 )
L ( LSP. 리스코프 치환 원칙 )
LSP 원칙
- 서브타입은 기반이 되는 슈퍼타입을 대체할 수 있어야 한다는 원칙
- 다시 말해, 자식 클래스는 부모 클래스의 기능을 수정하지 않고도 부모 클래스와 호환되어야 한다.
- 논리적으로 엄격하게 관계가 정립이 되어야 한다는 의미
잘못된 예시
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 {} // 이제 위배되는 것은 아무것도 없네요!
I ( ISP. 인터페이스 분리 원칙 )
- 클래스는 자신이 사용하지 않는 인터페이스의 영향을 받지 않아야 한다.
- 즉, 해당 클래스에게 무의미한 메소드의 구현을 막자는 의미
- 인터페이스를 너무 크게 정의하기보다는 필요한 만큼 정의하고 클래스는 입맛에 맞게 필요한 인터페이스들을 구현하도록 유도한다.
D ( DIP. 의존성 역전 원칙 )
DIP 원칙
- DIP는 Java의 Spring 프레임워크나 Node.js의 Nest.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("클라우드 데이터");
'IT' 카테고리의 다른 글
[IT] 레이턴시 ( Latency ) (2) | 2024.10.24 |
---|---|
[IT] 빅 엔디안, 리틀 엔디안 ( 바이트 저장 순서 ) (1) | 2024.10.21 |
[IT] 테스트 코드 ( Test Code ) (1) | 2024.09.28 |
[IT] 계층형 아키텍처 패턴 ( Layered Architecture Pattern ) (0) | 2024.09.27 |
[IT] 아키텍처 패턴 ( Architecture Pattern ) (1) | 2024.09.27 |