Skip to main content
soso01 blog
  1. Posts/

오브젝트 9장 유연한 설계

·4 mins

01. 개방-폐쇄 원칙 #

  • 소프트웨어 개체는 확장에 대해 열려 있어야 학, 수정에 대해서는 닫혀 있어야 한다.
    • 확장에 대해 열려 있다 : 애플리케이션의 요구사항이 변경될 때 이 변경에 맞게 새로운 ‘동작’을 추가하여 기능을 확장한다.
    • 수정에 대해 닫혀 있다 : 기존의 코드를 수정하지 않고도 애플리케이션의 동작을 추가하거나 변경할 수 있다.

컴파일 의존성을 고정시키고 런타임 의존성을 변경하라 #

  • 사실 개방 폐쇄 원칙은 런타임 의존성과 컴파일타임 의존성에 관한 이야기다.
    • 이전 예제 DIscountPolicy 에서 자식 클래스를 추가함으로써 할인정책이 없거나 중복 적용이 가능한 기능을 확장했다.
    • 단순히 새로운 클래스를 추가하는 것만으로 Movie를 새로운 컨텍스트에 사용되도록 확장할 수 있었음.
  • 개방-폐쇄 원칙을 수용하는 코드는 컴파일타임 의존성을 수정하지 않고도 런타임 의존성을 쉽게 변경할 수 있다.

추상화가 핵심이다 #

  • 개방 폐쇄 원칙의 핵심은 추상화에 의존하는 것이다.
    • 추상화를 사용해 생략된 부분을 문맥에 적합한 내용으로 채워넣음으로써 각 문맥에 적합하게 기능을 구체화하고 확장할 수 있다.
    • 개방 폐쇄 원칙의 관점에서 생략되지 않고 남겨지는 부분은 다양한 상황에서의 공통점을 반영한 추상화의 결과물이다.
    • 추상화를 통해 생략된 부분은 확장의 여지를 남긴다. 이것이 추상화가 개방폐쇄 원칙을 가능하게 만드는 이유이다.
  • 명시적 의존성과 의존성 해결 방법을 통해 컴파일타임 의존성을 런타임 의존성으로 대체함으로써 실행 시에 객체의 행동을 확장할 수 있다.
    • 핵심은 추상화다.
    • 올바른 추상화를 설계하고 추상화에 대해서만 의존하도록 관계를 제한함으로써 설계를 유연하게 확장할 수 있다.
  • 변경에 의한 파급효과를 최대한 피하기 위해 변하는 것과 변하지 않는 것이 무엇인지 이해하고 이를 추상화의 목적으로 삼아야만 한다.
  • 추상화가 수정에 대해 닫혀있을 수 있는 이유는 변경되지 않을 부분을 신중하게 결정하고 올바른 추상화를 주의 깊게 선택했기 때문이다.

02. 생성 사용 분리 #

  • 유연하고 재사용 가능한 설계를 위해 객체 생성과, 객체 사용을 분리해야 한다. (생성과 사용 분리)
  • 사용으로부터 생성을 분리하는 데 사용되는 가장 보편적인 방법은 객체를 생성할 책임을 클라이언트로 옮기는 것이다.

FACTORY 추가하기 #

  • 생성 책임을 클라이언트로 옮긴 배경에는 Movie는 특정 컨텍스트에 묶여서는 안 되지만, 클라이언트는 묶여도 상관이 없다는 전제가 깔려 있다.
    • 하지만 클라이언트도 특정한 컨텍스트에 묶이길 원하지 않는다면?
    • 이 경우 객체 생성과 관련된 책임만 전담하는 별도의 객체를 추가하고 클라이언트에서 사용하도록 만들 수 있다.
    • 이처럼 생성과 사용을 분리하기 위해 객체 생성에 특화된 객체를 FACTORY라고 부른다.

순수한 가공물에게 책임 할당하기 #

  • FACTORY는 도메인 모델이 아닌, 순수하게 기술적 결정으로 생성된 클래스이다.
    • 전체적으로 결합도를 낮추고 재사용성을 높이기 위해서 도메인 개념에게 할당되어 있던 객체 생성 책임을 도메인 개념과는 아무런 상관이 없는 가공의 객체로 이동시킨 것이다.
  • 모든 책임을 도메인 객체에게 할당하면 낮은 응집도, 높은 결합도, 재사용성 저하와 같은 심각한 문제점에 봉착하게 될 가능성이 높아진다.
    • 이 경우 도메인 개념을 표현한 객체가 아닌 설계자가 편의를 위해 임의로 만들어낸 가공의 객체에게 책임을 할당해서 문제를 해결해야 한다.
    • 이러한 도메인과 무관한 인공적 객체를 PURE FABRICATION이라고 부른다.
  • 이런 측면에서 객체지향이 실세계의 모방이라는 말은 옳지 않다.
    • 객체지향 애플리케이션은 도메인 개념뿐만 아니라 설계자들이 임의로 창조한 인공적인 추상화를 포함한다.
    • 인공적으로 창조한 객체들이 도메인 개념을 반영하는 객체들보다 오히려 더 많은 비중을 차지하는 것이 일반적이다.
  • 설계자로서 우리의 역할은 도메인 추상화를 바탕으로 애플리케이션 로직을 설계하는 동시에 품질의 측면에서 균형을 맞추는 데 필요한 객체들을 창조하는 것이다.
    • 먼저 도메인의 본질적인 개념을 표현하는 추상화를 이용해 애플리케이션을 구축하고, 도메인 개념이 만족스럽지 않다면 인공적인 객체를 창ㅇ조하자.
    • 객체지향이 실세계를 모방해야 한다는 주장에 현혹될 필요가 없다.

03. 의존성 주입 #

  • 사용하는 객체가 아닌 외부의 독립적인 객체가 인스턴스를 생성한 후 이를 전달해서 의존성을 해결하는 방법을 의존성 주입이라고 부른다.
  • 의존성 주입은 의존성을 해결하기 위해 의존성을 객체 퍼블릭 인터페이스에 명식적으로 드러내서 외부에서 필요한 런타임 의존성을 전달할 수 있도록 만드는 방법을 포괄하는 명칭이다.
    • 의존성 주입은 생성자 주입 / setter 주입 / 메서드 주입으로 나뉜다.

숨겨진 의존성은 나쁘다. #

  • 의존성 주입 외에도 의존성을 해결할 수 있는 다양한 방법이 있고 그중 SERVICE LOCATOR 패턴을 소개한다.
  • SERVICE LOCATOR
    • 의존성을 관리하는 객체 저장소를 따로 두고 여기서 이 저장소에 메시지를 전달하는 방법.
    • 의존성이 숨겨지므로 좋은 방법은 아님.
    • 의존성을 구현 내부로 감출 경우 의존성과 관련된 문제가 런타임에서 발견되므로 디버깅이 어렵다.
    • 명시적으로 퍼블릭 인터페이스에 의존성을 드러내자.

04. 의존성 역전 원칙 #

추상화와 의존성 역전 #

  • 객체 사이의 협력이 존재할 때 그 협력의 본질을 담고 있는 것은 상위 수준의 정책이다.
    • 이런 상위 수준의 클래스가 하위 수준의 클래스에 의존한다면 하위 수준의 변경에 의 해 상위 수준의 클래스가 영향을 받게 될 것이다.
    • (ex. Movie클래스가 AmountDiscount에 직접 의존하는 경우)
    • 추상화로 해결한다. (DiscountPolicy)
  • 유연하고 재사용 가능한 설계를 원한다면 모든 의존성의 방향이 추상 클래스나 인터페이스와 같은 추상화를 따라야 한다.
  • 정리
    • 상위 수준의 모듈은 하위 수준의 모듈에 의존해서는 안 된다. 둘 모두 추상화에 의존해야 한다.
    • 추상화는 구체적인 사항에 의존해서는 안된다. 구체적인 사항은 추상화에 의존해야 한다.
  • 이를 의존성 역전 원칙이라 부른다.
    • 역전이란? 전통적인 절차형 프로그래밍과 의존성 방향이 반대 방향으로 나타나기 때문

05. 유연성에 대한 조언 #

  • 항상 유연한 설게가 좋은것은 아니다.
    • 설계의 미덕은 단순함과 명확함으로 나온다.
    • 단순하고 명확한 설계의 코드는 읽기 쉽고 이해하기 편하다.
    • 하지만 유연한 설계는 이와는 다른 길을 걷는다.
    • 변경하기 쉽고 확장하기 쉬운 구조를 만들기 위해서는 단순함과 명확함의 미덕을 버리게 될 가능성이 높다.
  • 유연한 설계라는 말의 이면에 복잡한 설계라는 의미가 숨어 있다.
    • 유연한 설계의 이런 양면성은 객관적으로 설계를 판단하기 어렵게 만든다.
    • 미래에 변경이 일어날지도 모른다는 막연한 불안감은 불필요하게 복잡한 설계를 낳는다.
  • 설계가 유연할수록 클래스 구조와 객체 구조 사이의 거리는 점점 멀어진다.
    • 유연함은 단순성과 명확성의 희생 위에서 자라난다.
    • 유연한 설계를 단순하고 명확하게 만드는 유일한 방법은 사람들 간의 긴밀한 커뮤니케이션뿐이다.
    • 복잡성이 필요한 이유와 합리적인 근거를 제시하지 않는다면 어느 누구도 설계를 만족스러운 해법으로 받아들이지 않을 것이다.
  • 불필요한 유연성은 불필요한 복잡성을 낳는다.
    • 유연성은 코드를 읽는 사람들이 복잡함을 수용할 수 있을 때만 가치가 있다.