Featured image of post 8. 개방 폐쇄 원칙

8. 개방 폐쇄 원칙

3부 - 설계 원칙

소프트웨어 개체(Artifact)는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다.

개방 폐쇄 원칙은 소프트웨어 개체의 행위는 확장할 수 있어야 하지만, 이때 개체를 변경해서는 안된다는 원칙이다.

요구사항을 살짝 확장하는 데 소프트웨어를 많이 수정해야 한다면, 그 소프트웨어 시스템을 설계한 아키텍트는 엄청난 실패에 맞닥뜨린 것이다.

OCP는 클래스와 모듈을 설계할 때 도움이 되는 원칙으로 알려져 있지만, 아키텍처 컴포넌트 수준에서 고려할 때 훨씬 중요한 의미를 가진다.

사고 실험: 재무제표 시스템

요구사항

  • 웹 페이지에 표시되는 데이터는 스크롤할 수 있음
  • 음수는 빨간색으로 출력
  • 추가 요구 사항: 동일한 정보를 보고서 형태로 변환해서 흑백 프린터로 출력 가능
    • 페이지 번호가 제대로 메겨져 있어야 함
    • 페이지마다 적절한 머리글과 바닥글 있어야 함
    • 표의 각 열에는 레이블이 있어야 함
    • 음수는 괄호로 감싸야함

소프트웨어 아키텍처가 훌륭하다면 추가 요구사항에 따라 변경되는 코드의 양이 가능한 최소화 될 것이다.(이상적인 변경량은 0)

이러한 경우 서로 다른 목적으로 변경되는 요소를 적절하게 분리하고(단일 책임 원칙), 이들 요소 사이의 의존성을 체계화함으로(의존성 역전 원칙) 변경량을 최소화 할 수 있다.

flowchart LR
    a[재무 데이터]
    b((재무 분석기))
    c[보고서용 재무 데이터]
    d((보고서를 웹에 표시))
    e((보고서를 프린터로 출력))
    
    a --> b
    b --> c
    c --> d
    c --> e
  • 보고서 생성이 두 개의 책임으로 분리 > 웹, 프린터
    • 책임을 분리했다면, 책임 중 하나에서 변경이 발생하더라도 다른 하나는 변경되지 않도록 의존성도 확실히 조직화 해야한다.
    • 행위가 확장될 때 변경이 발생하지 않음을 보장해야한다.

이러한 목적을 달성하려면 처리과정을 클래스 단위로 분할, 컴포넌트 단위로 구분해야 한다.

  • 모든 의존성이 소스 코드 의존성을 나타낸다.
    • FinancialDataMapper는 구현 관계를 통해 FinancialDataGateway를 알고 있지만, FinancialDataGatewayFinancialDataMapper에 대해 알지 못한다.
  • 이중선은 화살표와 오직 한 방향으로만 교차한다.
    • 모든 컴포넌트 관계는 단 방향으로 이루어진다.
    • 화살표는 변경으로부터 보호하려는 컴포넌트를 향하도록 그려진다.

A 컴포넌트에서 발생한 변경으로부터 B 컴포넌트를 보호하려면 반드시 A 컴포넌트가 B 컴포넌트에 의존해야한다.

  • Presenter에서 발생한 변경으로부터 Controller를 보호
  • View에서 발생한 변경으로부터 Presenter를 보호
  • Interactor는 다른 모든 것에서 발생한 변경으로부터 보호

Interactor는 변경 폐쇄 원칙을 가장 잘 준수하는 곳에 위치하고 있는데, 이는 Interactor가 애플리케이션에서 가장 높은 수준의 정책, 가장 중요한 문제를 담당하고 있기 때문이다.

보호의 계층구조는 수준 level이라는 개념을 바탕으로 구성되어야하며, 높은 수준일수록(세부 구현과 멀 수록) 변경에 대해 강력한 보호가 필요하다.

아키텍처 수준에서 개방 폐쇄 원칙은 아키텍트는 기능이 어떻게, 왜, 언제 발생하는지에 따라서 기능을 분리하고, 분리한 기능을 컴포넌트의 계층구조로 조직화한다.

컴포넌트 계층구조를 이와 같이 조족화하면 저수준 컴포넌트에서 발생한 변경으로부터 고수준 컴포넌트를 보호할 수 있다.

방향성 제어

FinancialDataGateway 인터페이스는 Interactor 컴포넌트에서 Database 컴포넌트의 의존성을 역전시키기 위해 FinancialReportGeneratorFinancialDataMapper 사이에 위치한다.

FinancialDataGateway 인터페이스가 없었다면 의존성이 Interactor 컴포넌트에서 Database 컴포넌트로 바로 향하게된다.

정보 은닉

FinancialReportRequester 인터페이스는 FinancialReportControllerInteractor 내부에 대해 너무 많이 알지 못하도록 막기 위해서 존재한다.

  • 인터페이스가 없었다면 ControllerFinancialEntities에 대해 추이 종속성(Transitive dependency)을 가지게 된다.
  • 추이 종속성을 가지게 되면, 소프트웨어 엔티티는 **‘자신이 직접 사용하지 않는 요소에는 절대로 의존해서는 안 된다.’**는 소프트웨어 원칙을 위반하게 된다.

Controller에서 발생한 변경으로부터 Interactor를 보호하는 일의 우선순위가 가장 높지만, 반대로 Interactor에서 발생한 변경으로 부터 Controller도 보호되길 바라기 때문에 Interactor 내부를 은닉한다.

추이 종속성(Transitive dependency)

클래스 A가 클래스 B에 의존하고, 클래스 B가 클래스 C에 의존한다면, 클래스 A는 클래스 C에 의존하게 된다.

  • 클래스 이외의 소프트웨어의 모든 엔티티에도 동일하게 적용된다.
  • 클래스 의존성이 순환적이라면, 모든 클래스가 서로 의존하게 되는 문제가 있다.

결론

개방 폐쇄 원칙은 아키텍처를 떠받치는 원동력 중 하나다. OCP의 목표는 시스템을 확장하기 쉬운 동시에 변경으로 인해 시스템이 너무 많은 영향을 받지 않도록 하는데 있다.

  • 시스템을 컴포넌트 단위로 분리한다.
  • 저수준 컴포넌트에서 발생한 변경으로부터 고수준 컴포넌트를 보호할 수 있는 형태의 의존성 계층구조가 만들어지도록 한다.