본문 바로가기
독서 기록

[오브젝트] 8장 - 의존성 관리하기

by 워냥 2024. 6. 13.


1. 개요

객체지향 설계를 위해 높은 응집도를 가진 객체를 만들게 된다.
응집도가 높은 객체는 한 가지 일만 잘하기에 다른 객체와 협력이 필요하다.
응집도가 높아질수록 자연스럽게 협력도 늘어난다.

 

하지만 협력을 하기 위해서는 다른 객체를 알아야 한다.
즉 협력의 과정에서 의존성이 생기게 되는 것이다.

 

협력을 위한 적절한 의존성은 유지하되, 변경을 막는 과도한 의존성은 제거해야 한다.
이를 의존성 관리라고 부른다.


2. 의존성이란?

실행 시점 의존성

런타임 의존성(run-time dependency)이라고도 부를 수 있다.
애플리케이션의 실행 시점에서 의존하는 객체가 정상적으로 동작하려면, 의존 대상 객체가 반드시 존재해야 한다.

 

구현 시점 의존성

컴파일 타임 의존성(compile-time dependency)이라고도 불린다.
코드 레벨에서 의존 대상 객체의 코드가 변경되면 의존하는 객체도 함께 변경된다.

 

유연한 개발을 위한 의존성 관리

런타임 의존성의 주체는 객체이고 컴파일 타임의 의존성은 클래스이다.
런타임 의존성과 컴파일 타임 의존성이 달라야 유연한 개발이 가능하다.

 

코드 레벨에서 의존하고 있는 대상의 정보를 구체적으로 몰라야 코드의 변경이 덜 일어난다.
추상화와 관련된 부분인데, 이후 의존성 해결 파트에서 다시 한번 다룬다.


3. 의존성 전이

의존성 전이(transitive dependency)는 A -> B -> C와 같이 의존하고 있는 상황에서 A도 C에 잠재적으로 의존하게 됨을 의미한다.

 

그러나 의존성은 함께 변경될 가능성을 의미하기 때문에 반드시 변경되는 것은 아니다.
의존성 전이는 의존성의 영향을 경고하기 위한 개념으로 사용된다.


4. 컨텍스트 독립성

클래스가 특정 문맥에 강하게 결합해 있으면 재사용이 어려워진다.
자신이 사용될 문맥에 대한 정보를 최소한만 포함하고 있어야 재사용이 수월하다.

 

그렇다면 클래스는 실행 컨텍스트를 모르는데, 어떻게 적절한 객체와 협업할 수 있을까?


5. 의존성 해결하기

컴파일타임 의존성을 실행 컨텍스트에 맞는 적절한 런타임 의존성으로 교체하는 것을 의존성 해결이라고 한다.
의존성 해결에는 세 가지 방법이 존재한다.

생성자를 통한 의존성 해결

Movie avatar = new Movie("아바타",
    Duration.ofMinutes(120),
    Money.wons(10000),
    new AmountDiscountPolicy(...));

생성자로 알맞은 인스턴스를 전달한다.

 

setter 메서드를 통한 의존성 해결

Movie avatar = new Movie(...);
avatar.setDiscountPolicy(new AmountDiscountPolicy(...));

인스턴스가 생성된 후 정책을 설정할 수 있는 setter 메서드를 제공한다.
setter 메서드를 이용하면 의존하고 있는 대상을 변경할 수 있는 유연성이 생긴다.

 

생성자 방식과 setter 방식을 혼합하면 의존성을 유연하게 관리할 수 있다.

 

인자를 이용한 의존성 해결

fee.minus(discountPolicy.calculateDiscountAmount(screening));

클래스가 할인 정책을 항상 알 필요가 없다면 필요한 시점에 인자로 받아 의존성을 해결할 수 있다.


6. 의존성과 결합도

바람직한 의존성

어떤 클래스를 재사용해야 하는 경우 내부 구현을 변경하게 된다면, 이는 바람직하지 못한 의존성이다.
반대로 컨텍스트에 독립적이라 다양한 환경에서 재사용할 수 있다면, 이는 바람직한 의존성이다.

 

바람직한 의존성은 약한 결합도(weak coupling), 바람직하지 못한 의존성은 강한 결합도(strong coupling)이라고 한다.

 

의존성과 결합도

의존성은 두 요소 사이의 관계 유무를 의미한다.
"의존성이 존재한다.", "의존성이 존재하지 않는다."라고 표현한다.

 

결합도는 두 요소 사이의 의존성의 정도를 표현한다.
"결합도가 강하다.", "결합도가 약하다."라고 표현한다.

 

지식과 결합도

한 요소가 다른 요소에 대해 많은 정보를 가지고 있다면, 둘은 강하게 결합한다.
많이 알고 있다는 것은 컨텍스트에 대한 지식을 많이 가지고 있다는 것이다.
따라서 요소를 재사용하기 위해서는 코드 수정이 필요할 것이다.

 

약하게 결합하기 위해 두 요소가 정보를 최대한 감추는 것이 중요하다.
정보를 감추기 위해 추상화를 사용하면 된다.

 

추상화에 의존하라

추상화에 의존할수록 두 요소가 알고 있는 정보가 적어져 관계가 유연해진다.
즉 추상화 정도가 높아질수록 결합도는 느슨해지게 된다.

  • 구체 클래스 의존성(concrete class dependency)
  • 추상 클래스 의존성(abstract class dependency)
  • 인터페이스 의존성(interface dependency)

아래로 갈수록 추상화 정도는 높아지고, 결합도는 낮아지게 된다.


7. 명시적 의존성

의존성은 퍼블릭 인터페이스에 노출되는 것이 좋다.
이를 명시적인 의존성(explict dependency) 라고 부른다.

 

의존성이 숨겨져 있으면 해당 요소가 어떤 의존성을 가졌는지 찾기 어렵다.
구현 내부에 숨겨진 의존성은 나중에 버그를 유발할 가능성이 높다.


8. 느낀 점

의존성 문제는 코드를 작성하다 보면 반드시 마주치게 된다.
높은 의존성 때문에 기능 하나를 수정할 때마다 모든 코드가 수정되는 경험을 많이 했다.

 

의존성을 관리하기 위해 적절한 추상화를 사용해야 하고, 의존성을 명시적으로 보이게 하는 것이 좋다는 것을 알게 되었다.

댓글