-
2023 - 08 - 03 객체지향 설계원칙 SOLIDToday I Learned/TIL 08 2023. 8. 1. 19:56
S.O.L.I.D.
1. 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}`); } }
2. O (OCP. 개방 폐쇄원칙) → 인터페이스 혹은 상속을 잘 쓰기
클래스는 확장에 대해서는 열려 있어야 하고, 수정에 대해서는 닫혀 있어야 한다.
클래스의 기존 코드를 변경하지 않고도 기능을 확장할 수 있어야 한다.
이는 인터페이스나 상속을 통해 해결이 가능하다.
https://sangwoorhie.tistory.com/184
2023 - 07 - 10 객체지향 설계 5가지 원칙 SOLID
객체지향은 소프트웨어의 핵심을 기능이 아닌 객체로 삼으며, 각각의 역할을 정의하는 것에 초점을 맞춘다. 객체지향 소프트웨어의 특징 1. 캡슐화, 다형성, 클래스상속을 지원한다. 2. 데이터
sangwoorhie.tistory.com
3. L (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 {} // 이제 위배되는 것은 아무것도 없네요!
4. I (ISP. 인터페이스 분리 원칙)
클래스는 자신이 사용하지 않는 인터페이스의 영향을 받지 않아야 한다. 즉, 해당 클래스에게 무의미한 메소드의 구현을 막기 위함. 따라서 인터페이스를 너무 크게 정의하기보단, 필요한 만큼 정의하고 클래스는 입맛에 맞게 필요한 인터페이스들을 구현하도록 유도해야 한다.
5. 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("클라우드 데이터");
'Today I Learned > TIL 08' 카테고리의 다른 글