개방 - 폐쇄 원칙
- 소프트웨어 개체(클래스, 모듈, 함수 등등)는 확장에 대해 열려 있어야 하고, 수정에 대해서는 닫혀 있어야 한다.
해당 내용에서의 키워드는 "확장"과 "수정"
- 확장에 대해 열려 있다.
- 애플리케이션의 요구사항이 변경될 때 이 변경에 맞게 새로운 '동작'을 추가해서 애플리케이션의 기능을 확장할 수 있다.
- 수정에 대해 닫혀 있다
- 기존의 '코드'를 수정하지 않고도 애플리케이션의 동작을 추가하거나 변경할 수 있다.
컴파일 타임 의존성을 고정시키고 런타임 의존성을 변경하라
- 사실 개방-폐쇄 원칙은 런타임 의존성과 컴파일 타임 의존성에 관한 이야기이다.
런타임 의존성
- 실행 시에 협력에 참여하는 객체들 사이의 관계
컴파일 타임 의존성
- 코드에서 드러나는 클래스들 사이의 관계
이 할인정책은 사실 이미 OCP를 따르고 있다.
만약, 복합 할인 정책이 추가되는 경우 기존 코드는 수정하지 않고 추가 확장이 가능하다.
즉, 확장에는 열려있어야하고 수정에는 닫혀 있어야하는것이 핵심!
개방-폐쇄 원칙을 수용하는 코드는 컴파일러 타임 의존성을 수정하지 않고도 런타임 의존성을 쉽게 변경 할 수 있다.
추상화가 핵심이다.
- 개방-폐쇄 원칙의 핵심은 추상화에 의존하는 것이다. 여기서 추상화와 의존이라는 두 개념 모두 중요하다.
추상화
- 핵심적인 부분 외 불필요한 부분은 생략하여 복잡성을 극복하는 기법
- 추상화를 사용하면 생략된 부분을 문맥에 적합한 내용으로 채워놓음으로써 각 문맥에 적합하게 기능을 구체화하고 확장할 수 있다.
- 추상화 부분은 수정에 닫혀 있다. (공통적인 부분은 변하지 않는다.)
추상화를 통해 생략된 부분은 확장의 여지를 남기는 것이 개방-폐쇄 원칙을 가능하게 만드는 이유이다.
개방 - 폐쇄 원칙에서 폐쇄를 가능하게 하는 것은 의존성의 방향이다.
수정에 대한 영향을 최소화 하기위해선 모든 요소가 추상화에 의존해야 한다.
생성 사용 분리
- 결합도가 높아질수록 개방-폐쇄 원칙을 따르는 구조를 설계하기가 어려워진다. 알아야 하는 지식이 많으면 결합도 또한 늘어난다.
- 객체 생성에 대한 지식은 과도한 결합도를 초래하는 경향이 있다.
- 객체의 타입과 생성자에 전달해야 하는 인자에 대한 과도한 지식은 코드의 특정한 컨텍스트를 강하게 결합시킨다.
- 콘텍스트를 바꾸기 위한 유일한 방법은 코드 안에 명시돼 있는 콘텍스트에 대한 정보를 직접 수정하는 것 뿐이다.
- 사용으로부터 생성을 분리하는 데 사용되는 보편적인 방법은 객체를 생성할 책임을 클라이언트로 옮기는 것
FACTORY 추가하기
- 전체적인 결합도를 낮추고 재사용성을 높이기 위해 도메인 개념에게 할당돼 있던 객체 생성 책임을 도메인 개념과는 아무런 상관이 없는 가공의 객체로 이동시킨 것이다.
순수한 가공물에게 책임 할당하기
- 책임 할당의 가장 기본이 되는 원칙은 책임을 수행하는 데 필요한 정보를 가장 많이 받고 있는 INFORMATION EXPERT에게 책임을 할당하는 것이다.
- 어떤 책임을 할당하고 싶다면 제일 먼저 도메인 모델 안의 개념 중 적절한 후보가 존재하는지 찾아봐야 한다.
여기서 Factory는 결합도를 낮추고 재사용성을 높이기 위한 순수 기술의 결정이기에, 아무런 관련이 없다.
표현적 분해
- 도메인에 존재하는 사물 또는 개념을 표현하는 객체들을 이용해 시스템을 분해하는 것을 말한다.
- 도메인 모델에 담겨 있는 개념과 관계를 따르며 도메인과 소프트웨어 사이의 표현적 차이를 최소화하는 것이 목적
- 객체지향 설계를 위한 가장 기본적인 접근법이다.
행위적 분해
- 도메인 개념을 표현한 객체가 아닌 설계자가 편의를 위해 임의로 만든 객체를 이용해 시스템을 분해하는 것을 말한다.
- 도메인 개념을 표현하는 객체에게 책임을 할당하는 것만으로는 부족한 경우가 발생한다.
- 데이터베이스 접근을 위한 객체와 같이 도메인 개념들을 초월하는 기계적인 개념도 필요한 경우가 있다.
- 책임을 할당하기 위해 창조되는 도메인과 무관한 인공적인 객체를 PURE FABRICATION(순수한 가공물)이라 한다.
- 어떤 행동을 추가하려고 하는데 이 행동을 책임질 마땅한 도메인 개념이 존재하지 않는 경우 PURE FABRICATION을 생성하고 이 객체에게 책임을 할당하면 된다.
- 객체지향 애플리케이션에서는 도메인 개념을 반영하는 객체들 보다 인공적으로 창조한 객체들이 더 많은 비중을 차지한다.
PURE FABRICATION 패턴
객체지향 설계는 문제 도메인 상의 개념을 소프트웨어 객체로 구현하고 책임을 할당한다. 하지만 만약 도메인 객체에 책임을 할당할 경우 HIGH COHESION, LOW COUPLING, 재사용성 등의 목적을 위반한다면 어떻게 해야 하는가?
문제 도메인 개념을 표현하지 않는, 인위적으로 또는 편의상 만든 클래스에 매우 응집된 책임을 할당하라. 이들 클래스는 문제 도메인 상에는 존재하지 않지만 순수하게 전체 설계의 품질을 높이기 위해 설계자의 임의에 따라 추가한 상상 속의 가공물이다.
PURE FABRICATION은 INFORMATION EXPERT 패턴에 따라 책임을 할당한 결과가 바람직하지 않을 경우 대안으로 사용된다. 어떤 객체가 책임을 수행하는 데 필요한 많은 정보를 가졌지만 해당 책임을 할당할 경우 응집도가 낮아지고 결합도가 높아진다면 가공의 객체를 추가해서 책임을 옮기는 것을 고민하라
순수한 가공물(pure fabrication)이라는 표현은 적절한 대안이 없을 때 사람들이 창조적인 무언가를 만들어낸다는 것을 의미하는 관용적인 표현이다.
의존성 주입
- 사용하는 객체가 아닌 외부의 독립적인 객체가 인스턴스를 생성한 후 이를 전달하여 의존성을 해결하는 방법
이 기법을 의존성 주입(DI)이라 하는 이유는 외부에서 의존성의 대상을 해결한 후 사용하는 객체 쪽으로 주입하기 때문이다.
의존성 주입(DI)은 의존성을 해결하기 위해 의존성을 객체의 퍼블릭 인터페이스에 명시적으로 드러내서 외부에서 필요한 런타임 의존성을 전달할 수 있도록 만드는 방법을 포괄하는 명칭이다.
생성자 주입 ( Constructor Injection )
- 객체를 생성하는 시점에 생성자를 통한 의존성 주입
setter 주입 ( Setter Injection )
- 이미 생성된 객체에 대해 setter 메서드를 통해 의존성을 해결한다.
메서드 주입 ( Method Injection )
- 메서드 실행 시 인자를 이용한 메서드 해결
숨겨진 의존성은 나쁘다.
- 의존성 주입 외에도 의존성을 해결할 수 있는 방법이 존재한다.
- 대표적인 방법은 SERVICE LOCATOR 패턴이다. SERVICE LOCATOR는 의존성을 해결할 객체들을 보관하는 일종의 저장소다.
- 외부에서 객체에게 의존성을 전달하는 의존성 주입과 달리 SERVICE LOCATOR의 경우 객체가 직접 SERVICE LOCATOR에게 의존성을 해결해줄 것을 요청한다.
이후 의존성 역전 원칙의 경우, 이전 포스팅을 진행하며 정리한 내용이 있기에 스킵하고자 한다. :)