의존성 관리에 대한 내용이다
자세한 내용이 궁금하면 오브젝트를 펼쳐보는 걸 추천드립니다.📖
시작하면서
객체지향 설계란 의존성을 관리하는 것이고 객체가 변화를 받아들일 수 있게 의존성을 정리하는 기술이라고 할 수 있다 [Metz12]
의존성 이해하기
변경과 의존성
어떤 객체가 협력하기 위해 다른 객체를 필요로 할 때 두 객체 사이에 의존성이 생기며, 실행 시점과 구현 시점에 서로 다른 의미를 가진다.
- 실행 시점 : 의존하는 객체가 동작하기 위해서 실행 시에 의존 객체가 있어야 한다.
- 구현 시점 : 의존 대상 객체가 변경되면 의존하는 객체도 변경된다.
또한 의존성은 변경의 영향 전파를 암시한다.
의존성 전이(transitive dependency)
의존성은 전이될 수 있다. 영화 할인 조건 코드를 다시 살펴보자.
public class Movie {
private Money fee;
private DiscountPolicy discountPolicy;
public Movie(Money fee, DiscountPolicy discountPolicy) {
this.fee = fee;
this.discountPolicy = discountPolicy;
}
public Money getFee() {
return fee;
}
public Money calculateDiscountFee(Screening screening) {
return fee.minus(discountPolicy.calculateDiscountAmount(screening));
}
}
public abstract class DiscountPolicy {
private List<DiscountCondition> conditions = new ArrayList<>();
public DiscountPolicy(DiscountCondition ... conditions) {
this.conditions = Arrays.asList(conditions);
}
public Money calculateDiscountAmount(Screening screening) {
for(DiscountCondition each : conditions) {
if (each.isSatisfiedBy(screening)) {
return getDiscountAmount(screening);
}
}
return Money.ZERO;
}
abstract protected Money getDiscountAmount(Screening screening);
}
public class AmountDiscountPolicy extends DiscountPolicy {
private Money discountAmount;
public AmountDiscountPolicy(Money discountAmount, DiscountCondition... conditions) {
super(conditions);
this.discountAmount = discountAmount;
}
@Override
protected Money getDiscountAmount(Screening screening) {
return discountAmount;
}
}
public interface DiscountCondition {
boolean isSatisfiedBy(Screening screening);
}
public class PeriodCondition implements DiscountCondition {
private int sequence;
public PeriodCondition(int sequence) {
this.sequence = sequence;
}
@Override
public boolean isSatisfiedBy(Screening screening) {
return screening.isSequence(sequence);
}
}
Screening을 주시하라. 해당 협력 관계에 모두 의존하고 있다.
의존성은 두 종류로 나눠진다.
- 직접 의존성 : 한 요소가 다른 요소에 직접 의존하는 경우, PeriodCondition이 Screening에 직접 의존하여 코드에 명시적으로 드러난다.
- 간접 의존성 : 직접적인 관계는 존재하지 않지만 의존성 전이에 의해 영향이 전파되는 경우로 코드에 명시적으로 드러나지 않는다.
런타임 의존성(run-time dependency)과 컴파일 의존성(complie-time dependency)
의존성은 다른 측면에서도 봐야 한다. 바로 런타임 의존성과 컴파일 의존성이다.
런타임은 애플리케이션이 실행되는 시점이고, 컴파일타임은 코드를 작성하는 시점이다.
런타임의 주인공은 객체로 런타임 의존성의 주제는 객체 사이의 의존성이고
컴파일의 주인공은 클래스로 컴파일 의존성의 주제는 클래스 사이의 의존성이다.
객체지향 프로그램의 실행 구조는 소스코드 구조와 일치하지 않는 경우가 종종 있다. 코드 구조는 컴파일 시점에 확정되는 것이고 이 구조에는 고정된 상속 클래스 관계들이 포함된다. 그러나 프로그램의 실행 시점 구조는 협력하는 객체에 따라서 달라질 수 있다. 즉, 두 구조는 전혀 다른 별개의 독립성을 갖는다...
컴파일 시점의 구조와 실행 시점 구조 사이에 차이가 있기 때문에 코드 자체가 시스템의 동작 방법을 모두 보여줄 수 없다. 시스템의 실행 시점 구조는 언어가 아닌 설계자가 만든 타입들 간의 관련성으로 만들어진다. 그러므로 객체와 타입 간의 관계를 잘 정의해야 좋은 실행 구조를 만들 수 있다. [Gof94]
컨텍스트 독립성
클래스는 협력 객체의 구체적인 클래스를 알면 알수록 강하게 결합되기 때문에 다른 문맥에서는 사용하기 어려워진다.
하지만 클래스가 사용될 문맥에 대해 최소한의 가정만 알면 다른 문맥에서 재사용하기 수월해지며, 이를 컨텍스트 독립성이라 한다.
한마디로 설계가 유연해지려면 구체적인 정보는 최소화해야 한다.
시스템을 구성하는 객체가 컨텍스트 독립적이라면 해당 시스템은 변경하기 쉽다. 여기서 컨텍스트 독립적이라는 말은 각 객체가 해당 객체를 실행하는 시스템에 관해 아무것도 알지 못한다는 의미다.
의존성 해결하기
컴파일타임 의존성에서 런타임 의존성으로 교체하는 것을 의존성 해결이라 부른다. 해결 방법은 세 가지가 있다.
1. 객체를 생성하는 시점에 생성자를 통해 의존성 해결
2. 객체 생성 후 setter 메서드를 통해 의존성 해결
3. 메서드 실행 시 인자를 이용해 의존성 해결
유연한 설계
의존성과 결합도
객체들이 협력하기 위해서는 서로의 존재와 수행 가능한 책임을 알아야 한다. 여기서 의존성이 생긴다. 따라서 모든 의존성은 협력이라는 매개체로 바람직한 것이지 모두 나쁜 것은 아니다.
문제는 의존성의 존재가 아니라 의존성의 정도다. 바람직한 의존성은 컨텍스트에 독립적이고 특정 컨텍스트에 결합된 의존성은 나쁜 것이다.
다른 용어로 결합도로 칭한다. 의존성이 바람직하면 낮은 결합도라 말하고 의존성이 나쁘면 높은 결합도라 한다.
추상화에 의존하라
추상화를 이용하면 문제 해결을 위한 불필요한 정보를 감출 수 있고 결합도를 느슨하게 관리해준다.
다음은 추상화와 결합도에 관점에서 구분하는 의존성이다.
- 구체 클래스 의존성(concrete class dependency) : 세부적인 구현 내용을 의존하며 높은 결합도를 가진다.
- 추상 클래스 의존성(abstract class dependency) : 메서드 내부 구현과 자식 클래스 종류를 숨긴다. 하지만 클라이언트는 상속 계층을 알아야 한다.
- 인터페이스 의존성(interface dependency) : 메시지 수신에 대한 지식만 남기기 때문에 낮은 결합도를 가진다.
new는 해롭다
new를 잘못 사용하면 결합도는 극단적으로 높아진다. 이유는 크게 두 가지다.
- 구체 클래스의 이름을 직접 명시한다.
- 어떤 인자를 이용해서 호출해야 하는지 알아야 한다.
로직에서 직접 new연산자를 사용하기보다는 외부에서 생성된 인스턴스를 받는 게 해결 방법이다.
* 가끔은 생성해도 무방한 경우도 있다. 주로 협력하는 기본 객체를 설정하고 싶은 경우다.
표준 클래스에 대한 의존은 해롭지 않다
의존성이 변경에 관련된 암시라고 했지만, JDK 표준 라이브러리처럼 변경될 확률이 거의 없는 경우는 해롭지가 않다.
조합 가능한 행동
유연하고 재사용 가능한 설계는 작은 객체들의 행동을 조합함으로써 새로운 행동을 이끌어내는 설계다. 훌륭한 객체지향 설계는 객체들의 조합으로 객체들이 무엇을 하는지 표현하는 설계다.
'서적 > 오브젝트: 코드로 이해하는 객체지향 설계' 카테고리의 다른 글
오브젝트 10_ 상속과 코드 재사용 (0) | 2021.06.09 |
---|---|
오브젝트 09_ 유연한 설계 (0) | 2021.06.09 |
오브젝트 07_ 객체 분해 (0) | 2021.06.09 |
오브젝트 06_ 메시지와 인터페이스 (0) | 2021.06.09 |
오브젝트 05_ 책임 할당하기 (0) | 2021.06.09 |