개방-폐쇄 원칙
소프트웨어 개체(artifact)는 확장에 열려 있어야 하고, 변경에는 닫혀 있어야 한다.
이 말은 소프트웨어 개체의 행위는 확장할 수 있어야 하지만, 변경해서는 안된다는 의미다. 만약 소프트웨어에서 요구 사항을 살짝 확장하기 위해서 엄청난 수정이 발생하게 된다면 해당 소프트웨어 시스템의 아키텍처는 실패한 아키텍처가 된다.
사고 실험
재무제표를 웹으로 보여주는 시스템을 예로 OCP를 알아보자. 해당 재무제표 시스템에서 웹이 아닌 보고서를 프린터로 출력해 달라는 요청사항이 생겼다면 새로운 코드를 작성해야 할 것이다. 훌륭한 소프트웨어 아키텍처라면 수정되는 코드의 양이 가능한 최소화될 것이다. 어떻게 하면 변경을 최소화할 수 있을까?
변경을 최소화할 수 있는 방법
- 서로 다른 목적으로 변경되는 요소를 분리 (단일 책임 원칙 SRP)
- 요소 사이의 의존성의 체계화 (의존성 역전 원칙 DIP)
단일 책임 원칙을 적용하면 데이터 흐름은 아래와 같이 변경된다.
보고서 생성에 대한 책임이 두 개의 책임으로 분리가 된다.
- 보고서 웹에 표시
- 보고서를 프린트로 출력
이처럼 책임을 분리한다면, 두 책임 중 하나를 변경 시 다른 하나는 변경되지 않도록 소스코드의 의존성도 확실히 조직화해야 한다. 또한, 새로 조직화된 구조에서는 행위가 확장될 때 변경이 발생되지 않음을 보장해야 한다.
이러한 목적을 달성하려면 처리 과정을 클래스 단위로 분할하고 이들 클래스를 컴포넌트 단위로 구분해야 한다. (그림 2. 참조)
여기 다이어그램에서 그려진 의존성 관계는 소스 코드의 의존성을 나타내고 있다. 예를 들어 FinancialDataMapper는 구현관계를 통해 FinacialDataGateway를 알지만, FinaicalDataGateway는 FiancialDataMapper에 대해 알지 못한다. 또한, 이중선은 화살표와 오직 한 방향으로만 교차한다. 이는 그림 3에서 보듯이 모든 컴포넌트 관계는 단방향으로 이루어진다는 뜻이다. 이들 화살표는 변경으로부터 보호하려는 컴포넌트를 향하도록 그려진다.
그림 3에 보이는 관계도에서 Presenter 변경으로부터 Controller를 보호, View 변경으로부터 Presenter를 보호, 다른 모든 클래스의 변경으로부터 Interactor를 보호하려고 한다는 것을 알 수 있다. Interactor 클래스가 어떠한 변경으로부터 영향받지 않도록 위치한 이유는 이 예제 애플리케이션에서 가장 높은 수준의 업무규칙을 포함하기 때문이다. 보호의 계층 구조는 '수준(level)'이라는 개념을 바탕으로 생성되는데 가장 낮은 수준인 view는 거의 보호를 받지 못하며, 최상위 수준인 Interactor는 최고의 보호를 받게 된다.
그래서 위 다이어그램들을 통해 주목해야 할 점들은 다음과 같다.
- 처리 과정을 클래스 단위로 분할, 클래스는 컴포넌트 단위로 분리한다.
- 다이어그램의 의존성은 소스코드의 의존성을 나타낸다.
- 호출하는 A 클래스는 구현단계에 따라 호출받는 B클래스를 알지만, 호출받는 B클래스는 호출하는 A 클래스에 대해 전혀 알지 못한다.
- 컴포넌트 관계는 단방향으로만 이루지고 이는 변경으로부터 보호하려는 컴포넌트를 향한다.
- A에서 발생한 변경으로부터 B 컴포넌트를 보호하려면 A가 B 컴포넌트에 의존해야 한다.
이것이 OCP가 동작하는 방식이며 아키텍트는 기능이 어떻게(how), 왜(why), 언제(when) 발생하는지에 따라서 기능을 분리하고, 분리한 기능을 컴포넌트의 계층구조로 조직화하여 저수준 컴포넌트에서 발생한 변경으로부터 고수준 컴포넌트를 보호한다.
방향성 제어 (의존성 역전)
그림 2를 통해 인터페이스로 의존성을 역전시켜 다른 컴포넌트로 바로 향하지 않게 함으로써 컴포넌트를 보호한다는 것을 알수있다. 예를 들어 FinacialDataGateway 인터페이스는 FinacialReportGenrator와 FinancialDataMapper 사이에 위치하는데, 이는 의존성을 역전시키기 위함이다. 의존성이 바로 Interactor 컴포넌트에서 Database 컴포넌트로 향하게 된다면 Database의 변경으로 인한 영향으로부터 Interactor를 보호할 수 없게 된다.
정보 은닉
FinancialReportRequester 인터페이스는 정보 은닉을 목적으로 하는데, FinancialReportController 가 Interactor 내부에 대해 너무 많이 알지 못하도록 막기 위해서 존재한다. 만약 FinancialReportRequester 가 없다면 Controller는 FinancialEntities에 대한 추이 종속성을 가지게 된다. (추이 종속성은 A클래스가 B클래스를 의존하고, B클래스가 C클래스를 의존한다면 A 클래스는 C 클래스에 의존되는 것을 말한다.)
이는 '자신이 직접 사용하지 않는 요소에는 절대 의존해서는 안된다'는 소프트웨어 원칙을 위반하게 된다. 따라서 Controller에서 발생하는 변경으로부터 Interactor를 보호하면서, 반대로 Interactor에서 발생한 변경으로부터 Controller를 보호하기 위해 Interactor 내부를 은닉하는 것이다.
결론
OCP의 목적은 시스템을 확장하기 쉬운 동시에 변경으로 인해 시스템에 너무 많은 영향을 받지 않도록 하는 데 있다. 이 목표를 달성하기 위해 시스템을 컴포넌트 단위로 분리하고, 저수준 컴포넌트에서 발생한 변경으로부터 고수준 컴포넌트를 보호할 수 있는 형태의 의존성 계층구조가 만들어지도록 해야 한다.
'클린 아키텍처' 카테고리의 다른 글
[개발서적] 클린 아키텍처 11 - DIP: 의존성 역전 원칙 (0) | 2025.04.08 |
---|---|
[개발서적] 클린 아키텍처 09 - LSP: 리스코프 치환 원칙 (0) | 2025.01.08 |
[개발서적] 클린 아키텍처 07 - SRP : 단일 책임 원칙 (Single Responsibility principle) (1) | 2024.12.24 |
[개발서적] 클린 아키텍처 07 - 설계 원칙 (SOLID) (0) | 2024.12.23 |
[개발서적] 클린 아키텍처 06 - 함수형 프로그래밍 (0) | 2024.12.18 |