코드 관점에서 주인공은 클래스입니다. 따라서 컴파일타임 의존성이 다루는 주제는 클래스 사이의 의존성 입니다.
객체지향 애플리케이션에서 런타임의 주인공은 객체입니다. 따라서 런타임 의존성을 다루는 주제는 객체 사이의 의존성입니다.
런타임 의존성과 컴파일타임 의존성이 다를 수 있습니다. 사실 유연하고 재사용 가능한 코드를 설계하기 위해서는 두 종류의 의존성을 서로 다르게 만들어야 합니다.
유연하고 재사용 가능한 설계를 창조하기 위해서는 동일한 소스코드 구조를 가지고 다양한 실행 구조를 만들 수 있어야 합니다.
어떤 클래스의 인스턴스가 다양한 클래스의 인스턴스와 협력하기 위해서는 협력할 인스턴스의 구체적인 클래스를 알아서는 안 됩니다.
컴파일타임 구조와 런타임 구조 사이의 거리가 멀면 멀수록 설계가 유연해지고 재사용 가능해집니다.
컨텍스트 독립성
클래스가 사용될 특정한 문맥에 대해 최소한의 가정만으로 이뤄져 있다면 다른 문맥에서 재사용하기가 더 수월해지는 것을 컨텍스트 독립성이라고 부릅니다.
설계가 유연해지기 위해서는 가능한 한 자신이 실행될 컨텍스트에 대해 구체적인 정보를 최대한 적게 알아야 합니다.
컨텍스트 독립성을 따르면 다양한 컨텍스트에 적용할 수 있는 응집력 있는 객체를 만들 수 있고 객체 구성 방법을 재설정해서 변경 가능한 시스템으로 나아갈 수 있습니다.
의존성 해결하기
컴파일타임 의존성은 구체적인 런타임 의존성으로 대체돼야 합니다.
컴파일타임 의존성을 실행 컨텍스트에 맞는 적절한 런타임 의존성으로 교체하는 것을 의존성 해결이라고 부릅니다.
의존성 해결하는 3가지 방법
객체를 생성하는 시점에 생성자를 통해 의존성을 해결
객체를 생성 후 setter 메서드를 통해 의존성 해결
메서드 실행 시 인자를 이용해 의존성 해결
02. 유연한 설계
의존성과 결합도
모든 의존성이 나쁜 것은 아닙니다. 의존성은 객체들의 협력을 가능하게 만드는 매개체라는 관점에서는 바람직한 것입니다.
의존성이 과하면 문제가 될 수 있습니다.
문제는 의존성의 존재가 아니라 의존성의 정도 입니다.
바람직한 의존성은 재사용성과 관련이 있습니다.
바람직한 의존성이란 컨텍스트에 독립적인 의존성을 의미하며 다양한 환경에서 재상용될 수 있는 가능성을 열어놓은 의존성을 의미합니다.
바람직하지 못한 의존성을 가리키는 좀더 세련된 용어가 결합도 입니다.
어떤 두 요소 사이에 존재하는 의존성이 바람직할 때 두 요소가 느슨한 결합도(loose coupling) 또는 약한 결합도(weak coupling)를 가진다고 말합니다.
반대로 두 요소 사이의 의존성이 바람직하지 못할 때 단단한 결합도(tight coupling) 또는 강한 결합도(strong coupling)를 가진다고 말합니다.
바람직한 의존성이란 설계를 재사용하기 쉽게 만드는 의존성 입니다.
의존성과 결합도
의존성은 두 요소 사이의 관계 유무를 설명합니다.
따라서 의존성의 관점에서는 ‘의존성이 존재한다’ 또는 ‘의존성이 존재하지 않는다’라고 표현해야 합니다.
결합도는 두 요소 사이엥 존재하는 의존성의 정도를 상대적으로 표현합니다.
따라서 결합도의 관점에서는 ‘결합도가 강하다’ 또는 ‘결합도가 느슨하다’라고 표현 합니다.
지식이 결합을 낳는다
서로에 대해 알고 있는 양이 결합도를 결정합니다.
결합도를 느슨하게 만들기 위해서는 협력하는 대상에 대해 필요한 정보 외에는 최대한 감추는 것이 중요합니다.
이를 해결하기 위해서 추상화를 사용합니다.
추상화에 의존하라
일반적으로 추상화와 결합도의 관점에서 의존 대상을 다음과 같이 구분하는 것이 유용합니다.
구체 클래서 의존성(concreate class dependency)
추상 클래스 의존성(abstract class dependency)
인터페이스 의존성(interface dependency)
결합도를 느슨하게 만들기 위해서는 구체적인 클래스보다 추상클래스에, 추상 클래스보다 인터페이스에 의존하도록 만드는 것이 더 효과적입니다.
의존하는 대상이 더 추상적일수록 결합도는 더 낮아집니다.
명시적인 의존성
모든 경우에 의존성은 명시적으로 퍼블릭 인터페이스에 노출하는 것을 명시적 의존성(explicit dependency)이라고 부릅니다.
의존성이 인터페이스에 표시되지 않는 것을 숨겨진 의존성(hidden dependency)이라고 부릅니다.
명시적인 의존성을 사용해야만 퍼블릭 인터페이스를 통해 컴파일타임 의존성을 적절한 런타임 의존성을로 교체할 수 있습니다.
클래스가 다른 클래스에 의존하는 것은 부끄러운 일이 아닙니다. 의존성은 다른 객체와의 협력을 가능하게 해주기 때문에 바람직한 것입니다. 경계해야 할 것은 의존성 자체가 아니라 의존성을 감추는 것입니다. 숨겨져 있는 의존성을 밝은 곳으로 드러내서 널리 알리면 설계가 유연하고 재사용 가능해 집니다.
new는 해롭다
new를 잘못 사용하면 클래스 사이의 결합도가 극단적으로 높아집니다.
해결 방법은 인스턴스를 생성하는 로직과 생성된 인스턴스를 사용하는 로직을 분리하는 것입니다. 필요한 인스턴스를 생성자의 인자로 전달받아 내부의 인스턴스 변수에 할당합니다.
사용과 생성의 책임을 분리하고, 의존성을 생성자에 명시적으로 드러내고, 구체 클래스가 아닌 추상 클래스에 의존하게 함으로써 설계를 유연하게 만들 수 있습니다. 그리고 그 출발은 객체를 생성하는 책임을 객체 내부가 아니라 클라이언트로 옮기는 것에서 시작했습니다.
가끔은 생성해도 무방하다
협력하는 기본 객체를 설정하고 싶은 경우는 클래스 안에서 객체의 인스턴스를 직접 생성하는 방식이 유용합니다.
여기서 트레이드오프의 대상은 결합도와 사용성입니다. 구체 클래스에 의존하게 되더라도 클래스의 사용성이 더 중요하다면 결합도를 높이는 방향으로 코드를 작성할 수 있습니다.
가급적 구체 클래스에 대한 의존성을 제거할 수 있는 방법을 찾기를 권장합니다.
종종 모든 결합도가 모이는 새로운 클래스를 추가함으로써 사용성과 유연성이라는 두 마리 토끼를 잡을 수 있느 경우도 있습니다.
표준 클래스에 대항 의존은 해롭지 않습니다
의존성이 불편한 이유는 그것이 항상 변경에 대한 영향을 암시하기 때문입니다. 따라서 변경될 확률이 거의 없는 클래스라면 의존성이 문제가 되지 않습니다.
비록 클래스를 직접 생성하더라도 가능한 한 추상적인 타입을 사용하는 것이 확장성 측면에서 유리합니다. 의존성에 의한 영향이 적은 경우에도 추상화와 의존성을 명시적으로 드러내는 것은 좋은 설계 습관입니다.
컨텍스트 확장하기
어떤 경우든 코드 내부를 직접 수정하는 것은 버그의 발생 가능성을 높입니다. 그렇기 때문에 예외 케이스도 새로운 정책으로 간주합니다.
결합도를 낮춤으로써 얻게 되는 컨텍스트의 확장이라는 개념이 유연하고 재사용 가능한 설계를 만드는 핵심입니다.
조합 가능한 행동
다양한 종류의 정책이 필요한 컨텍스트에서 클래스를 재사용할 수 있는 이유는 코드를 직접 수정하지 않고도 협력 대상인 인스턴스를 교체할 수 있기 때문입니다.
유연하고 재사용 가능한 설계는 작은 객체들의 행동을 조합함으로써 새로운 행동을 이끌어 낼 수 있는 설계입니다.
훌륭한 객체지향 설계란 객체가 어떻게 하는지를 표현하는 것이 아니라 객체들의 조합을 선언적으로 표현함으로써 객체들이 무엇을 하는지를 표현하는 설계입니다. 그리고 이런 설계를 창조하는 데 있어서의 핵심은 의존성을 관리하는 것입니다.