Skip to main content

9. 유연한 설계

01. 개방-폐쇄 원칙#

  • 개방-폐쇄 원칙(Open-Closed Principle, OCP) 는 소프트웨어 개체(클래스, 모듈, 함수 등)은 확장에 대해 열려 있어야하고, 수정에 대해서는 닫혀 있어야 합니다.
  • 키워드는 확장과 수정입니다.
    • 확장에 대해 열려 있다 : 애플리케이션의 요구사항이 변경될 때 이 변경에 맞게 새로운 동작을 추가해서 애플리케이션의 기능을 확장할 수 있다.
    • 수정에 대해 닫혀 있다 : 기존의 코드를 수정하지 않고도 애플리케이션의 동작을 추가하거나 변경할 수 있다.

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

  • 개방-폐쇄 원칙을 수용하는 코드는 컴파일타임 의존성을 수정하지 않고도 런타임 의존성을 쉽게 변경할 수 있으면 됩니다.

추상화가 핵심이다#

  • 개방-폐쇄 원칙의 핵심은 추상화에 의존하는 것입니다.
  • 명시적 의존성과 의존성 해결 방법을 통해 컴파일타임 의존성을 런타임 의존성으로 대체함으로써 실행 시에 객체의 행동을 확장할 수 있습니다.
  • 추상화를 하더라도 모든 수정에 대해 설계가 폐쇄되는 것은 아닙니다.
  • 추상화가 수정에 대해 닫혀 있을 수 있는 이유는 변경되지 않을 부분을 신중하게 결정하고 결정하고 올바른 추상화를 주의 깊게 선택했기 때문입니다.

02. 생성 사용 분리#

  • 결합도가 높아질수록 개방-폐쇄 원칙을 따르는 구조를 설계하기 어려워집니다.
  • 유연하고 재사용 가능한 설계를 원한다면 객체와 관련된 두 가지 책임을 서로 다른 객체로 분리해야 합니다. 즉, 생성과 사용을 분리(separating use from creation) 해야 합니다.

FACTORY 추가하기#

  • 생성과 사용을 분리하기 위해 객체 생성에 특화된 객체를 FACTORY라고 부릅니다.

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

  • 책임 할당의 가장 기본이 되는 원칙은 책임을 수행하는 데 필요한 정보를 가장 많이 알고 있는 INFROMATION EXPERT에게 책임을 할당하는 것입니다.
  • 모든 책임을 도메인 객체에게 할당하면 낮은 응집도, 높은 결합도, 재사용성 저하와 같은 문제점에 봉착하게 될 가능성이 높아집니다.
  • 어떤 행동을 추가하려고 하는데 이 행동을 책임질 마땅한 도메인 개념이 존재하지 않든다면 PURE FABRICATION을 추가하고 이 객체에게 책임을 할당합니다.
  • 대부분의 디자인 패턴은 PURE FABRICATION을 포함합니다.

03. 의존성 주입#

  • 사용하는 객체가 아닌 외부의 독립적인 객체가 인스턴스를 생성한 후 이를 전달해서 의존성을 해결하는 방법을 의존성 주입(Dependency Injection) 이라고 부릅니다.
  • 의존성 주입에서는 의존성을 해결하는 3가지 방법이 있습니다.
    • 생성자 주입(constructor injection) : 객체를 생성하는 시점에 생성자를 통한 의존성 해결
    • setter 주입(setter injection) : 객체 생성 후 setter 메서드를 통한 의존성 해결
    • 메서드 주입(method injection) : 메서드 실행 시 인자를 이용한 의존성 해결

C#에서는 setter 주입 대신 프로퍼티 주입(property injection) 이라는 용어를 사용합니다.

숨겨진 의존성은 나쁘다#

  • 의존성 주입 외에도 의존성을 해결할 수 있는 다양한 방법이 존재하며 대표적인 방법은 SERVICE LOCATOR 패턴입니다.
  • SERVICE LOCATOR는 의존성을 해결할 객체들을 보관하는 일종의 저장소입니다.
  • 외부에서 객체에게 의존성을 전달하는 의존성 주입과 달리 SERVICE LOCATOR의 경우 객체가 직접 SERVICE LOCATOR에게 의존성을 해결해줄 것을 요청합니다.
  • SERVICE LOCATOR 패턴은 서비스를 사용하는 코드로부터 서비스가 누구인지, 어디에 있는지를 몰라도 되게 해줍니다.
  • SERVICE LOCATOR 패턴의 가장 큰 단점은 의존성을 감춘다는 것입니다.
  • 의존성을 구현 내부로 감추도록 강요하는 SERVICE LOCATOR는 캡슐화를 위반할 수 밖에 없습니다.
  • 위의 핵심은 명시적인 의존성이 숨겨진 의존성보다 좋습니다.
  • 의존성 주입을 지원하는 프레임워크를 사용하지 못하는 경우나 깊은 호출 계층에 걸쳐 동일한 객체를 계속해서 전달해야 하는 고통이 있는 경우, SERVICE LOCATOR 패턴을 사용하는 것을 고려해야 합니다.

04. 의존성 역전 원칙#

추상화와 의존성 역전#

  • 추상화에 의존하도록 수정하면 하위 수준 클래스의 변경으로 인해 상위 수준의 클래스가 영향 받는 것을 방지할 수 있습니다.
  • 의존성 역전 원칙(Dependency Inversion Principle, DIP)라고 부릅니다
    • 상위 수준의 모듈은 하위 수준의 모듈에 의존해서는 안됩니다.
    • 추상화는 구체적인 사항에 의존해서는 안됩니다.

의존성 역전 원칙과 패키지#

  • 역전은 의존성의 방향뿐만 아니라 인터페이스의 소유권에도 적용됩니다.
  • 유연하고 재사용 가능하며 컨텍스트에 독립적인 설계는 전통적인 패러다임이 고수하는 의존성의 방향을 역전시킵니다.
  • 훌륭한 객체지향 설계를 위해서는 의존성을 역전시켜야 합니다. 그리고 의존성을 역전시켜야만 유연하고 재사용 가능한 설계를 얻을 수 있습니다.

05. 유연성에 대한 조언#

유연한 설계는 유연성이 필요할 때만 옳다#

  • 변경하기 쉽고 확장하기 쉬운 구조는 단순함과 명확함의 미덕을 버리게 될 가능성이 높습니다.
  • 유연성은 항상 복잡성을 수반합니다.
  • 불필요한 유연성은 불필요한 복잡성을 낳습니다.

협력과 책임이 중요하다#

  • 객체의 협력과 책임은 중요합니다.
  • 설계를 유연하게 만들기 위해서는 먼저 역할, 책임, 협력에 초점을 맞춰야합니다.
  • 객체를 생성하는 방법에 대한 결정은 모든 책임이 자리를 잡은 후 가장 마지막 시점에 내리는 것이 적절합니다.
  • 의존성을 관리해야하는 이유는 역할, 책임, 협력의 관점에서 설계가 유연하고 재사용 가능해야 합니다.
Last updated on