오늘은 Head First Design Pattern 책의 40 ~ 62p를 요약할 것이며, Strategy Pattern 에 대해 알아볼 예정이다.
Strategy Pattern은 다음과 같은 경우에 주로 이용되고 있다.
Head First Design Pattern에서는 오리 연못 시뮬레이션 게임
으로 Strategy Pattern을 설명했다. 하지만 나는 내 것으로 만들기 위해 예시를 조금 변경해 진행해 보겠다.
나는 리그오브레전드(LOL)을 좋아하기 때문에 리그오브레전드 게임을 예시로 Strategy Pattern에 대해 설명해보겠다. 리그오브레전드는 5대5 팀 게임으로 150개 이상의 챔피언
중 하나를 선택해 플레이하는 인기있는 게임이다.
우리가 만약 이 챔피언을 객체로 정의하면 어떤 구조가 될까? 좀 더 살펴보자.
150가지 이상의 챔피언
들은 모두 패시브
라는 고유 성질과 4가지의 스킬을 가지고 있는 것을 알 수 있다.
그냥 단순히 객체간의 관계를 고려해보면 다음과 같은 구조가 나올 것이다.
단순히 보자면 Champion
이라는 추상 클래스를 상속한 Poppy
는 각 메소드를 Override하여 구현해야만 한다.
이제 각 메소드만 구현하면 뽀삐
라는 챔피언은 각각의 스킬들을 사용할 수 있을 것이다.
리그오브레전드의 사일러스
라는 챔피언의 궁극기는 다음과 같은 정의를 가지고 있다.
만약 우리가 해당 챔피언에 대해 클래스로 정의한다면 어떻게 정의해야 다음과 같은 기능을 만들 수 있을까?
기존 UML을 볼 경우 다음과 같은 클래스 다이어 그램이 나올 것이다.
지금까지의 상황을 읽고 다음 3가지의 질문에 생각하는 시간을 가져보자.
- 만약 사일러스가 뽀삐의 궁을 뺏으려면 다음과 같은 구조로 구현이 가능할까?
- 150개의 챔피언 스킬들 중 사일러스와 같이 궁을 훔치거나 복사, 제거가 가능한 챔피언이 있다면 해당 구조로 개발이 가능할까?
- 내 궁이랑 상대 궁극기를 바꾸는 챔피언이 등장하면 해당 구조로 구현이 가능할까?
음 1번은 어떻게라도 만들 수는 있을 것 같다. 2번과 3번은 기존에 구현한 스킬 메소드에 대해 변경 또는 제거가 필요한 부분이어서 구현이 가능할지는 잘 모르겠다는 생각이다.
다음과 같이 결론이 도출되었을 때 우리는 어떤 구조로 바꾸어야 다음 문제들을 모두 해결할 수 있을까?
=> 이 때 사용할 수 있는 패턴이 Strategy 패턴이다. 앞서 Strategy 패턴이 정의하는 내용을 상기시켜보자.
행위를 클래스로 캡슐화해 동적으로 행위를 자유롭게 바꿀 수 있게 해주는 패턴
정의에 나온 말처럼 행위
를 캡슐화하면 가능할지 알아보자.
먼저 모든 행위는 skill이라는 인터페이스로 통합시키겠다. 이후 각각의 스킬들(qwer)을 행위
로 인식하고 진행해보겠다.
다음과 같이 진행할 경우, 각각의 챔피언은 Skill에 대해 초기화하고 실행간 바꿔야 되는 부분이 있을 경우, setRSkill()을 통해 챔피언의 궁극기 스킬을 변경할 수가 있게 되었다.
물론, QSkill 과 같은 인터페이스를 추가로 만들어 Skill을 상속하지 않아도 된다. 변수명으로 q,w,e,r로 초기화해 사용해도 괜찮다. 하지만 각 스킬의 종류
는 구분되어져야 된다고 생각해 다음과 같이 구분하였다.
이제 코드로 구현을 진행해보자.
public interface Skill {
public void perform();
}
public interface WSkill extends Skill {
}
// 각 스킬별로 만들어 줬음 Q,W,E,R
public class SylasE implements ESkill {
@Override
public void perform() {
System.out.println("sylas e!");
}
}
public class Champion {
private QSkill qSkill;
private WSkill wSkill;
private ESkill eSkill;
private RSkill rSkill;
public Champion(QSkill qSkill, WSkill wSkill, ESkill eSkill, RSkill rSkill) {
this.qSkill = qSkill;
this.wSkill = wSkill;
this.eSkill = eSkill;
this.rSkill = rSkill;
}
public void performQSkill(){
qSkill.perform();
};
public void performWSkill(){
wSkill.perform();
};
public void performESkill(){
eSkill.perform();
};
public void performRSkill(){
rSkill.perform();
};
public void setqSkill(QSkill qSkill) {
this.qSkill = qSkill;
}
public void setwSkill(WSkill wSkill) {
this.wSkill = wSkill;
}
public void seteSkill(ESkill eSkill) {
this.eSkill = eSkill;
}
public void setRSkill(RSkill rSkill) {
this.rSkill = rSkill;
}
public QSkill getqSkill() {
return qSkill;
}
public WSkill getwSkill() {
return wSkill;
}
public ESkill geteSkill() {
return eSkill;
}
public RSkill getrSkill() {
return rSkill;
}
}
public class Sylas extends Champion {
public Sylas() {
super(new SylasQ(), new SylasW(), new SylasE(), new SylasR());
}
}
public class main {
public static void main(String[] args) throws IOException {
Poppy poppy = new Poppy();
Sylas sylas = new Sylas();
poppy.performRSkill();;
sylas.performRSkill();
sylas.setRSkill(poppy.getrSkill());
sylas.performRSkill();
}
}
물론 이게 LOL을 구현한 로직은 아닐거고 정답 또한 아닐 것이다. 다만, 특정 상황에서 동적으로 행동을 지정하는 방법을 사일러스라는 특정 캐릭터의 행위로 변경해서 예시를 만들어 봤다.
스트레티지 패턴은 이와 같이 행위를 캡슐화하여 이를 동적으로 교환하면서 사용할 수 있도록 만드는 패턴이다. 스트레티지 패턴을 사용한다면 각 기능을 상황에 따라 동적으로 변경해가며 사용할 수 있는 이점이 있으니 참고하면 좋을 것 같다.
Head First Design Pattern에서 나온 오리 연못 시뮬레이션 게임
도 예시로 확인하고 싶은 사람들은 [디자인패턴] 1. 스트래티지 패턴 개념과 예제 (strategy pattern)를 참고해보자.
책에 나온 내용을 정리한 블로그인데 잘 설명한 것 같아 링크를 남겨놓겠다.