객체의 세계에서 협력은 필수적이다. 협력이 존재한다는 것은 객체간의 의존성이 발생한다는 의미이며 (메세지를 보내고 받을) 이러한 의존성은 최소화되어야한다. 즉 무분별한 의존성은 오히려 악영향을 주는 것이다.
충분히 협력적이면서도 유연한 객체를 만들기 위해 의존성을 관리하는 방법
내가 가지고 있는 어떠한 인스턴스나 파라미터를 통해 들어온 값이 변경될 때 나도 같이 변해야한다는 말은 곧 그 객체에 의존하고 있다는 것을 의미한다.
하나의 객체가 다른 객체에 의존하고 있는 경우 의존하고 있는 객체가 변경되는 경우도 변경이 발생할 수 있다. 이런 경우를 의존성이 전이된다 라고 한다.
추상화된 인터페이스와 의존하는 영화 객체는 컴파일시점에서 가격과 퍼센트 할인에 대해서 알지 못한다. 즉 컴파일시점에는 할인이라는 것과 의존하고 있지만 런타임시점에서는 구체적으로 어떠한 할인과 결합하고 있는지 알 수 있게된다.
이러한 특징이 하나의 객체를 다른 객체와 런타임시점에 의존성을 갖게하며 유연한 설계 그리고 다른 클래스들과 협력할 가능성을 열어주기 때문에 변경에 유용한 것이다.
객체는 자신과 결합할 구체적인 클래스에 대해 아는 것은 좋지 않다.
설계가 유연해지기 위해서는 가능한 한 자신이 실행될 컨텍스트에 대한 구체적인 정보를 최대한 적게 알아야 한다. 컨텍스트에 대한 정보가 적으면 적을수록 더 다양한 컨텍스트에서 재사용될 수 있기 때문이다.
컴파일타임 의존성을 실행 컨텍스트에 맞는 적절한 런타임 의존성으로 교체하는 것을 의존성 해결이라고 부른다
객체를 생성하는 시점에 생성자를 통해 의존성 해결
객체 생성 후 setter 메서드를 통해 의존성 해결
객체 생성 이후 Setter를 통해서 의존성을 해결하는 경우 의존성을
주입하기 전까지 불완전한 상태를 유지( 아직 주입되지 않은 의존성에 접근하는경우 ).
이러한 단점을 제거하기 위해서 생성단계에서 의존성을 주입하고 변경되는 경우 의존성을 해결하는 방식을 주로 택한다.
추상화 수준에 따른 의존성
아래로 갈수록 결합도가 느슨해진다.
의존성이 명시적이지 않으면 의존성을 파악하기 위해 내부 구현을 직접 살펴볼 수 밖에 없다. 커다란 클래스에 정의된 긴 메서드 내부 어딘가에서 인스턴스를 생성하는 코드를 파악하는 것은 쉽지 않다. 뿐만 아니라 의존성이 명시적이지 않으면 다른 컨텍스트에서 재사용하기 위해 내부 구현을 직접 변경해야 한다.
new 를 잘못 사용하면 클래스 사이의 결합도가 극단적으로 높아진다. new 가 해로운 이유는 구체클래스에 의존하게 만들기 때문이며 알아야하는 정보의 양을 증가시키기 때문에 궁극적으로 결합도가 높아진다.
구체적인 클래스 이름을 기술해야 하는데, 이런 경우 추상화가 아닌 구체 클래스에 의존할 수 밖에 없기 때문에 결합도가 높아진다.
new 연산자는 생성하려는 구체 클래스뿐만 아니라 어떤 인자를 이용해 클래스의 생성자를 호출해야 하는지도 알아야 한다.
물론 New를 통해 생성하는 경우 유용한 경우도 존재한다.
즉 여러가지 정책 중 한가지의 정책이 대부분 사용된다면?
이를 추상화하여 제공하면 클라이언트 코드에서 중복이 많아질 것이다.
이런 경우 생성자를 두개를 두는 방식이나 메서드 별 오버로딩을 통해 이러한 문제를 극복할 수 있다.
즉 자주 사용되는 것을 내부적으로 New로 선언하고
변경될 수 있는 부분에 대한 여지를 다른 생성자나 오버로딩된 메서드를 통해 제공하는 것이다.
모든 의존을 저렇게 열어두어야 하는가?
물론 그렇지 않다. 변경에 대한 영향을 암시하기 때문에 우리는 추상화를 통해 변경에 유연하게 대처하는 방식을 택하고 있다. 하지만 자바 JDK에 포함된 표준 클래스는 자주 변경되는가? 거의 변경될 일이 없고 변경되더라도 우리가 사용하는 부분에 있어서 큰 변화는 존재하지 않는다.
즉 변경이 되는 대상에 대해서는 추상화를 통해 의존성을 명시적으로 받아와 캐스팅하는 것이 바람직하며
자주 변경되지 않는 대상에 대해서는 구체클래스에 의존해도 되는 것이다. 그럼에도 불구하고 ArrayList를 List로 한 번 포장하는 것은 당연히 좋은 습관이다.
요약
어떻게 하는 것이 아니라 무엇을 하는지 를 선언적으로 표현하고 드러냄으로써 유연하고 재사용 가능한 설계를 만든다. 객체 네트워크 행위에 대한 선언적인 정의를 통해서. 어떻게가 아닌 무엇에 집중한다.
설계를 유연하게 만들 수 있었던 이유는 추상화에 의존하고 생성자를 통해 의존성을 명시적으로 드러냈으며 new와 같이 구체 클래스를 직접적으로 다뤄야하는 부분을 외부로 옮겼기 때문이다. 우리는 이제 추상화된 곳에 자식 클래스를 추가함으로써 간단하게 영화가 사용될 컨텍스트를 확장할 수 있다. 결합도를 낮춤으로써 얻게되는 컨텍스트 확장이라는 개념이 유연하고 재사용 가능한 설계를 만드는 핵심인 것이다.
같은 말로
사용과 생성의 책임을 분리하고, 의존성을 생성자에 명시적으로 드러내고, 구체 클래스가 아닌 추상 클래스에 의존하게 함으로써 설계를 유연하게 만들 수 있다. 그리고 그 출발은 객체를 생성하는 책임을 객체 내부가 아니라 클라이언트로 옮기는 것에서 시작했다. 즉 객체 내부적으로 강하게 의존하는 설계가 아니라 클라이언트의 요구 및 변화를 수용할 수 있는 형태로 확장성있게 설계되었다.