잘 설계된 객체지향 애플리케이션은 작고 응집도 높은 객체들로 구성된다. 작고 응집도 높은 객체란 책임의 초점이 명확하고 한 가지 일만 잘하는 객체를 의미한다.
협력을 위해서는 의존성이 필요하지만 과도한 의존성은 애플리케이션을 수정하기 어렵게 만든다. 객체지향 설계의 핵심은 협력을 위해 필요한 의존성은 유지하면서도 변경을 방해하는 의존성은 제거하는데 있다.
어떤 객체가 협력하기 위해 다른 객체를 필요로 할때 두 객체 사이에 의존성이 존재하게 된다. 의존성은 실행 시점과 구현 시점에 서로 다른 의미를 가진다.
의존성은 전이될 수 있기 때문에 의존성의 종류를 직접 의존성과 간접 의존성으로 나누기도 한다. 직접 의존성이란 말 그대로 한 요소가 다른 요소에 직접 의존하는 경우를 가리킨다. 간접 의존성이란 직접적인 관계는 존재하지 않지만 의존성 전이에 의해 영향이 전파되는 경우를 가리킨다.
// 직접 의존성
Class Movie {
private AmountDiscountPolicy amountDiscountPolicy
= new AmountDiscountPolicy();
...
}
// 간접 의존성
class Movie {
private DiscountPolicy discountPolicy;
Movie(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
...
}
의존성과 관련해서 다뤄야 하는 또 다른 주제는 런타임 의존성과 컴파일 타임 의존성의 차이다. 만약 여기서 사용하는 런타임과 컴파일타임의 의미를 의해할 필요가 있다.
객체지향 애플리케이션에서 런타임의 주인공은 개게다. 따라서 런타임 의존성이 다루는 주제는 객체 사이의 의존성이다. 반면 코드 관점에서 주인공으 클래스다. 따라서 컴파일 타임 의존성이 다루는 주제는 클래스 사이의 의존성이다.
여기서 중요한 것은 런타임 의존성과 컴파일타임 의존성이 다를 수 있다는 것이다. 사실 유연하고 재사용 가능한 코드를 설계하기 위해서는 두 종류의 의존성을 서로 다르게 만들어야 한다.
클래스는 자신과 협력할 객체의 구체적인 클래스에 대해 알아서는 안된다. 구체적인 클래스를 알면 알수록 그 클래스가 사용되는 특정한 문맥에 강하게 결합되기 때문이다.
클래스가 사용될 특정한 문맥에 대해 최소한의 가정만으로 이뤄져 있다면 다른 문맥에서 재사용하기가 더 수월해진다. 이를 컨텍스트 독립성이라고 부른다.
시스템을 구성하는 객체가 컨텍스트 독립적이라면 해당 시스템은 변경하기 쉽다. 여기서 컨텍스트 독립적이라는 말은 각 객체가 해당 객체를 실행하는 시스템에 관해 아무것도 알지 못한다는 의미다.
다른 환경에서 재사용하기 위해 내부 구현을 변경하게 만드는 모든 의존성은 바람직하지 않은 의존성이다. 이를 다른 용어로 결합도로 일컫는다.
의존성이 바람직할 때 느슨한 결합도 또는 약한 결합도를 가진다고 말한다. 반대로 두 요소 사이의 의존성이 바람직하지 못할 때 단단한 결합도 또는 강한 결합도를 가진다고 말한다.
결합도의 정도는 한 요소가 자신이 의존하고 있는 다른 요소에 대해 알고 있는 정보의 양으로 결정된다. 한 요소가 다른 요소에 대해 더 많은 정보를 알고 있을수록 두 요소는 강하게 결합된다. 서로에 대해 알고 있는 지식의 양이 결합도를 결정한다.
결합도를 느슨하게 만들기 위해서는 협력하는 대상에 대해 필요한 정보 외에는 최대한 감추는 것이 중요하다. 그것이 바로 추상화다.
추상화란 어떤 양상, 세부사항, 구조를 좀 더 명확하게 이해하기 위해 특정 절차나 물체를 의도적으로 생략하거나 감춤으로써 복잡도를 극복하는 방법이다.
일반적으로 추상화와 결합도의 관점에서 의존 대상을 다음과 같이 구분하는 것이 유용하다. 목록에서 아래쪽으로 갈수록 클라이언트가 알아야 하는 지식의 양이 적어지기 때문에 결합도가 느슨해진다.
결합도 측면에서 new가 해로운 이유는 크게 두 가지다.
훌륭한 객체지향 설계란 객체가 어떻게 하는지를 표현하는 것이 아니라 객체들의 조합을 선언적으로 표현함으로써 객체들이 무엇을 하는지를 표현하는 설계다.