전략 패턴 (Strategy Pattern)
객체들이 할 수 있는 행위에 대해 전략 클래스를 생성하고, 유사한 클래스를 캡슐화하는 인터페이스를 정의한다.
객체의 행위를 동적으로 수정하고 싶은 경우, 직접 행위를 수정하지 않고 전략을 바뀌주기만 함으로써 행동을 유연하게 확장할 수 있다.
즉, 일련의 알고리즘을 정의하고 각각을 캡슐화하고, 상호 교환 가능하게 만든다.
패턴을 사용하면 알고리즘을 사용하는 클라이언트와 독립적으로 알고리즘을 변경할 수 있게 된다.
샘플 오리 프로그램
Duck을 상속받는 RedheadDuck과 MallardDuck이 존재한다.
이들은 Duck에 속하는 객체로서 공통된 quack, swim 메서드를 수행하고, display 메서드는 오버라이딩해서 각자에 맞게 사용한다.
요구사항 추가
오리에게 날 수 있다는 조건을 추가하라고 지시받았다.
그래서 Duck에 fly() 메서드를 만들기 직전이다.
그런데 Duck의 모든 하위 클래스가 날 수 있는건 아니라는 사실을 깨달았다.
그래서 Duck을 상속받고 fly를 Override 하는 방법을 선택하려 한다.
문제가 많이 발생했다.
새로운 오리 종류가 추가될 때마다 다양한 특징을 가진 오리가 늘어났고, 오버라이딩이 잦아졌다.
결국 상속을 사용한 문제 해결은 어려워졌고 다음과 같은 단점을 발견했다.
- 코드는 서브클래스들간에 중복된다.
- 런타임 동작 변경이 어렵다.
- 오리에 대한 모든 세부사항을 확인하기 어렵다.
- Duck 클래스의 변경 사항은 의도치않게 다른 오리들에게 영향을 줄 수 있다.
해결 방안
변경되는 부분을 가져와 캡슐화 한다면 나중에 변경되지 않는 부분에 대해 영향을 주지 않고 변경하거나 확장할 수 있다.
즉, 코드의 나머지 부분에 영향을 미치지 않도록 다양한 부분을 캡슐화 해야한다.
변하는 것과 변하지 않는 것 구분하기
- fly()와 quack() 메서드는 오리에 따라 달라지는 Duck 클래스의 일부이다.
fly와 quack을 각각 캡슐화 하도록 하자.
새로운 디자인을 통해 Behavior 인터페이스로 각각을 캡슐화 할 수 있다.
이를 통해 슈퍼클래스였던 Duck의 덩치가 약간 줄어들고 변하는 사항을 분리할 수 있다.
Fly와 Quack 행동에 대해서 인터페이스를 만들고 이들을 구현한 구현체들을 생성했다.
장점
- 재사용성 증가 - Duck이 아닌 다른 클래스에 대해서도 Fly와 Quack을 사용할 수 있게 되었다.
- OCP 원칙 준수 - 새로운 행동의 추가가 기존의 코드에 영향을 주지 않는다.
질문
1. 언제나 애플리케이션을 만들고 변경 사항이 있음을 확인한 후에 캡슐화 해야하는가?
항상 그렇지 않다.
변경될 영역이 예상된다면 이를 처리할 수 있는 유연한 코드를 개발할 수 있다.
전략 패턴의 적용은 개발 수명 주기의 모든 단계에서 적용 가능하다.
샘플 오리 프로그램 리팩토링
1. FlyBehavior 및 QuackBehavior 유형의 두 인스턴스 변수를 추가한다 .
2. 각각의 오리 개체는 비행을 위한 FlyWithWings 및 Quack 구현체를 런타임 시 객체에 할당할 수 있다.
3. 이 동작을 FlyBehavior 및 QuackBehavior 클래스로 이동했기 때문에 Duck 클래스(및 모든 하위 클래스)에서 fly() 및 quack() 메서드를 제거해야한다.
4. Duck 클래스의 fly() 및 quack()를 두 개의 유사한 메서드인 performFly() 및 performQuack()로 교체한다.
전략 적용
Duck 클래스
public abstract class Duck {
protected FlyBehavior flyBehavior;
protected QuackBehavior quackBehavior;
public void performQuack() {
quackBehavior.quack();
}
public void performFly() {
flyBehavior.fly();
}
public void swim() {
}
public void display() {
}
}
MallardDuck 클래스
public class MallardDuck extends Duck {
public MallardDuck() {
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
@Override
public void display() {
System.out.println("I'm MallardDuck");
}
}
청둥오리(mallard duck)는 quack 행동은 Quack 객체를 통해 수행하고, fly 행동은 FlyWithWings 객체를 통해 수행한다.
동적으로 행동 전략 설정
public void setFlyBehavior(FlyBehavior fb) {
flyBehavior = fb;
}
public void setQuackBehavior(QuackBehavior qb) {
quackBehavior = qb;
}
Duck 클래스에 setter를 추가해 줌으로써 런타임 동안에 행동을 설정해 줄 수 있다.
리팩토링 이후
Duck을 확장하는 오리 클래스들, FlyBehavior을 구현하는 fly 행위들, QuackBehavior을 구현하는 quack 행위 등 캡슐화가 이루어 졌다.
소프트웨어 개발에서 초기 모델을 만들 때보다 프로그램을 유지보수하는데 훨씬 많은 시간을 사용하게 된다.
따라서 유지보수성과 확장성을 갖추기 위해서 많은 고민과 설계가 필요하다.
전략 패턴은 확장성에 있어서 큰 도움이 될 것이고 OCP 원칙과 깊은 관계를 가지고 있다.
추가 자료
https://enos.itcollege.ee/~jpoial/java/naited/Java-Design-Patterns.pdf
'Lang > Java' 카테고리의 다른 글
[Java] 가비지 컬렉션(GC, Garbage Collection) 기초 (4) | 2022.11.12 |
---|---|
옵저버 패턴 (Observer Pattern) (3) | 2022.10.21 |
[Java] Lambda 특징과 활용 (1) | 2022.10.03 |
함수형 프로그래밍과 Java #1 (6) | 2022.09.08 |
[디자인 패턴] 싱글톤 패턴 (Creational) (0) | 2022.01.10 |