OCP

SW 개체는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다.

개체의 행위는 확장할 수 있어야 하지만, 개체를 변경해서는 안된다.
만약, 요구사항을 살짝 확장하는 데 SW를 엄청 수저앻야 한다면, 그 SW 시스템 설계는 실패다.

SW 설계를 공부한 지 얼마 안되는 사람들은 OCP를 클래스와 모듈을 설계할 때 도움되는 원칙이라고 알고 있다.
-> (나 역시... 마찬가지)
하지만 아키텍처 컴포넌트 수중에서 OCP를 고려하면 훨씬 중요한 의미를 갖는다.

사고 실험

  1. 재무제표를 Web으로 보여주는 시스템이 있다고 해보자.
  2. 표시되는 데이터는 스크롤할 수 있으며, 음수는 빨간색으로 출력한다.
  3. 보고서 형태로 변환해서 흑백 프린터로 출력해 달라고 요청
    • 페이지마다 번호
    • 페이지마다 머리글/꼬리글
    • 표의 각 열에는 레이블
    • 음수는 괄호로 묶음

새로운 코드를 작성해야 하는 것은 맞다.
하지만 SW 아키텍처가 좋다면 변경되는 코드의 양이 최소화될 것이다.

HOW?

  • SRP : 서로 다른 목적으로 변경되는 요소를 적절하게 분리
  • DIP : 요소 사이의 의존성을 체계화

SRP를 적용하면 그림은 아래와 같다.

웹/프린트 의 책임을 분리시킨다.
이처럼 책임을 분리한다면, 두 책임 중 하나에서 변경이 발생하더라도 다른 하나는 변경되지 않도록 소스 코드 의존성을 확실히 조직화해야 한다.
또한, 새로 조직화한 구조에서는 행위가 확장될 때 변경이 발생하지 않음을 보장해야 한다.

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

열린 화살표는 사용
닫힌 화살표는 구현/상속 이다.

여기서 모든 의존성이 소스 코드 의존성을 나타낸다는 사실이다.
A 클래스 -> B 클래스 라면, A 클래스에서는 B 클래스에서는 호출하지만, B 클래스는 A 클래스를 모른다.

다른 중요한 점은 이중 선은 화살표와 오직 한 방향으로만 교차한다는 사실이다.

A 컴포넌트에서 발생한 변경으로부터 B 컴포넌트를 보호하려면 반드시 A 컴포넌트가 B 컴포넌트에 의존( A -> B)해야 한다.
그림에서는 Presenter의 변경으로부터 Controller를 보호하고
View의 변경으로부터 Presenter를 보호한다.
Interactor는 모든 것으로부터 보호되는데, 그 이유는 interactor가 업무 규칙을 포함하기 때문이다.
Interactor = 주요 업무, 나머지 = 주변 업무

BUT, Controller는 Presenter와 View에 비해서는 중요한 업무를 한다.
마찬가지로 Presenter 또한 View 보다는 더 중요한 업무를 한다.
-> 이것이 바로 아키텝처 수준에서 OCP가 동작하는 방식이다.
아키텍트는 HOW, WHY, WHEN 발생하는지에 따라서 기능을 분리하고, 분리한 기능을 컴포넌트의 계층구조로 조직화한다.
컴포넌트를 계층구조로 조직화하므로써 저수준 컴포넌트에서 발생한 변경으로부터 고수준 컴포넌트를 보호할 수 있다.

방향성 제어

IF) FinancialDataGateway 인터페이스는 FinancialReportGenerator와 FinancialDataMapper 사이에 위치하는데,
이는 의존성을 역전시키기 위해서다
만약, FinancialDataGateway 인터페이스가 없었다면, 의존성은 Interactor 컴포넌트에서 DB 컴포넌트로 바로 향하게 된다.
FinancialReportPresenter 인터페이스와 2개의 View 인터페이스도 같은 목적을 갖는다.

정보 은닉

FinancialReportRequester 인퍼테이스는 방향성 제어와는 다른 목적을 가진다.
이 인터페이스는 FinancialReportController가 Interactor 내부에 대해 너무 많이 알지 못하도록 막기 위한 목적을 갖는다.
만약 이 인터페이스가 없었다면 Controller는 FinancialEntities에 대해 추이 종속성을 가지게 된다.

추이 종속성
- A 클래스가 B 클래스에 의존하고, B 클래스가 C 클래스에 의존한다면 -> A 클래스는 C 클래스에 의존하게 된다.

추이 종속성을 가지면, SW 엔티티는 '자신이 직접 사용하지 않는 요소에는 절대 의존해서는 안된다.'라는 원칙을 위반한다.
-> 이 원칙은 ISP와 CRP를 설명할 때 다시 설명한다.

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

결론

OCP는 시스템의 아키텍처를 떠받치는 원동력 중 하나다.
OCP의 목표는 시스템을 확장하기 쉬운 동시에, 변경으로 인해 너무 많은 영향을 받지 않도록 함에 있다.
이러한 목표를 달성하려면 시스템을 컴포넌트 단위로 분리하고, 저수준 컴포넌트에서 발생한 변경으로부터 고수준 컴포넌트를 보호할 수 있는 형태의 의존성 계층구조가 만들어져야 한다.

+ Recent posts