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
복사