전략 패턴을 활용한 스킬 구현
오늘은 콘솔 게임의 스킬 시스템을 자바스크립트로 구현하는 과정에서 겪었던 고민들과 그 해결책을 정리해 보았습니다. 특히 전략 패턴(Strategy Pattern)을 활용하여 스킬 시스템을 유연하고 확장 가능하게 만드는 방법에 집중했습니다.
스킬 시스템의 핵심 고민: 유연성과 확장성
처음 스킬 시스템을 구현 할 때는 간단히 switch문을 이용해 구현할려고 했습니다. 그런데 이렇게 하면 새로운 스킬을 추가하는게 어렵고 버프나 디버프 같은 스킬을 구현하는데 한계가 있었습니다.
그래서 Skill 클래스를 상속 받는 하위 클래스로 만들 생각도 했지만, 이러면 스킬 종류가 늘어날수록 클레스 계층이 복잡해지고, 런타임에 스킬의 동작을 변경하기 어렵다는 단점이 있다는 것을 알게 되었습니다.
전략 패턴의 도입: "상속보다는 구성(Composition over Inheritance)"
이러한 문제를 해결하기 위해 전략 패턴을 적용하기로 결정했습니다. 전략 패턴은 객체의 행동을 별동의 클래스로 캡슐화하고, 런타임에 클래스를 교체할 수 있도록 하는 디자인 패턴입니다.
- Skill 클래스: 스킬의 이름, 설명, 값 등 정적인 정보와 어떤 스킬을 사용할지에 대한 정보만 가집니다. 스킬이 발동될 때, 실제 동작은 자신이 가지고 있는 전략 객체에 따라 동작합니다.
- 동작 스킬 클래스들(PowerSlashSkill, DefenceBuffSkill): 각 전략의 구체적인 동작 방식을 정의합니다. 예를 들어 PowerSlashSkill은 데미지 계산 및 적용 로직을, DefenceBuffSKill은 방어력 증가 로직만을 가집니다. 모든 스킬은 execute라는 공통 인터페이스 메서드를 구현하도록 설계했습니다.
장점
- 높은 유연성: 새로운 스킬 동작을 추가하거나 기존 스킬 동작을 변경할 때, SKill 클래스 자체를 수정 할 필요 없이 새로운 전략 클래스만 만들거나 수정하면 됩니다.
- 런타임 동작 변경: 스킬 레벨업이나 아이템 효과 등으로 Skill 객체가 사용하는 스킬을 런타임에 동적으로 변경할 수 있습니다.
- 단일 책임 원칙: Skill 클래스는 스킬 정보 관리와 전략 위임이라는 책임만, 전략 클래스는 특정 해동 구현이라는 책임만 가집니다.
구현 내용
다음과 같이 Skill에 정적인 정보만 받습니다.
export class Skill {
constructor(data) {
this._id = data.id;
this._name = data.name;
this._type = data.type;
this._classType = data.classType;
this._baseValue = data.baseValue;
this._duration = data.duration;
this._maxUses = data.maxUses;
this._description = data.description;
this.InitDuration = data.duration;
...
}
그리고 Skill 클래스에 전략 객체를 담을 변수를 생성자 메서드에 다음과 같이 선언해주었습니다.
export class Skill {
constructor(data) {
...
const SkillClass = skillStrategies[this._classType];
if (!SkillClass) {
console.error(
`Error: Unknown strategy type for skill ${this._name}: ${this._classType}`
);
} else {
this.usingSkill = new SkillClass();
}
}
전략 클래스들을 객체로 묶어 관리하는 skillStrategies 클래스를 만들었는데, C#의 Dictionary처럼 유용하게 사용할 수 있어서 편리했습니다. 자바스크립트에서는 이렇게 객체 리터럴을 활용해 전략들을 손쉽게 관리할 수 있다는 점이 정말 좋았습니다
import { AgilityBuffSkill } from "./AgilityBuffSkill.js";
import { DefenceBuffSkill } from "./DefenceBuffSkill.js";
import { PerfectHealSkill } from "./PerfectHealSkill.js";
import { PoisonDeBuffSkill } from "./PoisonDeBuffSkill.js";
import { PowerSlashSkill } from "./PowerSlashSkill.js";
import { SmallHealSkill } from "./SmallHealSkill.js";
const skillStrategies = {
AgilityBuffSkill,
PerfectHealSkill,
PoisonDeBuffSkill,
PowerSlashSkill,
DefenceBuffSkill,
SmallHealSkill
};
export {skillStrategies};
전략 클래스들이 반드시 구현해야 할 execute 메서드를 위해 SkillStrategy라는 인터페이스 역할을 하는 클래스를 만들고 각 전략 클래스에 상속시켰습니다. C#처럼 명확한 interface 키워드가 없어 이런 식으로 클래스를 직접 활용해야 한다는 점은 자바스크립트에서 다소 불편하게 느껴졌습니다.
export class SkillStrategy{
execute(caster, target, skillData){
throw new Error("SkillStrategy를 상속받은 클래스는 'execute' 메서드를 반드시 구현해야 합니다.")
}
}
마지막으로 스킬 클래스를 각각의 파일로 만들어 구현하였습니다.
정리
오늘 배운 전략 패턴을 활용한 스킬 시스템 구현은 코드의 유연성, 확장성, 그리고 유지보수성을 크게 향상시키는 중요한 방법임을 다시 한번 깨달았습니다. 초기에는 다소 복잡하게 느껴질 수 있지만, 게임의 규모가 커지고 스킬 종류가 다양해질수록 그 진가를 발휘할 것입니다. 또한, 자바스크립트의 기본적인 배열 접근 방식과 반복문의 정확한 사용법을 다시 한번 상기하는 계기가 되었습니다.
'이노베이션캠프 > TIL' 카테고리의 다른 글
Prisma Client 생성 안되는 오류 (0) | 2025.07.21 |
---|---|
이노베이션 캠프 10일차_AWS에 Node.js 서버 배포 (0) | 2025.07.17 |
이노베이션 캠프 8일차_과제 전투시스템 개선 (0) | 2025.07.15 |
이노베이션 캠프 7일차_서버 문이 안 열려요?! (0) | 2025.07.14 |
이노베이션 캠프 6일차_JSON 데이터가 정상 로드 안 되는 현상 (0) | 2025.07.12 |