Skip to content

오브젝트, CHAPTER 08 의존성 관리하기, 2024-09-01 #456

Description

@jongfeel

CHAPTER 08 의존성 관리하기

객체지향 설계의 핵심은 협력을 위해 필요한 의존성은 유지하면서 변경을 방해하는 의존성은 제거하는 데 있다.
이런 관점에서 객체지향 설계는 의존성을 관리하는 것이고 객체가 변화를 받아들일 수 있게 의존성을 정리하는 기술이라고 할 수 있다[Metz12].

01 의존성 이해하기

변경과 의존성

의존성 전이

런타임 의존성과 컴파일타임 의존성

어떤 클래스의 인스턴스가 다양한 클래스의 인스턴스와 협력하기 위해서는 협력할 인스턴스의 구체적인 클래스를 알아서는 안 된다.
실제로 협력할 객체가 어떤지는 런타임에 해결해야 한다.
클래스가 협력할 객체의 클래스를 명시적으로 드러내고 있다면 다른 클래스의 인스턴스와 협력할 가능성 자체가 없어진다.
따라서 컴파일타임 구조와 런타임 구조 사이의 거리가 멀면 멀수록 설계가 유연해지고 재사용이 가능해진다.

컨텍스트 독립성

클래스가 특정 문맥에 강하게 결합될수록 다른 문맥에서 사용하기는 더 어려워진다.
클래스가 사용될 특정한 문맥에 대해 최소한의 가정만으로 이뤄져 있다면 다른 문맥에서 재사용하기가 더 수월해진다.
이를 컨텍스트 독립성이라고 부른다.

의존성 해결하기

컴파일 타임 의존성은 구체적인 런타임 의존성으로 대체된다.
이것을 의존성 해결이라고 부른다.

  • 객체를 생성하는 시점에 생성자를 통해 의존성 해결
  • 객체 생성 후 setter 메서드를 통해 의존성 해결
  • 메서드 실행 시 인자를 이용해 의존성 해결

02 유연한 설계

의존성과 결합도

의존성은 협력을 위해 반드시 필요한 것으로 나쁜 것은 아니다.
다만 바람직하지 못한 의존성이 유연하지 못한 협력 관계를 만든다.
바람직한 의존성은 재사용성과 관련이 있다. 다양한 환경에서 재사용할 수 있다면 그 의존성은 바람직한 것이다.

이걸 표현하는 용어로 결합도(coupling)가 있다.
의존성이 바람직하면 loose coupling, weak coupling을 가진다고 한다.
반대라면 tight couping, stroing couping을 가진다고 표현한다.

의존성은 두 요소 사이의 관계 유무를 표현하므로
정확히는 "의존성이 존재한다" 혹은 "의존성이 존재하지 않는다"라고 표현하는 것이 맞다.
결합도는 의존성의 정도를 상대적으로 표현하는 것이므로
"결합도가 강하다", "결합도가 느슨하다"로 표현한다.

지식이 결합을 낳는다

더 많이 알수록 더 많이 결합된다.
더 많이 알고 있다는 것은 더 적은 컨텍스트에서 재사용 가능하다는 것을 의미한다.
기존 지식에 어울리지 않는 컨텍스트에서 클래스의 인스턴스를 사용하기 위해서는 클래스를 수정해야 한다.
결합도를 느슨하게 유지하려면 협력하는 대상에 대해 더 적게 알아야 한다.

추상화에 의존하라

추상화와 결합도의 관점에서 의존 대상의 목록 중 아래로 갈 수록 클라이언트가 알아야 하는 지식의 양이 적어진다.

  • concrete class dependency
  • abstract class dependency
  • interface dependency

인터페이스는 상속 계층을 몰라더 협력이 가능해진다. 추상 클래스 의존성 보다는 결합도가 낮은데
다양한 클래스 상속 계층에 속한 객체들이 동일한 메시지를 수신할 수 있도록 컨텍스트 확장이 가능하기 때문이다.

실행 컨텍스트 정보가 적을수록 결합도가 낮아진다.
의존 대상이 더 추상적일수록 결합도는 더 낮아진다.

명시적인 의존성

퍼블릭 인터페이스에 노출된다면 명시적인 의존성(explicit dependency)이라고 부른다.
내부에서 직접 의존성 인스턴스를 생성하는 방식은 외부에 정보를 감추게 된다.
즉, 퍼블릭 인터페이스에 표현되지 않는데 이를 숨겨진 의존성(hideen dependency)이라고 부른다.

의존성은 명시적으로 표현돼야 한다.
유연하고 재사용 가능한 설계란 퍼블릭 인터페이스를 통해 의존성이 명시적으로 드러나는 설계다.
그래야 컴파일 타임 의존성을 런타임 의존성으로 교체할 수 있다.

new는 해롭다

  • 구체 클래스의 이름을 직접 기술하게 된다. 따라서 추상화가 아닌 구체 클래스에 의존하게 된다.
  • 생성해야 하는 인자의 정보도 알고 있어야 한다. 이것은 지식의 양이 늘어나게 되므로 결합도가 높아진다.

사용과 생성의 책임 분리
의존성을 생성자에 명시적으로 노출
구체 클래스가 아닌 추상 클래스에 의존

가끔은 생성해도 무방하다.

주로 협력하는 기본 객체를 설정하고 싶은 경우에는 그렇게 해도 된다.
의존성에 해당하는 기본 객체는 내부에서 생성해 두고
객체 생성 시점이나 의존성 설정 시점에 변경 가능하게 하는 방식.

표준 클래스에 대한 의존은 해롭지 않다

변경될 확률이 없는 클래스는 의존성이 문제되지 않는데, JDK에 포함된 표준 클래스가 이 부류에 속한다.
예로 ArrayList의 경우는 직접 생성해서 대입하는 것이 일반적이고 ArrayList가 수정될 확률은 없기 때문에 인스턴스를 직접 생성해도 문제되지 않는다.

다만 추상 타입을 사용하는 것이 확장성 측면에서 유리하므로 List 인터페이스 타입으로 대체하면 설계 유연성을 높일 . 수있다.

컨텍스트 확장하기

할인 혜택이 존재하지 않는다는 것도 정책의 한 종류로 간주하고 NoneDiscountPolicy 클래스를 추가하면
할인 정책의 유무에 대한 분기 처리를 하지 않고도 구현할 수 있다.

중복 할인 정책을 적용할 수 있다고 한다면 DiscountPolicy의 array를 받을 수 있게 인터페이스를 변경하면 중복 할인을 쉽게 적용할 수 있다.

조합 가능한 행동

유연하고 재사용 가능한 설계는 객체가 어떻게(how) 하는지를 장황하게 나열하지 않고도 객체들의 조합을 통해 무엇(what)을 하는지를 표현하는 클래스들로 구성된다.

클래스의 인스턴스를 생성하는 코드
객체가 어떤 객체와 연결되었는지
를 파악하는 것으로 객체의 행동을 예상하고 이해할 수 있다.
이는 선언적으로 객체의 행동을 정의할 수 있는 것으로 볼 수 있다.

훌륭한 객체지향 설계는 객체가 어떻게 하는지를 표현하는 것이 아니라
객체들의 조합을 선언적으로 표현해 객체들이 무엇을 하는지를 표현하는 설계이다.
이런 설계를 하는데 있어서의 핵심은 의존성을 관리하는 것이다.

Metadata

Metadata

Assignees

Labels

GDGGDG Songdo and Incheon activity

Projects

Relationships

None yet

Development

No branches or pull requests

Issue actions