디자인 패턴이란?
디자인 패턴은 기존 환경 내에서 반복적으로 일어나는 문제들을 어떻게 풀어나갈 것인가에 대한 일종의 해결책이다. 디자인 패턴하면 떠오르는 'Gof의 디자인 패턴'에서는 객체지향적 디자인 패턴의 카테고리를 "생성패턴(Creational Pattern)", "구조 패턴(Structural Pattern)", "행동 패턴(Behavioral Patter)" 이렇게 3가지로 구분하고 있다.
그중 행동 패턴에 속하는 전략 패턴에 대해 알아보자.
전략 패턴(Strategy)
전략 패턴 은 알고리즘들의 패밀리를 정의하고, 각 패밀리를 별도의 클래스에 넣은 후 그들의 객체들을 상호교환 할 수 있도록 하는 행동 디자인 패턴이다.
예시
문제점
당신은 RPG 게임을 만들기로 했다. RPG 게임의 특성상 캐릭터의 직업이 있고 캐릭터마다의 특징이 있다.
이 게임에서 가장 많이 요청되는 기능 중 하나가 전직과 공격이다.
게임의 첫 번째 버전에서는 직업이 전사만 있었다. 하지만 유저들은 다른 직업도 나오길 원했다. 그래서 다음 업데이트 때 마법사라는 직업을 추가했다.
사람들은 만족했지만 유저들은 의견이 적극적으로 반영된 게임을 극찬하며 다른 직업들도 나오길 바랬다.
여기서 이제 문제가 시작된다. 새 직업을 추가할 때마다 메인 클래스의 크기가 두 배로 늘어났고, 어느 시점부터는 관리하기가 힘들거라 예상되었다.
또한 새로운 기능을 구현하려면 거대한 동일 클래스를 변경해야 하는데, 이렇게 바꾼 내용들이 다른 팀원들이 생성한 코드와 충돌할 거라 생각되었다.
해결책
이런 문제를 해결하기 위해 전략 패턴을 도입했다. 각 직업을 별도의 전략 클래스로 분리하여, 메인 클래스는 각 직업에 해당하는 전략을 참조하도록 설계했다.
이렇게 하면 새로운 직업이 추가될 때마다 메인 클래스의 코드 수정을 최소화할 수 있고, 각 직업의 특징이나 기능을 변경할 때에도 해당 전략 클래스만 수정하면 되게 되었다.
이로써 유저들의 요구에 계속 빠르게 대응할 수 있게 되었고, 게임 시스템의 확장성과 유지보수성이 크게 향상되었다.
또한, 다른 팀원들과의 협업이 원활해지면서 코드 충돌 문제도 줄일 수 있었다. 새로운 직업이 추가될 때마다 전략을 생성하고 설정하는 작업만 진행하면 되므로 게임의 기능이 빠르게 확장할 수 있게 되었다.
코드로 보기
// 전략 인터페이스
interface AttackStrategy {
void attack();
}
// 검으로 공격하는 전략
class SwordAttack implements AttackStrategy {
public void attack() {
System.out.println("검으로 공격!");
}
}
// 마법으로 공격하는 전략
class MagicAttack implements AttackStrategy {
public void attack() {
System.out.println("마법으로 공격!");
}
}
// 직업 인터페이스
interface Job {
void performJob();
}
// 기사 클래스
class Warrior implements Job {
private AttackStrategy attackStrategy;
public Warrior(AttackStrategy attackStrategy) {
this.attackStrategy = attackStrategy;
}
public void setAttackStrategy(AttackStrategy attackStrategy) {
this.attackStrategy = attackStrategy;
}
public void performJob() {
// 기사의 특별한 능력 실행
if (attackStrategy != null) {
attackStrategy.attack();
}
}
}
// 마법사 클래스
class Wizard implements Job {
private AttackStrategy attackStrategy;
public Wizard(AttackStrategy attackStrategy) {
this.attackStrategy = attackStrategy;
}
public void setAttackStrategy(AttackStrategy attackStrategy) {
this.attackStrategy = attackStrategy;
}
public void performJob() {
// 마법사의 특별한 능력 실행
if (attackStrategy != null) {
attackStrategy.attack();
}
}
}
// 게임 실행
public class Game {
public static void main(String[] args) {
// 기사 생성 및 공격 방법 설정
Knight knight = new Knight(new SwordAttack());
// 기사가 검으로 공격
knight.performJob();
// 마법사 생성 및 공격 방법 설정
Wizard wizard = new Wizard(new MagicAttack());
// 마법사가 마법으로 공격
wizard.performJob();
// 기사의 전략 변경 (새로운 전략으로 전환)
knight.setAttackStrategy(new MagicAttack());
// 기사가 이제는 마법으로 공격
knight.performJob();
}
}
장단점
장점
- 유연성: 전략 패턴은 알고리즘을 독립적으로 정의하고 교환할 수 있도록 해준다. 이로써 새로운 전략을 간편하게 추가하거나 기존 전략을 변경할 수 있다.
- 유지보수성: 각 전략이 별도의 클래스로 캡슐화되어 있기 때문에 코드 변경이 한 전략에만 영향을 미치게 되며, 다른 전략들에는 영향을 주지 않는다.
- 확장성: 새로운 전략 클래스를 추가하거나 기존 전략을 수정함으로써 시스템에 쉽게 새로운 기능을 도입할 수 있다.
단점
- 복잡성 증가: 전략 패턴을 도입하면 클래스의 수가 늘어나고 코드 구조가 복잡해질 수 있다.
- 클라이언트가 알아야 할 내용 증가: 클라이언트는 사용 가능한 전략에 대한 정보를 알아야 하므로, 일부 클라이언트 코드가 전략을 선택하는 논리를 가지게 된다.
- 전략 변경 오버헤드: 동적으로 전략을 변경할 필요가 없는 경우에는 전략 패턴의 오버헤드가 발생할 수 있다.
템플릿 메서드와의 차이점
- 템플릿 메서드 패턴:
- 상속을 기반으로 하며, 부모 클래스에서 알고리즘의 구조를 정의하고 일부 단계를 서브클래스로 미룬다.
- 클래스 수준에서 작동하므로 정적이다.
- 전략 패턴:
- 합성(컴포지션)을 기반으로 하며, 객체의 행동의 일부를 변경 가능한 전략 객체로 분리하여 동적으로 교환할 수 있도록 한다.
- 객체 수준에서 작동하므로 런타임에 행동을 전환할 수 있다.
'CS' 카테고리의 다른 글
디자인 패턴 - 빌더패턴(Builder) (2) | 2023.11.26 |
---|---|
디자인패턴 - 프로토타입 패턴(Prototype) (0) | 2023.11.19 |
GoF 디자인 패턴: 소프트웨어 디자인의 핵심 (0) | 2023.11.05 |
CSR 과 SSR 그리고 SPA (1) | 2023.10.28 |
jOOQ 란 (0) | 2023.04.28 |