////
Search

8장 - 의존성 관리하기

Date
2024/03/18 14:43
Tags

1. 의존성 이해하기

1.1. 변경과 의존성

어떤 객체가 협력하기 위해 다른 객체를 필요로 할 때 두 객체 사이에 의존성이 존재하게 된다.
이는 실행 시점과 구현 시점에 서로 다른 의미를 가진다.
실행 시점 = 의존하는 객체가 정상적으로 동작하기 위해서는 실행 시에 의존 대상 객체가 반드시 존재해야 한다.
구현 시점 = 의존 대상 객체가 변경될 경우 의존하는 객체도 함께 변경된다.
두 요소 사이의 의존성은 의존되는 요소가 변경될 때 의존하는 요소도 함께 변경될 수 있다.
의존성은 변경에 의한 영향의 전파 가능성을 암시한다.

1.2. 의존성 전이

의존성 전이는 특정 객체가 의존하는 객체에 의존하는 경우 다른 객체가 가지는 의존성에 대해 자동적으로 의존하게 된다는 것이다.
의존성은 함께 변경될 가능성을 의미하기에 모든 경우에 의존성이 전이되는 것은 아니다.
의존성의 실제 전이 여부는 캡슐화에 따라 달라진다.
의존성은 전이될 수 있기 때문에 의존성의 종류를 직접 의존성과 간접 의존성으로 나누기도 한다.
직접 의존성 = 말 그대로 한 요소가 다른 요소에 직접 의존하는 경우
간접 의존성 = 직접적인 관계는 존재하지 않지만 의존성 전이의 영향이 전파되는 경우
시스템간 의존성이 되더라도 그 본질은 변하지 않는다.

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

런타임 = 애플리케이션이 실행되는 시점
컴파일타임 = 일반적으로 작성된 코드를 컴파일하는 시점을 가르키지만 문맥에 따라 코드 그 자체를 가르키기도 한다.
객체지향 애플리케이션에서 런타임의 주인공은 객체로 런타임 의존성이 다루는 주제는 객체 사이의 의존성이다.
컴파일타임의 의존성이 다루는 문제는 클래스 사이의 의존성이다.
컴파일타임에서는 구현체에 대한 어떤 언급도 없지만 런타임에서는 실제 구현체 인스턴스와 협력한다.
컴파일타임에서 특정 구현체에 대해서만 의존한다면 다른 객체를 런타임에서 다르게 의존할 수 없을 것이다.
이는 다른 할인 정책(strategy)의 추가를 어렵게한다.
결합도를 높히고, 유연성을 저하시킨다.
코드 작성시점은 구현체의 존재 유무를 모르지만 실행 시점에서는 구현체와 협력할 수 있게된다.
유연하고 재사용 가능한 설계를 만들기 위해서는 동일한 소스코드 구조를 가지고 다양한 실행 구조를 만들 수 있어야 한다.
컴파일타임과 런타임 구조 사이의 거리가 멀면 멀수록 설계가 유연해지고 재사용 가능해진다.

1.4. 컨텍스트 독립성

구체적인 클래스를 알면 알수록 그 클래스가 사용되는 특정한 문맥에 강하게 결합된다.
이는 다른 문맥에서 사용을 어렵게 만든다.
클래스가 사용될 특정한 문맥에 대해 최소한의 가정만으로 이뤄져 있다면 문맥에서 재사용하기가 더 수월해진다.
이를 컨텍스트 독립성이라고 부른다.
컨텍스트에 대한 정보가 적을 수록 더 다양한 컨텍스트에서 재사용될 수 있다.
결과적으로 유연하고 변경에 탄력적으로 대응할 수 있게 될 것이다.

1.5. 의존성 해결하기

의존성 해결 = 컴파일 타임 의존성을 실행 컨텍스트에 맞는 적절한 런타임 의존성을 교체하는 작업
의존성 해결을 위해서 사용하는 일반적인 3가지 방법
객체를 생성하는 시점에 생성자를 통해 의존성 해결
객체 생성 후 setter 메서드를 통해 의존성 해결
다만 객체 생성 이후 전달되기 때문에 중간에 객체 사용시 NPE가 발생할 수 있다.
메서드 실행 시 인자를 이용해 의존성 해결
가장 좋지만 메서드가 실행될 때마다 의존성이 달라져야할 경우에만 유용하다.

2. 유연한 설계

2.1. 의존성과 결합도

협력은 의존성을 낳고, 모든 의존성이 나쁜건 아니지만 과하면 문제가 될 수 있다.
추상화에 의존하지 않는 경우 재사용성의 여지를 없애버린다.
바람직한 의존성 이란? 컨텍스트 독립성을 지키며 다양한 환경에서 재사용 할 수 있도록 의존하는 것 = 재사용성

2.2. 지식이 결합을 낳는다.

결합도의 정도는 한 요소가 자신이 의존하고 있는 다른 요소에 대해 알고 있는 정보의 양으로 결정된다.
더 많은 정보를 알고 있을 수록 더 많이 결합된다.
결합도의 단계(출처 기록공간 티스토리)

2.3. 추상화에 의존하라

추상화 = 어떤 양상, 세부사항, 구조를 좀 더 명확하게 이해하기 위해 특정 절차나 물체를 의도적으로 생략/감춤으로써 복잡도를 줄이는 방법
이를 사용하면 다루고 있는 문제를 해결하는 데 불필요한 정보를 감출 수 있다.
지식의 양을 의도적으로 줄임으로서 느슨한 결합을 유지할 수 있다.

2.4. 명시적인 의존성

의존성의 대상을 생성자의 인자로 전달받는 방법과 생성자 안에서 직접 생성하는 방법의 가장 큰 차이는 퍼블릭 인터페이스를 통해 전략을 주입받을 수 있는지에 대한 여부다.
이런 방법을 명시적인 의존성이라고 부른다.
반대로 내부적으로 직접 구현체를 지정해 생성하는 방법을 숨겨진 의존성이라 부른다.

2.5. new는 해롭다

new를 잘못 사용하면 결합도가 극단적으로 높아진다.
new 연산자를 사용하기 위해서는 구체 클래스의 이름을 직접 기술해야 한다.
new를 사용하는 클라이언트는 추상화가 아닌 구체 클래스에 의존할 수밖에 없기 때문에 결합도가 높아진다.
new 연산자는 생성하려는 구체 클래스뿐만 아니라 어떤 인자를 이용해 클래스의 생성자를 호출해야 하는지도 알이야 한다.
new를 사용하면 클라이언트가 알아야 하는 지식의 양이 늘어나기 때문에 결합도가 높아진다.
사용의 책임과 생성의 책임을 분리해서 의존성을 생성자에 명시적으로 드러내고 이를 주입받아 사용해 설계를 유연하게 만들 수 있다.

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

진리의 케바케 대다수의 상황은 주입이 옳지만 직접 생성이 유용한 경우도 물론 있다.
주로 기본 협력객체를 설정하고 싶은 경우가 이에 속한다.
좋은 방법으로는 일반적으로 외부에 의존성을 알리되 별도의 기본 메서드를 만들어 기본생성 메서드를 체이닝해 기본 객체를 생성하는 것이다.
public class Movie { private DiscountPolicy discountPolicy; public Movie(String title, Duration runningTime, Money fee) { this(title, runningTime, fee, new AmountDiscountPoIicy(...)); } public Movie(String title, Duration runningTime, Money fee, DiscountPoIicy discountPolicy) { ... this. discountPolicy = discountPolicy; } }
Java
복사