1. 좋은 객체 지향 설계의 5가지 원칙(SOLID)

Yuri JI·2023년 1월 4일
0
  • 클린코드 저자 로버트 마틴이 좋은 객체 지향 설계의 5가지 원칙을 정리

    • SRP : 단일 책임 원칙
    • OCP : 개방-폐쇄 원칙
    • LSP : 리스코프 치환 원칙
    • ISP : 인터페이스 분리 원칙
    • DIP : 의존관계 역전 원칙
  • SRP 단일 책임 원칙 : Single Responsibility Principle

    • 하나의 클래스는 하나의 책임만 가져야한다.
    • 하지만 책임이라는 것이 모호하다 (책임의 크고 작음, 문맥과 상황에 따라 다름)
    • 단일 책임의 기준 : 변경
      • 변경이 있을 때 파급 효과가 적으면 단일 책임 원칙을 잘 따른 것이다.
    • 계층을 잘 나눠야하는 이유이다. 단일 책임 원칙을 지키지않으면 유지보수가 빡세진다.
  • ⭐ OCP 개방 폐쇄 원칙 : Opne/Closed Principle 🤨

    • 소프트웨어 요소는 확장에서는 열려있으나 변경에는 닫혀있어야 한다.
    • 다형성을 활용해보자
    • 인터페이스를 구현한 새로운 클래스를 하나 만들어서 새로운 기능을 구현 …. !
    • 문제점
      public class MemeberService {
      	// MemberRepository m = new MemoryMemberRepository(); // 기존코드
      	MemberRepository m = new JdbcMemberRepository(); // 변경코드
      }
      • 구현 객체를 변경하려면 클라이언트 코드를 변경해야 한다… !

      • 다형성은 사용했지만, OCP 원칙을 지킬 수 없다.

        = 다시 MemoryMemberRepository로 변경하고 싶다면 MemberService클래스 코드의 변경이 필요하다. 
        
        =  MemberService 클라이언트가 구현 클래스를 직접 선택하기 때문에, 구현 객체를 변경하려면 클라이언트 코드를 변경해야 한다.

        ⭐ 해결방법

      • 객체를 생성하고, 연관관계를 맺어주는 별도의 조합, 설정자가 필요하다.

      • 이걸 스프링 컨테이너가 해준다 !

  • LSP 리스코프 치환 원칙 : Liskov substitution principle

    프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야한다.

    • 예를 들어, 자동차 인터페이스가 있다. 기능을 구현하려한다. 악셀을 밟으면 앞으로 가는 기능을 만들었다. 근데 내가 뒤로 가라고 클래스를 만들었다. 컴파일 오류는 안 난다. → LSP 위반 ! 느리더라도 앞으로만 가면 LSP 지키는 것이다.
    • 따라서, 리스코프 치환 원칙은 컴파일 성공하는 것을 넘어서, 하위 클래스는 인터페이스 규약(엑셀을 밟으면 앞으로 간다)을 기능적으로 보장해줘야한다.
    • why? 인터페이스를 구현한 구현체를 믿고 사용하기 위해서, 다형성 지원을 위한 원칙이다.
  • ISP 인터페이스 분리 원칙 : Interface segregation principle

    • 특정 클라이언트를 위한 인터페이스가 여러 개가 범용 인터페이스 하나보다 낫다.
    • 자동차 인터페이스가 너무 덩치가 커져서 운전 인터페이스, 정비 인터페이스로 분리하면, 사용자 클라이언트도 운전자 클라이언트, 정비사 클라이언트로 분리할 수 있다.
    • 왜? 분리하면 정비 인터페이스 자체가 변해도 운전자 클라이언트에 영향을 주지 않는다.
    • 인터페이스가 명확해지고, 대체 가능성이 높아진다. (덩어리가 작으면 기능 구현및 유지보수가 편하다 ~)
  • ⭐ DIP 의존 관계 역전 : Dependency inversion principle

    • 추상화에 의존해야지, 구체화에 의존하면 안된다. = 의존성 주입

    • → 구현 클래스에 의존하지 말고, 인터페이스에 의존하다.

    • → 클라이언트는 인터페이스만 바라보면 된다.

    • → 역할(Role)에 의존해야한다.

    • 왜? 클라이언트가 인터페이스에 의존해야 유연하게 구현체를 변경할 수 있다. 구현체에 의존하면 변경이 어려워진다.

      // MemberService클래스는 MemberRepository를 필드로 가지면서,
      // new로 MemoryMemberRepostiory를 할당 
      // -> 인터페이스에 의존하지만 동시에 구현체에도 의존하고 있다. **-> DIP 위반** 
      // 의존한다는 것 = 안다. MemberService가 MemberRepostiory(인터페이스)뿐만 아니라, MemoryMemberRepostiory(구현 클래스)도 알고 있다
      // MemberService 클라이언트가 구현 클래스를 직접 선택하고 있는 것이다. 
      public class MemeberService {
      	MemberRepository m = new MemoryMemberRepository(); // 기존코드
      	MemberRepository m = new JdbcMemberRepository(); // 변경코드
      }
    • 해결 방법

      • MemberService는 MemberRepository 인터페이스(추상)만 의존하도록 설계해야한다.

정리

객체 지향의 핵심은 다형성 이지만, 다형성 만으로는 부족하다.

다형성만으로는 구현 객체를 변경할 때 클라이언트 코드(MemberService)도 함께 변경해야하고,

이는 OCP(개방 폐쇄 원칙), DIP(의존 관계 역전)를 지키지 못 하는 것이다.

→ 무언가가 더 필요하다 !

📌 다형성(polymorphism)이란?
 부모-자식 상속 관계에 있는 클래스에서상위 클래스가 동일한 메시지로 하위 클래스들을 서로 다르게 동작시키는 객체 지향 원리이다.
profile
안녕하세요 😄

0개의 댓글