Strategy 패턴
전략 패턴 (Strategy Pattern) 이란?
- 전략 패턴은 전략을 쉽게 바꿀 수 있도록 해주는 디자인 패턴이다.
- 전략이란, 어떤 목적을 달성하기 위해 일을 수행하는 방식, 비즈니스 규칙, 문제를 해결하는 알고리즘
- 같은 문제를 해결하는 여러 알고리즘이 클래스별로 캡슐화되어 있다.
- 필요할 때 교체할 수 있도록 함으로써 동일한 문제를 다른 알고리즘으로 해결할 수 있게 하는 디자인 패턴
- 특히 게임 프로그래밍에서 게임 캐릭터가 자신이 처한 상황에 따라 공격이나 행동하는 방식을 바꾸고 싶을 때 전략 패턴은 매우 유용하다.
전략 패턴 예시
[로봇 만들기]
아톰과 태권V를 만들어보자.
두 로봇은 공격 기능과 이동 기능이 있다.
아톰 | 태권V | |
---|---|---|
공격 기능 | 펀치 공격 | 미사일 공격 |
이동 기능 | 비행 가능 | 걷기 |
위와 같은 기능을 탑재할 수 있게 클래스 다이어그램을 설계했다.
- 추상 클래스인 로봇에 공격 기능 attack과 이동 기능 move를 정의했다.
- 구체 클래스인 아톰과 태권V가 구현하게 했다.
Robot.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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();
}
- 추상 클래스
Robot
에 추상메서드attack
,move
를 정의했다.
Atom.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Atom extends Robot{
public Atom(String name) {
super(name);
}
@Override
public void attack() {
System.out.println("펀치 공격 가능하다.");
}
@Override
public void move() {
System.out.println("날 수 있다.");
}
}
TaekwonV.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TaekwonV extends Robot{
public TaekwonV(String name) {
super(name);
}
@Override
public void attack() {
System.out.println("미사일 공격 가능하다.");
}
@Override
public void move() {
System.out.println("걸을 수만 있다.");
}
}
- 아톰과 태권V는 로봇의 하위 클래스이다.
- 아톰의 경우,
attack
메서드에 펀치 공격 가능,move
메서드에 날 수 있다고 나타냈다. - 태권V의 경우,
attack
메서드에 미사일 공격 가능,move
메서드에 걸을 수만 있음을 나타냈다.
Solution.java [클라이언트 코드]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Solution {
public static void main(String[] args) {
// 아톰, 태권 V를 생성
Robot atom = new Atom("아톰");
Robot taekwonV = new TaekwonV("태권V");
System.out.println(atom.getName());
// 각 메서드 호출
atom.move();
atom.attack();
System.out.println(taekwonV.getName());
taekwonV.move();
taekwonV.attack();
}
}
output
1
2
3
4
5
6
7
아톰
펀치 공격 가능하다.
날 수 있다.
태권V
미사일 공격 가능하다.
걸을 수만 있다.
딱히 문제 될 건 없는 것 같은데?
지금까지 설계한 클래스의 문제점을 살펴보자.
[문제점 1]
- 예를 들어, 아톰이 날 수는 없고 오직 걸을수만 있도록 만들고 싶다면?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Atom extends Robot{
public Atom(String name) {
super(name);
}
@Override
public void attack() {
System.out.println("펀치 공격 가능하다.");
}
@Override
public void move() {
// 날 수 없도록 수정 : 날 수 있다. -> 걸을 수만 있다.
System.out.println("걸을 수만 있다.");
}
}
- 새로운 기능으로 변경 시, 기존 코드의 내용을 수정해야 한다.
- OCP(Open-Closed Principle) 개방 폐쇄 원칙 위반
- 또한 Atom 클래스의 move 메서드와 TaekwonV 클래스의 move 메서드가 중복되는 상황 발생.
- 만약 걷는 방식에 문제가 있거나 새로운 방식으로 수정하려면?
- 모든 중복된 코드를 일관성 있게 변경해야 한다.
- 유지 보수 면에서 좋지 않다.
[문제점 2]
- 현재 설계는 로봇 자체가 캡슐화 단위이므로 새로운 로봇을 추가하기가 매우 쉽다.
- 그러나, 새로운 로봇에 기존의 공격 또는 이동 방법을 추가하거나 변경하려고 하면 문제가 발생한다.
- 가령, 마징가Z 로봇에 태권V의 미사일 공격 기능을 사용하려고 한다면?
TaekwonV
클래스의attack
메서드가 중복해서 사용된다.- 중복 코드는 나중에 심각한 문제를 일으킨다.
[해결책]
- 우선 로봇 설계에서 문제를 해결하려면 무엇이 변화되었는지 찾아야 한다.
- 변화된 것을 찾은 후에, 이를 클래스로 캡슐화해야 한다.
- 로봇이 변화되면서 문제를 일으키는 요인은 이동 방식과 공격 방식의 변화다.
- 새로운 방식의 이동 및 공격 기능이 계속해서 추가 될 수 있을텐데…
- (1) 기존의 로봇이나 새로운 로봇이 이러한 기능을 별다른 코드 변경 없이 제공받아야 한다.
- (2) 기존의 공격이나 이동 방식을 다른 공격이나 이동 방식으로 쉽게 변경할 수 있어야 한다.
- 캡슐화하려면, 이동 방식과 공격 방식을 담은 구체적인 클래스들을 은닉해야 한다.
- 공격과 이동을 위한 인터페이스를 각각 만들고, 이들을 실제 구현한 클래스를 만들어야 한다.
[개선된 설계]
- 새로운 공격 방식이 개발되어 현재 로봇에 제공할 경우…
AttackStrategy
인터페이스가 변화에 대한 일종의 방화벽 역할을 수행한다.Robot
클래스 변경을 차단한다.
- 새로운 기능의 추가가 기존의 코드에 영향을 미치지 못하므로 OCP를 만족하는 설계가 된다.
[개선 된 최종 소스 코드]
Robot.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public abstract class Robot {
private String name;
private MovingStrategy movingStrategy; // 이동 전략
private AttackStrategy attackStrategy; // 공격 전략
public Robot(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void move() {
movingStrategy.move();
}
public void attack() {
attackStrategy.attack();
}
public MovingStrategy getMovingStrategy() {
return movingStrategy;
}
// 각각 public한 set함수를 두었다.
public void setMovingStrategy(MovingStrategy movingStrategy) {
this.movingStrategy = movingStrategy;
}
public AttackStrategy getAttackStrategy() {
return attackStrategy;
}
public void setAttackStrategy(AttackStrategy attackStrategy) {
this.attackStrategy = attackStrategy;
}
}
MovingStrategy.java / AttackStrategy.java
1
2
3
4
5
6
7
public interface MovingStrategy {
public void move();
}
public interface AttackStrategy {
public void attack();
}
MovingStrategy
이동전략은 추상메서드move
를 포함한다.AttackStrategy
공격전략은 추상메서드attack
을 포함하는 인터페이스로 둔다.
WalkingStrategy.java / FlyingStrategy.java / MissileStrategy.java / PunchStrategy.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class WalkingStrategy implements MovingStrategy {
@Override
public void move() {
System.out.println("걸을 수만 있다.");
}
}
public class FlyingStrategy implements MovingStrategy {
@Override
public void move() {
System.out.println("날 수 있다.");
}
}
public class MissileStrategy implements AttackStrategy {
@Override
public void attack() {
System.out.println("미사일 공격 가능하다.");
}
}
public class PunchStrategy implements AttackStrategy {
@Override
public void attack() {
System.out.println("펀치 공격 가능하다.");
}
}
- 이동전략을 구현한 걷기전략, 비행전략을 클래스로 만들었다.
- 공격전략을 구현한 미사일전략, 펀치전략을 클래스로 만들었다.
Solution.java [클라이언트 코드]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Solution {
public static void main(String[] args) {
// 아톰과 태권V를 생성
Robot atom = new Atom("아톰");
Robot taekwonV = new TaekwonV("태권V");
// 그리고, 이동전략 공격전략을 셋팅한다.
atom.setMovingStrategy(new FlyingStrategy());
atom.setAttackStrategy(new PunchStrategy());
taekwonV.setMovingStrategy(new WalkingStrategy());
taekwonV.setAttackStrategy(new MissileStrategy());
System.out.println(atom.getName());
atom.move();
atom.attack();
System.out.println(taekwonV.getName());
taekwonV.move();
taekwonV.attack();
}
}
- 아톰의 이동전략을 비행가능에서 걷기만 가능으로 변경하려면 아톰 클래스의 수정 없이 클라이언트 코드에서 전략만 수정하면 된다.
💡 핵심 정리
- 전략패턴은 스위치를 전환하듯 전략을 바꿔서 같은 문제를 다른 방법으로 해결하기 쉽게 만들어주는 패턴
- 예시에서는
Robot
코드의 수정 없이 이동 전략, 공격 전략을 변경할 수 있었다.
This post is licensed under CC BY 4.0 by the author.