객체지향 설계의 핵심은
협력을 위해 필요한 의존성은 유지하면서도 변경을 방해하는 의존성은 제거하는 데 있다
의존성 이해하기
변경과 의존성
의존성은 실행 시점과 구현 시점에 서로 다른 의미를 가진다
의존성은 함께 변경될 수 있는 가능성을 의미, 모든 경우에 의존성이 전이되는 것은 아니다
의존성이 실제로 전이될지 여부는 견경의 방향과 캡슐과의 정도에 따라 달라진다.
런타임 의존성과 컴파일타임 의존성
- 런타임 의존성 : 객체 사이의 의존성
- 컴파일타임 의존성 : 클래스 사이의 의존성
런타임 의존성과 컴파일타임 의존성이 다를 수 있다
컨텍스트 독립성
클래스가 사용될 특정한 문맥에 대해 최소한의 가정만으로 이뤄져 있다면
다른 문맥에서 재사용하기가 더 수월해진다.
의존성 해결
컴파일타임 의존성을 실행 컨텍스트에 맞는 적절한 런타임 의존성으로 교체하는 것
의존성 해결 방법
- 객체를 생성하는 시점에 생성자를 통해 의존성 해결
- 객체 생성 후 setter 메서드를 통해 의존성 해결
- 객체를 생성한 이후에도 의존하고 있는 대상을 변경할 수 있다.
- 의존 대상 설정 전까지는 불완전 상태
- 메서드 실행 시 인자를 이용해 의존성 해결
- 협력 대상에 대해 지속적으로 의존 관계를 맺을 필요 없이 메서드가 실행되는 동만만 일시적으로 의존 관계가 존재해도 무방하거나,
- 메서드가 실행될 때마다 의존 대상이 매번 달라져야 하는 경우에 유용
유연한 설계
의존성과 결합도
- 의존성은 두 요소 사이의 관계 유무
- 결합도는 두 요소 사이에 존재하는 의존성의 정도를 상대적으로 표현
모든 의존성이 나쁜 것은 아니다. 의존성은 객체들의 협력을 가능하게 만드는 매개체라는 관점에서 바람직하지만, 의존성이 과하면 문제가 될 수 있다
지식이 결합을 낳는다
결합도를 느슨하게 만들기 위해서는 협력하는 대상에 대해 필요한 정보 외에는 최대한 감추는 것이 중요 -> 추상화
추상화에 의존하라
추상화 : 특정 절차나 물체를 의도적으로 생략하거나 감춤으로써 복잡도를 극복하는 방법
대상에 대해 알아야 하는 지식의 양을 줄일 수 있기 때문에 결합도를 느슨하게 유지할 수 있다.
의존하는 대상이 더 추상적일수록 결합도는 더 낮아진다.
의존 대상 구분 (아래쪽으로 갈수록 알아야 하는 지식의 양이 적어지기 때문에 결합도가 느슨)
- 구체 클래스 의존성
- 추상(abstract) 클래스 의존성
- 인터페이스(interface) 의존성
명시적인 의존성 (explicit dependency)
퍼블릭 인터페이스에 의존성을 명시적으로 노출하는 것
<-> 숨겨진 의존성(hidden dependency)
new는 해롭다
- new 연산자 사용을 위해 구체 클래스의 이름을 직접 기술해야 한다.
- new를 사용하는 클라이언트는 추상화가 아닌 구체 클래스에 의존할 수밖에 없기 때문에 결합도가 높아진다.
- new 연산자는 생성하려는 구체 클래스뿐만 아니라 어떤 인자를 이용해 클래스의 생성자를 호출해야 하는지도 알아야 한다.
- new를 사용하면 클라이언트가 알아야 하는 지식의 양이 늘어나기 때문에 결합도가 높아진다.
new로 인한 결합도 증가 해결 방법
- 인스턴스를 생성하는 로직과 생성된 인스턴스를 사용하는 로직을 분리
- 직접 인스턴스를 생성해서는 안 된다. 단지 해당하는 인스턴스를 전달받아(생성자, setter, 인자를 통해) 사용하기만 해야 한다.
- 생성하는 책임을 클라이언트로 옮기고, 생성된 인스턴스를 사용하는 책임만 남겨야 한다
- 사용과 생성의 책임을 분리하고 생성하는 책임을 클라이언트로 옮김
- 의존성을 생성자에 명시적으로 드러냄
- 구체 클래스가 아닌 추상 클래스에 의존
가끔은 생성해도 무방하다
표준 클래스에 대한 의존은 해롭지 않다.
변경될 확률이 거의 없는 클래스라면 의존성이 문제 되지 않는다
컨텍스트 확장하기
생성자에서 받는 타입이 추상화되어 있기 때문에 원하는 대로 바꾸어 적용할 수 있다