좋은 객체 지향 설계를 위한 5가지 원칙 - SOLID

gwjeon·2021년 9월 19일
10


학습참조
인프런-스프링 핵심 원리 기본편(김영한님)


✅ SOLID란?

SOLID란 좋은 객체지향 소프트웨어 설계를 위한 5가지 원칙이다. 5가지 원칙은 다음과 같다.

  • SRP: 단일 책임 원칙(Single responsibility principle)
  • OCP: 개방-폐쇄 원칙(Open/closed principle)
  • LSP: 리스코프 치환 원칙(Liskov substitution principle)
  • ISP: 인터페이스 분리 원칙(Interface segregation principle)
  • DIP: 의존관계 역전 원칙(Dependency inversion principle)

✅ SRP 단일 책임 원칙(Single responsibility principle)

단일 책임 원칙(single responsibility principle)이란 모든 클래스는 하나의 책임만 가지며, 클래스는 그 책임을 완전히 캡슐화해야 함을 일컫는다. 클래스가 제공하는 모든 기능은 이 책임과 주의 깊게 부합해야 한다. [위키백과]

  • 한 클래스는 하나의 책임만 가져야 한다.
  • 하나의 책임이라는 기준은 모호함.
    • 클 수 있고, 작을 수 있다.
    • 문맥과 상황에 따라 다르다.
  • 원칙을 잘 지켰느냐의 중요한 기준은 변경임. 변경이 있을 때 파급 효과가 적으면 단일 책임의 원칙을 잘 따른 것임.
  • 예시로 UI 변경시 View단 외에도 여러 애플리케이션 코드들에 영향을 끼친다면 단일 책임의 원칙을 잘 지키지 못한 것임.
  • 설계시 기능과 모듈 단위로 계층을 나누는 것(예를 들어 MVC패턴) 역시 이러한 책임 원칙을 지키기 위한 것임.

✅ OCP 개방-폐쇄 원칙(Open/closed principle)

개방-폐쇄 원칙(OCP, Open-Closed Principle)은 '소프트웨어 개체(클래스, 모듈, 함수 등등)는 확장에 대해 열려 있어야 하고, 수정에 대해서는 닫혀 있어야 한다'는 프로그래밍 원칙이다. [위키백과]

  • 가장 중요시 되는 원칙.

  • 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.

    • 어떻게 코드의 변경없이 확장이 가능한가?
    • 다형성을 활용하면 가능
    • 참조: [JAVA] 다형성이란?
      • Taxi의 차량이 확장되어도 확장된 차량 소스코드만 새로이 생기는 것이지 TaxiDriver 입장에서 기존 코드에는 변경이 없다.
      • 하지만 실질적으로 Main 함수에 인스턴스를 초기화하는 부분에서 다른 차량의 인스턴스로 교체 해주어야 함으로 변경이 일어남. OCP 원칙을 위반한 것.
      • 분명 다형성을 사용했지만 원칙을 재대로 지킬 수 없음.
      • 이를 해결 하기 위해선 객체를 생성하고, 연관관계를 맺어주는 별도의 조립, 설정자가 필요함.
      • 그 역할을 스프링과 같은 프레임워크에서 가능하게끔 만들어 줌. (Dependency Injection과 같은..)

✅ LSP 리스코프 치환 원칙(Liskov substitution principle)

컴퓨터 프로그램에서 자료형 S가 자료형 T의 하위형이라면 필요한 프로그램의 속성(정확성, 수행하는 업무 등)의 변경 없이 자료형 T의 객체를 자료형 S의 객체로 교체(치환)할 수 있어야 한다는 원칙이다. [위키백과]

  • 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
  • 다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 한다는 것, 다형성을 지원하기 위한 원칙, 인터페이스를 구현한 구현체는 믿고 사용하려면 이 원칙이 필요하다.
  • 단순히 컴파일에 성공하는 것을 넘어선다는 이야기
  • 자동차 인터페이스의 엑셀은 앞으로 가라는 기능, 뒤로 가게 구현하면 LSP를 위반한다.
  • 즉 인터페이스 설계에서 부터 인터페이스를 구현하고자 하는 구현체는 인터페이스에서 요구하는 규약을 잘 지켜야 한다.

✅ ISP 인터페이스 분리 원칙(Interface segregation principle)

인터페이스 분리 원칙은 클라이언트가 자신이 이용하지 않는 메서드에 의존하지 않아야 한다는 원칙이다. 인터페이스 분리 원칙은 큰 덩어리의 인터페이스들을 구체적이고 작은 단위들로 분리시킴으로써 클라이언트들이 꼭 필요한 메서드들만 이용할 수 있게 한다. 이와 같은 작은 단위들을 역할 인터페이스라고도 부른다. 인터페이스 분리 원칙을 통해 시스템의 내부 의존성을 약화시켜 리팩토링, 수정, 재배포를 쉽게 할 수 있다. [위키백과]

  • 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
  • 자동차 인터페이스 -> 운전 인터페이스, 정비 인터페이스로 분리.
  • 사용자 클라이언트 -> 운전자 클라이언트, 정비사 클라이언트로 분리.
  • 분리하면 정비 인터페이스 자체가 변해도 운전자 클라이언트에 영향을 주지 않음.
  • 인터페이스가 명확해지고, 대체 가능성이 높아진다.
  • 즉 만약 어떠한 기능을 구현하기 위한 인터페이스가 존재한다면 가능한 그 안에서도 기능 별로 분리가 가능 한 만큼 분리 하는것이 좋다는 얘기임.

✅ DIP 의존관계 역전 원칙(Dependency inversion principle)

의존관계 역전 원칙은 소프트웨어 모듈들을 분리하는 특정 형식을 지칭한다. 이 원칙을 따르면, 상위 계층(정책 결정)이 하위 계층(세부 사항)에 의존하는 전통적인 의존관계를 반전(역전)시킴으로써 상위 계층이 하위 계층의 구현으로부터 독립되게 할 수 있다. 이 원칙은 다음과 같은 내용을 담고 있다.
첫째, 상위 모듈은 하위 모듈에 의존해서는 안된다. 상위 모듈과 하위 모듈 모두 추상화에 의존해야 한다.
둘째, 추상화는 세부 사항에 의존해서는 안된다. 세부사항이 추상화에 의존해야 한다.
이 원칙은 '상위와 하위 객체 모두가 동일한 추상화에 의존해야 한다'는 객체 지향적 설계의 대원칙을 제공한다. [위키백과]

  • 프로그래머는 "추상화에 의존해야지, 구체화에 의존하면 안된다." 의존성 주입은 이 원칙을 따르는 방법 중 하나임.
  • 쉽게 이야기해서 클라이언트는 구현 클래스에 의존하지 말고, 인터페이스에 의존하라는 뜻.
  • 다형성과 깊은 관계가 있는데 클라이언트는 인터페이스에만 의존하면 되지, 구현체에는 의존 할 필요가 없다는 것.
    • 참조: [JAVA] 다형성이란?
      • TaxiDriver(클라이언트)는 Taxi라는 인터페이스에만 의존하면 되지, 그 Taxi가 어떤 차인지, 아반떼인지 소나타인지 그랜저인지의 구현체에 대해서는 중요하지 않음.
      • TaxiDriver는 아반떼만 운전을 해보았기 때문에 아반떼만을 Taxi로 사용 할 수 있어서는 안된다는 것임.
      • TaxiDriver는 만약 아반떼가 고장 났을 경우 언제든지 다른 차량으로 교체하여 운전 할 수 있어야 함.
      • 키보드와 같은 입력장치도 마찬가지, 키보드가 고장 났을 경우 언제든지 다른 키보드로 교체할 수 있어야 함.
      • 하지만 실질적으로 Main 함수에 인스턴스를 초기화하는 부분에서 다른 차량의 인스턴스로 교체 해주어야 함으로 이는 Taxi 인터페이스와 Avente, Sonata, Grandeur의 구현체에 모두 의존하고 있는 것임.(추상화에도 의존하고 구체화에도 의존) 즉 DIP를 위반함.
      • 분명 다형성을 사용했지만 원칙을 재대로 지킬 수 없음.
      • 이를 해결 하기 위해선 객체를 생성하고, 연관관계를 맺어주는 별도의 조립, 설정자가 필요함.
      • 그 역할을 스프링과 같은 프레임워크에서 가능하게끔 만들어 줌. (Dependency Injection과 같은..)

profile
ansuzh

4개의 댓글

comment-user-thumbnail
2021년 9월 20일

좋은 글 감사합니다. :)

1개의 답글
comment-user-thumbnail
2021년 9월 28일

SRP의 책임은 Robert C. Martin에 의하면 기능에 대한 결정권자 단위로 분리하는 것을 기본으로 하므로,
모호하기 보다는 연습이 필요한 부분이라고 생각합니다.

1개의 답글