Java Abstract - 컴파일, 런타임 의존 관계
의존관계 종류는 컴파일 타임 의존관계와 런타임 의존관계로 이루어져 있다.
- Compile Time
- 코드 컴파일 시점의 이야기한다.
- 작성한 코드를 타입 및 여러 요소를 결정하고 생각하는 시점이다.
- 당연하겠지만 인스턴스는 없다.
- Runtime
- 프로그램 실행 시점을 이야기한다.
- 새로운 인스턴스가 생성한다.
- 가비지 콜렉션이 동작 중인 상태이다.
컴파일 타임 의존관계와 런타임 의존관계
컴파일 타임 의존관계
IList <---IList 의존 관계--- WatchProcessor
+---------+ +-----------------------+
| size() | | IList<> list |
| add() | | WatchProcessor(CList) |
| get() | | proc(size) |
| ... | +-----------------------+
+----+----+
↑
┌-----+-----┐
+----+----+ +----+----+
| size() | | size() |
| add() | | add() |
| get() | | get() |
| ... | | ... |
+----+----+ +----+----+
CList CLinkedList
- 컴파일 타임 의존관계인 것은 자바 컴파일러가 보고 있는 의존 관계이다.
- 컴파일은 클래스의 모든 의존관계가 나타난다.
- 부모인 IList 에서는 CList, CLinkedList 관련 코드를 작성하지 않았고 이 클래스들을 모른다.
- CList, CLinkedList는 부모인 IList 를 안다. 명확히 코드로 작성되어있다.
- WatchProcessor 클래스는 IList 인터페이스를 사용하나 CList, CLinkedList 를 모른다. 오로지 IList 인터페이스에만 의존하게 된다.
런타임 의존관계
WatchProcessor
+-----------------------+
| IList<> list = x01 | <- 참조 값 전달 받음
| WatchProcessor(CList) |
| proc(size) |
+-----------------------+
↑
┌-----┘
+----+----+ +----+----+
| size() | | size() |
| add() | | add() |
| get() | | get() |
| ... | | ... |
+----+----+ +----+----+
CList CLinkedList
x01 x02
- 런타임 의존관계는 프로그램이 동작 중일 때 보이는 의존관계로 생성한 인스턴스와 그것을 참조하는 의존 관계를 나타낸다.
- 런타임 의존관계는 언제든 계속 변할 수 있다.
예제 - WatchProcessor 클래스 CList 의존관계 사용
CList<Integer> list = new CList<>();
WatchProcessor watch = new WatchProcessor(list);
watch.proc(1_000_000);
- WatchProcess 클래스는 의존관계인 IList 에게 CList 타입의 list 인스턴스로 참조하고 있다.
- 이는 list(x01) 인스턴스가 WatchProcessor 에게 의존하는 것이다.
예제 - WatchProcessor 클래스 CLinkedList 의존관계 사용
WatchProcessor
+-----------------------+
| IList<> list = x02 | <- 참조 값 전달 받음
| WatchProcessor(CList) |
| proc(size) |
+-----------------------+
↑
└-------┐
+----+----+ +----+----+
| size() | | size() |
| add() | | add() |
| get() | | get() |
| ... | | ... |
+----+----+ +----+----+
CList CLinkedList
x01 x02
CLinkedList<Integer> list = new CLinkedList<>();
WatchProcessor watch = new WatchProcessor(list);
watch.proc(1_000_000);
- WatchProcess 클래스는 의존관계인 IList 에게 CLinkedList 타입의 list 인스턴스로 참조하고 있다.
- 이는 list(x02) 인스턴스가 WatchProcessor 에게 의존하는 것이다.
정리하기
WatchProcessor - 인터페이스 도입
public class WatchProcessor {
private final IList<Integer> list;
...
}
- IList 인터페이스 도입으로 리스트 자료구조를 사용하면서 원하는 알고리즘으로 변경할 수 있게 되었다.
- 리스트 의존 관계를 미리 결정한 것이 아닌 런타임 객체 생성 시점으로 미뤘으므로 IList 구현체를 변경하여도 WatchProcessor 코드를 변경이 필요 없다.
- 생성자 통해 런타임 의존관계를 주입하였으므로 생성자 의존관계 주입이다.
- OCP 원칙을 올바르게 지켰으므로 클라이언트 코드 변경 없이 IList 인터페이스의 구현을 자유롭게 확장할 수 있다.
WatchProcessor - 구체적인 자료 구조 사용
public class WatchProcessor {
private final CList<Integer> list = new CList<>();
...
}
- 만약, WatchProcessor 클래스가 구체적인 CList 사용한다면 컴파일 타임 의존 관계가 고정이 되버린다. 이 경우 알고리즘 변경 시 많은 코드를 변경해주어야 한다.
의존 관계 사용으로 클라이언트 클래스는 컴파일 타임에 추상화를 의존하고, 런타임에 의존 관계 주입해 구현체를 외부에서 주입 받아 사용하는 것이 많은 이점을 얻게 된다.
💡
전략 패턴(Strategy Pattern)
디자인 패턴 중 중요한 패턴 중 하나로 전략 패턴이다. 전략 패턴의 특징은 알고리즘을 클라이언트 코드의 변경 없이 쉽게 교체하는 것이다.
즉 예시에서 사용한 IList 인터페이스도 전략 패턴에 속하게 된다.
디자인 패턴 중 중요한 패턴 중 하나로 전략 패턴이다. 전략 패턴의 특징은 알고리즘을 클라이언트 코드의 변경 없이 쉽게 교체하는 것이다.
즉 예시에서 사용한 IList 인터페이스도 전략 패턴에 속하게 된다.
참고 내용 - 재사용성 실전에서의 느낌 점
재사용성과 확장성 있는 코드로 작성하면서 느낀 점은 코드의 재사용을 높이려면 외부에서 결정을 미루는 것이었다. 내부에서 미리 결정하면 출력 결과에 변화가 필요한 경우 내부 코드를 모두 뜯어야 한다. 이러한 일을 막기 위해 재사용과 확장성 있는 코드를 작성해왔고, 이러한 코드의 공통은 외부에서 무언가 전달 받아 실행하는 것이었다.