OCP란?
소프트웨어 개체(artifact)는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다.
이 말을 다시 적으면 "소프트웨어 개체의 행위는 확장할 수 있어야 하지만, 이때 개체를 변경해서는 안 된다" 라는 뜻이다. 소프트웨어에서 기능을 추가하려고하는데 시스템을 엄청 많이... 수정해야한다면, 이는 엄청난 실패이다. 이러한 상황을 피할 수 있는 설계가 OCP원칙을 따른 설계라고 할 수 있을 것이다.
OCP는 단순히 클래스와 모듈 수준을 넘어 아키텍쳐는 더 중요한 의미를 가진다.
OCP의 목표
시스템을 확장하기 쉬운 동시에 변경으로 인해 시스템이 너무 많은 영향을 받지 않도록 하는데 있다.
사고 실험
이번 장에서는 사고실험 예시로 나온 클래스 설계 도면을 보면서 OCP를 이해해 보도록 하자. (OCP외에도 몇가지 원칙이 더 나오니 더불어 확인해볼 것!)
전략 세우기
우선, 순서대로 생각을 해본다면 새로운 수정사항이 들어왔다고 가정했을때, 가장 이상적인 케이스는 변경되는 코드의 양을 최소화 하는 것이다. 이러한 구조의 시스템을 만들기 위해서는 어떻게 설계가 되어야할까?
- 서로 다른 목적으로 변경되는 요소를 적절하게 분리 (단일 책임 원칙 SRP)
- 이 요소들 사이의 의존성을 체계화(의존성 역전 원칙 DIP)
위 두가지를 준수 했다면, 변경량의 최소화를 달성 할 수 있을 것이다.
책의 예시에서는 재무 데이터를 이용하여 보고서를 프린트하는 프로그램을 재무데이터 계산, 재무 보고서 출력 두가지 요소로 나누었다. 이는 SRP 원칙을 준수한 예이다.
책임을 분리 했다면, 이 두 책임의 의존성 문제를 해결 해야하는데, 이를 위해서 위 설계도의 이중선으로 표시한 컴포넌트 단위 구분이 필요하다. 또한 행위 확장시 변경이 발생되지 않음을 보장해야 한다. 이는 아래 컴포넌트 구성하기를 통해 그 방법을 확인해 보도록 하자.
컴포넌트 구성하기
위 예시에 보이는 컴포넌트들을 차분히 살펴보며 잘 설계된 예시를 통해 어떻게 OCP가 이루어질 수 있는지 확인해보자.
(A->B 는 A클래스에서 B클래스를 호출한다는 뜻. A-▷B 는 A는 B를 구현한 구현체이다 라는 뜻, 즉 B는 interface.)
크게 View를 담당하는 컴포넌트 2개, Presenter를 담당하는 컴포넌트 2개, Controller, Interactor, Database 컨트롤러로 구성되어있음을 확인할 수 있다.
1. Controller
- Controller는 Interactor를 사용하고 있고, Controller의 수정에 interactor는 영향을 받지 않지만, interactor 수정에는 controller가 영향을 받는 관계이다. Controller는 Interactor의 존재를 알고 있음.(Controller->Interactor)
- Controller는 Financial Report Presenter라는 interface를 통해 Presenter를 사용하게 됨을 확인할 수 있다. 따라서 Presenter에 구체적으로 어떤 Presenter가 붙어있는지 모른다. 여기에서 interface를 활용하면서 의존 역전을 통해 Presenter의 수정이 직접적으로 controller에 영향이 가는 일은 없도록 설계하였다.(책에서는 이를 "Presenter에서 발생한 변경으로부터 Controller를 보호하고자 한다"라고 표현하였다. Presenter<-Controller)
2. Presenter
- Presenter는 Financial Report Presenter 인터페이스를 구현하고 있다.
- WebView와 PDF View는 각각 Presenter의 View 인터페이스를 통해서 사용이 된다. 즉 Presenter는 구체적인 View의 존재를 모르며, Presenter를 View의 변경으로 부터 보호하고 있는 관계이다.(Presenter<-View)
3. View
- 각 View 구현체는 Presenter의 View 인터페이스를 통해 소통한다.
4. Interactor
- interactor는 Database의 변경으로부터 보호받고있다.(Interactor<-Database)
- Financial Report Generator는 Financial Report Requester를 구현한 클래스이고 Financial Report Request와 Financial Report Response 데이터구조, Financial entities 클래스를 사용하고 있다. 그리고 Database와는 Financial Data Gateway인터페이스로 소통한다.
5. Database
- Database는 Interactor에 의존적임 (Database -> Interactor).
- Financial Data Mapper는 Financial Data Gateway인터페이스의 구현체이며, Interactor의 Financial Report Generator와의 소통 방식이다.
- Financial Data Mapper에서는 Financial entities를 가져와서 사용한다. (Financial Entities의 변화에 의존적, 하지만 entity의 변경 확률은 적다는 점 참고)
- Financial Data Mapper는 Financial Database를 사용하여 데이터를 가져온다.
컴포넌트들의 관계를 확인해보게 되면 모든 컴포넌트 관계는 단 방향으로 이루어진다. 이들 화살표는 변경으로부터 보호하려는 컴포넌트를 향하도록 그려진다.
A 컴포넌트에서 발생한 변경으로부터 B 컴포넌트를 보호하려면
반드시 A 컴포넌트가 B 컴포넌트에 의존해야한다.
위 컴포넌트 관계도를 통해 Interactor는 다른 모든 것에서 발생한 변경으로부터 보호하고자 함을 알 수 있다. 따라서 OCP를 가장 잘 준수할 수 있는 곳에 위치하고 있다.
이러한 Interactor의 의미를 파악해 보자면 아래와 같은 의미이다.
(아래와 같은 의미를 가지기에 모든 컴포넌트로 부터 보호받는 위치에 있어야 한다고 보는게 나을 듯하다.)
- Interactor는 업무 규칙을 포함하고 있다
- 가장 높은 수준의 정책을 포함한다.
- Interactor 이외의 컴포넌트는 모두 주변적인 문제를 처리한다.
- 가장 중요한 문제는 Interactor가 담당한다.
여기서 컴포넌트 전체적으로 계층관점으로 보자면.
- Interactor 입장에서는 Controller, Database가 부수적임.
- Controller입장에서는 Presenter와 View에 비해서는 중요한 업무를 하고 있음(더 높은 수준의 개념)
- 컴포넌트 계층구조를 이와 같이 조직화하면 저수준 컴포넌트에서 발생한 변경으로부터 고수준 컴포넌트(핵심 로직)를 보호할 수 있다.
결론
OCP의 목표 달성을 위해서는 1.시스템을 컴포넌트 단위로 분리하고, 2.저수준 컴포넌트에서 발생한 변경으로부터 고수준 컴포넌트를 보호할 수 있는 형태의 의존성 계층구조가 만들어지도록 해야한다.
'Design > Architecture' 카테고리의 다른 글
[Clean Architecture]SOLID 원칙 : 5.DIP 의존성 역전 원칙 (0) | 2021.09.27 |
---|---|
[Clean Architecture]SOLID 원칙 : 4.ISP 인터페이스 분리 원칙 (0) | 2021.09.26 |
[Clean Architecture]SOLID 원칙 : 3.LSP 리스코프 치환 원칙 (0) | 2021.09.16 |
[Clean Architecture]SOLID 원칙 : 1.SRP 단일 책임 원칙 (0) | 2021.09.10 |
[Clean Architecture]SOLID 원칙 개론 (0) | 2021.09.07 |