[디자인 패턴] 전략 패턴

이현경·2025년 10월 13일

전략 패턴이란?

전략 패턴은 디자인 패턴 중 하나로 "알고리즘(전략)을 통째로 갈아 끼울 수 있게 만드는 패턴"이다. 전략 패턴은 if-else나 switch문으로 수많은 로직을 분기 처리하고 있을 때, OCP 원칙을 적용하여 코드를 개선하고 싶을 때 사용한다.

전략 패턴은 다음 과정을 따라 수행된다.

  1. 전략 인터페이스 정의
  2. 각 전략을 클래스로 구현
  3. 전략을 사용하는 Context(상황) 클래스 구현

아래의 예제를 보며 더 정확히 알아보자.




시나리오: 게임 케릭터 이동 방식 구현하기

배경: 플레이어의 아바타가 세계를 탐험하는 간단한 게임을 개발하고자 한다. 플레이어의 아바타를 나타내는 Character 클래스가 존재한다.

문제 상황: 캐릭터의 이동 방식은 현재 상태 및 지형에 따라 달라진다.

  • 평지에서 걷는다.
  • 물 속에서는 수영한다.
  • 비행 물약을 마시면 일시적으로 날아다닐 수 있다.

먼저 이 로직을 단순하게 Character 클래스 안의 move() 메소드 하나에 if-else문으로 구현해보자.

public class Character {
	public void move(String movingMethod, double distance) {
		if("walking".equals(movingMethod)) {
			// 걷기 로직
		} else if("swimming".equals(movingMethod)) {
			// 수영하기 로직
		} else if("flying".equals(movingMethod)) {
			// 날기 로직
		} 
		// 새로운 이동 방식이 추가될 때마다 이 클래스를 수정해야 한다 (OCP 위반)
	}
}

만약 위와 같이 구현한다면 추후 '기어가기'나 '순간이동'과 같은 새로운 이동 방식을 추가하기 위해선 Character 클래스 코드를 직접 수정해야 한다. 이는 개방-폐쇄 원칙(OCP)를 위반하게 된다.


이제 전략 패턴을 적용해보자.

  1. 전략 인터페이스 정의
public interface MoveStrategy {
	void move(double distance);
}
  1. 각 전략을 클래스로 구현
public class WalkingStrategy implements MoveStrategy {
	public void move(double distance) {
		System.out.println(distance+"km만큼 걸어가는 중...🚶‍♀️‍➡️");
	}
}
public class SwimmingStrategy implements MoveStrategy {
	public void move(double distance) {
		System.out.println(distance+"m만큼 수영하는 중...🏊‍♀️");
	}
}
public class FlyingStrategy implements MoveStrategy {
	public void move(double distance) {
		System.out.println(distance+"km만큼 잠깐 날아가는 중...🧚‍♀️");
	}
}
  1. 전략을 사용하는 Context 클래스 구현
public class Character {
	public void moveCharacter(MoveStrategy moveStrategy, double distance) {
		moveStrategy.move(distance);
	}
}

전략패턴을 적용하여 '이동한다'는 전략을 Character 클래스로부터 완전히 분리하였다. 추후 다른 이동 방식이 추가된다고 하여도 Character 클래스의 코드는 전혀 수정할 필요 없이 새로운 이동방식 클래스만 만들면 된다.

// main 메소드에서 사용
public class Main {

	public static void main(String[] args) {
		// Character 생성
		Character character = new Character();
		
		// 각각의 움직임 strategy 객체를 생성
		MoveStrategy walking = new WalkingStrategy();
		MoveStrategy swimming = new SwimmingStrategy();
		MoveStrategy flying = new FlyingStrategy();
		
		System.out.println("모험가가 광활한 초원을 탐험합니다.");
		character.moveCharacter(walking, 10); 
		
		System.out.println("모험가가 강을 넘으려 합니다.");
		character.moveCharacter(swimming, 500);
		
		System.out.println("강 속 보물상자에서 비행 물약을 발견했습니다!");
		character.moveCharacter(flying, 1);

	}

}

실행결과:

모험가가 광활한 초원을 탐험합니다.
10.0km만큼 걸어가는 중...🚶‍♀️‍➡️
모험가가 강을 넘으려 합니다.
500.0m만큼 수영하는 중...🏊‍♀️
강 속 보물상자에서 비행 물약을 발견했습니다!
1.0km만큼 잠깐 날아가는 중...🧚‍♀️




조금만 더: 약속은 명확히

사실 위에서 전략 패턴을 적용해 구현한 코드는 완벽하지 않다. 자세히 보면 다른 이동방식은 이동 거리의 단위가 km인 것에 반해 SwimmingStrategy만 단위가 m인 것을 확인할 수 있다. 이렇게 전략마다 미묘한 차이가 나는 것은 좋지 않은 설계이다.

전략 패턴의 핵심은 MoveStrategy라는 추상화된 약속을 통해 세부적인 이동 방식을 숨기는 것이다. 하지만 SwimmingStrategy만 다른 단위를 사용한다면, 숨겨져야 할 내부 구현 방식이 밖으로 새어 나온 것이다. 이는 사용하는 입장에서 전략마다 어떤 단위를 쓰는지 미리 알고 있어야 하며, 만약 distance를 활용하여 계산을 하게 된다면 아래와 같은 지저분한 분기문을 사용해야 할 수도 있다.

// 클라이언트가 전략의 내부 사정을 알아야만 함
if (currentStrategy instanceof SwimmingStrategy) {
    // 미터(m) 단위로 계산하는 로직
} else {
    // 킬로미터(km) 단위로 계산하는 로직
}

이런 코드가 생기는 순간, 전략 패턴을 사용한 의미가 사라진다. 따라서 가장 좋은 해결책은 전략 인터페이스에서부터 미리 약속을 통일하는 것이다.

// 수정된 예
public class WalkingStrategy implements MoveStrategy {
    public void move(double distanceKM) {
        System.out.println(distanceKM + "km만큼 걸어가는 중...🚶‍♀️‍➡️");
    }
}
public class SwimmingStrategy implements MoveStrategy {
    public void move(double distanceKM) {
        System.out.println(distanceKM + "km만큼 수영하는 중...🏊‍♀️");
    }
}
public class FlyingStrategy implements MoveStrategy {
	public void move(double distanceKM) {
		System.out.println(distanceKM+"km만큼 잠깐 날아가는 중...🧚‍♀️");
	}
}




추가: 상태를 가지는 컨텍스트

현재 Character 클래스는 이동할 때마다 외부에서 전략을 주입받는 '상태가 없는' 방식이다. 전략 패턴의 또 다른 구현 방식으로 Character가 현재의 이동 전략을 '상태'로써 기억할 수 있다.

// 상태를 가지는 Character 클래스 예시
class Character {
    private MoveStrategy currentMoveStrategy; // 현재 전략을 상태로 저장

    // 현재 전략을 변경하는 메소드
    public void setMoveStrategy(MoveStrategy moveStrategy) {
        this.currentMoveStrategy = moveStrategy;
    }

    // 저장된 현재 전략을 사용하여 이동
    public void move(double distance) {
        if (currentMoveStrategy == null) {
            System.out.println("이동 방법을 먼저 정해주세요!");
            return;
        }
        currentMoveStrategy.move(distance);
    }
}

// main 메소드
Character character = new Character();

character.setMoveStrategy(new WalkingStrategy()); // 현재 상태를 '걷기'로 설정
character.move(10); // "걷는 중..."

character.setMoveStrategy(new FlyingStrategy()); // 상태를 '비행'으로 변경
character.move(1); // "날아가는 중..."




소감

전략 패턴을 적용하는 것은 OCP 원칙을 적용하는 것과 유사하여 실습에 큰 어려움은 없었다. 그리고 공부를 하면서 알게된 상태를 기억하는 Character 클래스는 좀 더 복잡한 코드에서 유용하게 사용할 수 있을 것 같았다. 다음에는 이를 활용한 좀 더 복잡한 실습을 해봐야겠다.

추가로 처음 전략 패턴을 연습할 때 무의식중에 전략끼리의 단위를 다르게 설정하였는데 나중에 다시 생각해보니 이것이 추상화를 해치는 행위였음을 깨달았다. 전략이 몇 개 없는 현재 코드에서는 이 차이가 사소해 보일 수 있지만, 이러한 비일관성을 미리 바로잡는 습관이 결국 클린 코드로 나아가는 중요한 밑거름이 될 것이라고 생각한다.

profile
커피 한 잔의 여유를 아는 품격있는 여자

0개의 댓글