ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 객체지향 설계 원칙 S.O.L.I.D
    카테고리 없음 2023. 12. 20. 17:20

    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 개방 폐쇄원칙) -> 인터페이스 혹은 상속을 잘쓰자

     

    확장에서는 OPEN되어 있어야하는데 수정에 대해서는 답혀있어야한다.

     

    기존코드를 변경하지 않고도 기능을 확장 할 수 있어야한다.

     

    즉, 인터페이스나 상속을 통해서 이를 해결할 수 있어야 한다.


    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 {} // 이제 위배되는 것은 아무것도 없네요!

     

     

    당연히 FLYING BIRD 가 들어갔으면 위배될 수 있는데 NONFLYINGBIRD는 위배되는것이 없기에 가능하다.

     


     

    I (ISP, 인터페이스 분리원칙)

     

    클래스는 자신이 사용하지 않는 인터페이스의 영향을 받지 않아야한다.

    해당 클래스에게 무의미한 메소드의 구현을 막자

     

    즉 인터페이스는 반드시 필요한 만큼 정의하고 입맛에 맞는 구현을 잘해서 

     

    최적화를 해주는것이 중요하다.

     


    DIP 원칙

     

    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("클라우드 데이터");

     

    즉 인터페이스 A가 있고 

     

    그걸 받은 CLASS를 B,C라 하면

     

    이걸 구현하는 CLASS는 B,C의 상위 객체인 A를 연결해서  B,C를 이용할 수 있다. 

Designed by Tistory.