소프트웨어 개체(클래스, 모듈, 함수 등등)는 확장에 대해 열려 있어야 하고, 수정에 대해서는 닫혀 있어야 한다.
여기서 키워드는 '확장'과 '수정'이다. 이 둘은 순서대로 애플리케이션의 '동작'과 '코드'의 관점을 반영한다
사실 개방-폐쇠 원칙은 런타임 의존성과 컴파일타임 의존성에 관한 이야기다. 런라임 의존성은 실행시에 협력에 참여하는 객체들 사이의 관계다.
컴파일타임 의존성은 코드에서 드러나는 클래스들 사이의 관계다. 그리고 앞자에서 살펴본 것처럼 유연하고 재사용 가능한 설계에서 런타임 의존성과 컴파일 타임 의존성을 서로 다른 구조를 가진다.
개방-폐쇄 원칙의 핵심은 추상화에 의존하는 것이다. 추상화를 사용하면 생략된 부분을 문맥에 적합한 내용으로 채워넣음으로써 각 문맥에 적합하게 기능을 구체화하고 확장할 수 있다.
여기서 주의할 점은 추상화를 했다고 해서 모든 수정에 대해 설계과 폐쇠되는 것은 아니라는 것이다. 수정에 대해 닫혀 있고, 확장에 대해 열려 있는 설게는 공짜로 얻어지지 않는다.
변경에 의한 파급효과를 최대한 피하기 위해서는 변하는 것과 변하지 않는 것이 무엇인지를 이해하고 이를 추상화의 목적으로 삼아야만 한다.
결합도가 높아질수록 개팡-폐쇠 원칙을 따르는 구조를 설계하기가 어려워진다. 알아야 하는 지식이 많으면 결합도도 높아진다. 특히 객체 생성에 대한 지식을 과도한 결합도를 초래하는 경향이 있다.
객체의 타입과 생성자에 전달해야 하는 인자에 대한 과도한 지식은 코드를 특정한 컨텍스트에 강합게 결합시킨다. 컨텍스트를 바꾸기 위한 유일한 방법은 코드 안에 명시 돼있는 컨텍스트에 대한 정보를 직접 수정하는 것뿐이다.
도메인 모델은 설계를 위한 중요한 출발점이지만 단지 출발점이라는 사실을 명심해야 한다. 실제로 동작하는 애플리케이션은 데이터베이스 접근을 위한 객체와 같이 도메인 개념들을 초월하는 기계적인 개념들을 필요로 할 수 있다.
모든 책임을 도메인 객체에게 할당하면 낮은 응집도, 높은 결합도, 재사용성 저하와 같은 심각한 문제점에 봉착하게 될 가능성이 높아진다. 크레이그 라만은 이처럼 책임을 할당하기 위해 창조되는 도메인과 무관한 인공적인 객체를 PURE FABRICATION(순수한 가공물)이라고 부른다.
어떤 행동을 추가하려고 하는데 이 행동을 책임질 마땅한 도메인 개념이 존재하지 않는다면 PURE FABRICATION을 추가하고 이 객체에게 책임을 할당하라. 따라서 PURE FABRICATION은 표현적 분해보다는 행위적 분해에 의해 생성되는 것이 일반적이다.
도메인 개념을 표현하는 객체와 순수하게 창조된 가공의 객체들이 모여 자신의 역할과 책임을 다하고 조화롭게 협력하는 애플리케이션을 설계하는 것이 목표여야 한다.
의존성 해결은 컴파일타임 의존성과 런타임 의존성의 차이점을 해소하기 위한 다양한 매커니즘을 포괄한다. 따라서 의존성 주입에서는 의존성을 해결하는 세 가지 방법을 가리키는 별도의 용어를 정의한다.
객체 사이의 협력이 존재할 때 그 협력의 본질을 담고 있는 것은 상위 수준의 정책이다. 어떤 협력에서 중요한 정책이나 의사결정, 비즈니스의 본질을 담고 있는 것은 상위 수준의 클래스다.
상위 수준의 변경에 의해 하위 수준이 변경되는 것은 납득할 수 있지만 하위 수준의 변경으로 인해 상위 수준이 변경돼서는 곤란하다.
의존성 역전 원칙에 따라 상위 수준의 협력 흐름을 재사용하기 위해서는 추상화가 제공하는 인터페이스의 소유권 역시 역전시켜야 한다.
유연하고 재사용 가능하며 컨텍스트에 독립적인 설계는 전통적인 패러다임이 고수하는 의존성의 방향을 역전시킨다. 전통적인 패러다임에서는 상위 수준 모듈이 하위 수준 모듈에 의존했다면 객체지향 패러다임에서는 상위 수준 모듈과 하위 수준 모듈이 모두 추상화에 의존한다.
유연성은 항상 복잡성을 수반한다. 유연하지 않은 설계는 단순하고 명확하다. 유연한 설계는 복잡하고 암시적이다. 특정 시점의 객체 구조를 파악하는 유일한 방법은 클래스를 사용하는 클라이언트 코드 내에서 객체를 생성하거나 변경하는 부분을 직접 살펴보는 것뿐이다.
마지막으로 하고 싶은 말은 객체의 협력과 책임이 중요하다는 것이다. 지금까지 클래스를 중심으로 구현 메커니즘 관점에서 의존성을 설명했지만 설계를 유연하게 만들기 위해서는 협력에 참여하는 객체가 다른 객체에게 어떤 메시지를 전송하는지가 중요하다.
의존성을 관리해야 하는 이유는 역할, 책임, 협력 관점에서 설계가 유연하고 재사용 가능해야 하기 때문이다. 따라서 역할, 책임, 협력에 먼저 집중하라.
이번 장에서 설명한 다양한 기법들을 적용하기전에 역할, 책일, 협력의 모습이 선명하게 그려지지 않는다면 의존성을 관리하는데 들이는 모든 노력이 물거품이 될 수도 있다는 사실을 명심하라.