Java Mid 18 - 전략패턴과 다양한 의존관계
in Programming Language on Java
컴파일 의존관계 vs 런타임 의존관계
전략패턴을 이해하기 전에 우선 컴파일 의존관계가 뭔지, 런타임 의존관계가 뭔지 알면 좋다.
컴파일 타임 의존관계와 런타임 의존관계는 객체지향 프로그래밍에서 의존성이 언제 결정되고, 어떤 시점에서 실제로 동작하는지를 설명하는 개념이다.
컴파일 타임 의존관계 (Compile-time Dependency)
- 정적(Static) 의존성이라고도 부른다.
- 컴파일 시점에 결정되는 의존관계로, 프로그램이 컴파일될 때 클래스와 메소드, 변수 등 객체 간의 참조 관계가 미리 결정된다.
- 소스 코드에서 사용된 클래스나 인터페이스가 어떤 구체적인 객체를 참조하는지 명확하게 나타난다.
public class Animal {
public void makeSound() {
System.out.println("Some sound");
}
}
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Bark");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Dog(); // 컴파일 타임 의존성: Animal과 Dog 간의 관계가 컴파일 시점에 결정됨
animal.makeSound();
}
}
여기서 Animal과 Dog는 컴파일 시점에 의존성이 설정된다. 코드 상에서 Dog가 어떤 클래스인지 명확히 참조되고 있으므로, 컴파일러가 타입을 알고 그에 맞춰 코드를 컴파일할 수 있다.
- 여기서
Animal과Dog는 컴파일 시점에 의존성이 설정된다. 코드 상에서Dog가 어떤 클래스인지 명확히 참조되고 있으므로, 컴파일러가 타입을 알고 그에 맞춰 코드를 컴파일할 수 있다.
런타임 의존관계 (Runtime Dependency)
- 동적(Dynamic) 의존성이라고도 한다.
- 프로그램 실행 시점에 결정되는 의존관계로, 구체적인 객체나 클래스의 생성과 연결이 런타임에 이루어진다.
- 특히 의존성 주입(DI, Dependency Injection), 전략 패턴(Strategy Pattern)과 같은 패턴을 사용할 때, 인터페이스에 대한 의존성은 컴파일 타임에 결정되지만 구체적인 구현체는 런타임에서 결정된다.
public class Main {
public static void main(String[] args) {
Animal animal = getAnimalInstance(); // 런타임 의존성: 어떤 구체적 객체가 생성될지 실행 시점에 결정됨
animal.makeSound();
}
public static Animal getAnimalInstance() {
// 런타임에 동적으로 객체를 결정 (예: 조건에 따라)
return new Dog(); // 혹은 new Cat();
}
}
- 여기서
getAnimalInstance()메소드는 실행 중에 동적으로 객체를 생성하고 반환하므로, 구체적인 클래스의 선택이 런타임에 이루어진다. 이와 같은 경우, 런타임에서 객체가 결정되고 의존성이 형성된다.
전략 패턴과 런타임 의존관계의 관계
- 전략 패턴은 행위(알고리즘)를 동적으로 교체할 수 있는 디자인 패턴으로, 이를 가능하게 하는 핵심은 런타임 의존관계다.
- 컴파일 타임에는 추상적인 인터페이스 또는 상위 클래스에 의존하고, 구체적인 알고리즘(구현체)은 런타임에 주입되기 때문에 의존성을 유연하게 관리할 수 있다.
- 컴파일 타임에는 인터페이스나 추상 클래스에 대한 의존성만 결정된다.
- 하지만 실제 구현체는 프로그램이 실행되는 런타임 시점에 결정되며, 이로 인해 다양한 상황에 맞게 알고리즘을 동적으로 선택하고 적용할 수 있다.
java
코드 복사
// Strategy 인터페이스
public interface Strategy {
void execute();
}
// ConcreteStrategy A
public class StrategyA implements Strategy {
public void execute() {
System.out.println("Executing Strategy A");
}
}
// ConcreteStrategy B
public class StrategyB implements Strategy {
public void execute() {
System.out.println("Executing Strategy B");
}
}
// Context 클래스
public class Context {
private Strategy strategy;
// 런타임에서 전략을 주입받음
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public void performTask() {
strategy.execute(); // 런타임에 결정된 전략 실행
}
}
// Main 클래스
public class Main {
public static void main(String[] args) {
Context context = new Context();
// 런타임에 Strategy A 선택
context.setStrategy(new StrategyA());
context.performTask(); // 출력: Executing Strategy A
// 런타임에 Strategy B로 변경
context.setStrategy(new StrategyB());
context.performTask(); // 출력: Executing Strategy B
}
}
런타임에 구체적인 전략을 선택하므로, 다양한 상황에 맞춰 행위를 변경할 수 있다.
새로운 전략(구현체)을 추가해도 클라이언트 코드의 수정 없이 유연하게 교체 가능하다.
클라이언트는 구체적인 구현체에 의존하지 않고 추상적인 인터페이스에 의존하므로, 구체적인 변경이 클라이언트에 영향을 미치지 않음.
컴파일 타임 의존관계와의 차이
- 컴파일 타임 의존관계에서는 모든 의존성이 코드 내에서 고정적으로 결정되며, 런타임에 변경할 수 없다.
- 반면, 런타임 의존관계를 사용하면 실행 중에 구체적인 구현체를 선택하거나 변경할 수 있기 때문에, 프로그램의 동적 동작을 구현할 수 있다.
Reference
인프런 김영한님의 자바강좌 + 나의 뇌