행위를 클래스로 캡슐화해 동적으로 행위를 자유롭게 바꿀 수 있게 해주는 패턴
➡️ 같은 문제를 해결하는 여러 알고리즘이 클래스별로 캡슐화되어 있고 이들이 필요할 때 교체할 수 있도록 함으로써 동일한 문제를 다른 알고리즘으로 해결할 수 있게 하는 디자인 패턴
Strategy
인터페이스나 추상 클래스로 외부에서 동일한 방식으로 알고리즘을 호출하는 방법을 명시
ConcreteStrategy
스트래티지 패턴에서 명시한 알고리즘을 실제로 구현한 클래스
Context
스트래티지 패턴을 이용하는 역할 수행
필요에 따라 동적으로 구체적인 전략을 바꿀 수 있도록 setter 메소드(집약 관계)를 제공
✔️ 집약 관계
public abstract class Robot {
private String name;
public Robot(String name) { this.name = name; }
public String getName() { return name; }
// 추상 메서드
public abstract void attack();
public abstract void move();
}
public class TaekwonV extends Robot {
public TaekwonV(String name) { super(name); }
public void attack() { System.out.println("I have Missile."); }
public void move() { System.out.println("I can only walk."); }
}
public class Atom extends Robot {
public Atom(String name) { super(name); }
public void attack() { System.out.println("I have strong punch."); }
public void move() { System.out.println("I can fly."); }
}
public class Client {
public static void main(String[] args) {
Robot taekwonV = new TaekwonV("TaekwonV");
Robot atom = new Atom("Atom");
System.out.println("My name is " + taekwonV.getName());
taekwonV.move();
taekwonV.attack();
System.out.println()
System.out.println("My name is " + atom.getName());
atom.move();
atom.attack();
}
}
❗️ 문제점
1. 기존 로봇의 공격과 이동 방법을 수정하는 경우
public class Atom extends Robot {
public Atom(String name) { super(name); }
public void attack() { System.out.println("I have strong punch."); }
public void move() { System.out.println("I can only walk."); } // 수정
}
➡️ 새로운 기능으로 변경하려고 기존 코드의 내용을 수정해야 하므로 OCP에 위배
TaekwonV와 Atom의 move() 메소드의 내용이 중복(중복 상황은 많은 문제를 야기하는 원인)
걷는 방식에 문제가 있거나 새로운 방식으로 수정하려면 모든 중복 코드를 일관성 있게 변경해야함
2. 새로운 로봇을 만들어 기존의 공격(attack) 또는 이동 방법(move)을 추가하는 경우
public class Sungard extends Robot {
public Sungard(String name) { super(name); }
public void attack() { System.out.println("I have Missile."); } // 중복
public void move() { System.out.println("I can only walk."); }
}
➡️ TaekwonV와 Sungard 클래스의 attack() 메소드의 내용 중복
❓ 해결책
➡️ 무엇이 변화되었는지 찾은 후에 이를 클래스로 캡슐화
Robot 클래스가 이동/공격 기능을 이용하는 클라이언트 역할 수행
스트레티지 패턴을 이용하면 새로운 기능의 추가(이동, 공격)가 기존 코드에 영향을 미치지 못하게 하므로 OCP를 만족하는 설계
Robot 클래스
public abstract class Robot {
private String name;
private AttackStrategy attackStrategy;
private MovingStrategy movingStrategy;
public Robot(String name) { this.name = name; }
public String getName() { return name; }
public void attack() { attackStrategy.attack(); }
public void move() { movingStrategy.move(); }
// 집약 관계, 전체 객체가 메모리에서 사라진다 해도 부분 객체는 사라지지 않는다.
// setter 메서드
public void setAttackStrategy(AttackStrategy attackStrategy) {
this.attackStrategy = attackStrategy; }
public void setMovingStrategy(MovingStrategy movingStrategy) {
this.movingStrategy = movingStrategy; }
}
구체적인 Robot 클래스
public class TaekwonV extends Robot {
public TaekwonV(String name) { super(name); }
}
public class Atom extends Robot {
public Atom(String name) { super(name); }
}
공격, 이동 기능에 대한 인터페이스와 구체적인 클래스
// 인터페이스
interface AttackStrategy { public void attack(); }
// 구체적인 클래스
public class MissileStrategy implements AttackStrategy {
public void attack() { System.out.println("I have Missile."); }
}
public class PunchStrategy implements AttackStrategy {
public void attack() { System.out.println("I have strong punch."); }
}
// 인터페이스
interface MovingStrategy { public void move(); }
// 구체적인 클래스
public class FlyingStrategy implements MovingStrategy {
public void move() { System.out.println("I can fly."); }
}
public class WalkingStrategy implements MovingStrategy {
public void move() { System.out.println("I can only walk."); }
}
Client에서의 사용
public class Client {
public static void main(String[] args) {
Robot taekwonV = new TaekwonV("TaekwonV");
Robot atom = new Atom("Atom");
/* 수정된 부분: 전략 변경 방법 */
taekwonV.setMovingStrategy(new WalkingStrategy());
taekwonV.setAttackStrategy(new MissileStrategy());
atom.setMovingStrategy(new FlyingStrategy());
atom.setAttackStrategy(new PunchStrategy());
/* 아래부터는 동일 */
System.out.println("My name is " + taekwonV.getName());
taekwonV.move();
taekwonV.attack();
System.out.println()
System.out.println("My name is " + atom.getName());
atom.move();
atom.attack();
}
}
Strategy Pattern을 활용하면 로직을 독립적으로 관리하는 것이 편해진다.
로직에 들어가는 '행동'을 클래스로 선언하고 인터페이스와 연결하는 방식으로 구성
[참고 자료]