본문 바로가기
프로그래밍 놀이터/디자인 패턴, 리펙토링

[책 정리] 8장. OCP(Open Closed Principle): 개방-폐쇄 원칙 - Clean Architecture

by 돼지왕 왕돼지 2020. 4. 12.
반응형

-

개방-폐쇄 원칙(OCP) 는 다음과 같다.

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

다시 말해 소프트웨어 개체의 행위를 확장할 수 있어야 하지만, 이 때 개체를 변경해서는 안 된다."

 

소프트웨어 아키텍처를 공부하는 가장 근본적인 이유가 바로 이 때문이다.

요구사항을 살짝 확장하는 데 소프트웨어를 엄청나게 수정해야 하면 이는 실패한 아키텍처이다.

 

 

 

사고 실험

 

-

재무제표를 웹 페이지로 보여주는 시스템이 있다고 하자.

웹 페이지에 표시되는 데이터는 스크롤 할 수 있고, 음수는 빨간색으로 출력한다.

이제 이해관계자가 동일한 정보를 보고서 형태로 변환해서 흑백 프린터로 출력해 달라는 요청이 왔다.

이 보고서에는 페이지 번호가 있어야 하고, 페이지마다 저절한 머리글과 바닥글이 있어야 하며, 표의 각 열에는 레이블이 있어야 하고, 또한 음수는 괄호로 감싸야 한다고 한다.

 

 

-

소프트웨어 아키텍처가 훌륭하다면 추가 요구사항에 대한 변경되는 코드의 양이 가능한 한 최소화된다.

이상적인 변경량은 0이다.

어떻게 하면 될까?

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

 

 

-

단일 책임 원칙 SRP 를 적용하면 데이터 흐름을 다음과 같이 할 수 있다.

재무 데이터 -> 재무 분석기 -> 보고서용 재무 데이터

    -> 1. 보고서를 웹에 표시

    -> 2. 보고서를 프린터로 출력

 

이처럼 책임을 분리했다면, 두 책임 중 하나에서 변경이 발생하더라도 다른 하나는 변경되지 않도록 소스 코드 의존성도 확실히 조직화해야 한다.

또한, 새로 조직화한 구조에서는 행위가 확장될 때 변경이 발생하지 않음을 보장해야 한다.

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

 

 

여기 또 하나 중요한 것은 모든 컴포넌트 관계는 단방향으로 이루어진다는 뜻이다.

이들 화살표는 변경으로부터 보호하려는 컴포넌트를 향하도록 그려진다.

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

 

 

-

Interator 는 DB, Controller, Presenter, View 에서 발생한 어떤 변경에도 영향을 받지 않는다.

그 이유는 바로 Interactor 가 업무 규칙을 포함하기 때문이다.

Interactor 는 앱에서 가장 높은 수준의 정책을 포함한다.

Interactor 이외의 컴포넌트는 모두 주변적인 문제를 처리한다. 가장 중요한 문제는 Interactor 가 담당한다.

 

 

-

보호 계층구조가 수준(level)이라는 개념을 바탕으로 어떻게 생성되는지 주목하자.

Interactor 는 가장 높은 수준의 개념이며, 따라서 최고의 보호를 받는다.

View는 가장 낮은 수준의 개념 중 하나이며, 따라서 거의 보호를 받지 못한다

Presenter 는 View보다는 높고 Controller 나 Interactor 보다는 낮은 수준에 위치한다.

 

이것이 아키텍처 수준에서 OCP 가 동작하는 방식이다.

아키텍트는 기능이 어떻게(how), 왜(why), 언제(when) 발생하는지에 따라서 기능을 분리하고, 분리한 기능을 컴포넌트의 계층구조로 조직화한다.

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

 

 

 

방향성 제어

 

-

FinancialDataGateway 인터페이스는 FinancialReportGenerator 와 FinancialDataMapper 사이에 위치하는데, 이는 의존성을 역전시키기 위해서다.

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

 

 

 

정보 은닉

 

-

FinancialReportRequester 인터페이스는 방향성 제어와는 다른 목적을 가진다.

이 녀석은 FinancialReportController 가 Interactor 내부에 대해 너무 많이 알지 못하도록 막기 위해 존재한다.

만약 이 인터페이스가 없었다면, Controller 는 FinancialEntities 에 대해 추이 종속성(transitive dependency)을 가지게 된다.

 

추이 종속성을 가지게 되면, 소프트웨어 엔티티는 '자신이 직접 사용하지 않는 요소에는 절대로 의존해서는 안 된다'는 소프트웨어 원칙을 위반하게 된다.

 

다시 말해 Controller 에서 발생한 변경으로부터 Interactor 를 보호하는 일의 우선순위가 가장 높지만,

반대로 Interactor 에서 발생한 변경으로부터 Controller 도 보호되기를 바란다.

이를 위해 Interactor 내부를 은닉한다.

 

 

 

결론

 

-

OCP 는 시스템 아키텍처를 떠받치는 원동력 중 하나다.

이의 목표는 시스템을 확장하기 쉬운 동시에 변경으로 인해 시스템이 너무 많은 영향을 받지 않도록 하는 데 있다.

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

 

반응형

댓글