전략 패턴
전략 패턴
객체들이 할 수 있는 행위 각각에 대해 전략 클래스를 생성하고, 유사한 행위들을 캡슐화 하는 인터페이스를 정의하여,
객체의 행위를 동적으로 바꾸고 싶은 경우 직접 행위를 수정하지 않고 전략을 바꿔주기만 함으로써 행위를 유연하게 확장하는 방법을 말한다.
간단히 말해서 객체가 할 수 있는 행위들 각각을 전략으로 만들어 놓고, 동적으로 행위의 수정이 필요한 경우 전략을 바꾸는 것만으로 행위의 수정이 가능하도록 만든 패턴이다.
전략 패턴 예제 - 로봇 만들기
추상 클래스 Robot의 자식 클래스로 Atom 클래스, TaekwonV 클래스가 존재하고, Robot의 추상 메서드로 attack(), move()가 있는 상황을 가정해보자. 이 때 attack과 move 추상메소드는 자식클래스에서 재정의하도록 하였다. 왜냐하면 어떤 로봇인지에 따라 움직이는 방식, 공격하는 방식이 다르기 때문이다. Atom이 펀치를 날린다면, TaekwonV는 미사일을 발사한다.
이와같은 설계방식은 언뜻 보기에는 적절해보인다. 다른 로봇을 만들때, 해당 클래스를 상속받아서 재구현하면 되므로 OCP를 만족한다. 즉, 특정 기능(클래스)을 추가하는데 기존의 코드가 변경되지 않는다.
그러나 만약, Sungard 로봇을 만든다고 했을 때 Sungard의 이동기능을 수영하는 것으로 하고, 공격기능을 TaekwonV(펀치)와 같게 설정하는 상황이면, Sungard의 공격기능에 TaekwonV의 공격 코드를 그대로 복사 붙여넣기 하면 된다.
이것도 OCP를 만족하지만, 다른 클래스와의 특정 메서드 중복 사용하는 것이며 당연히 코드를 복사 붙여넣기 하는 것은 매우 좋지않다. 이는 유지보수를 더욱 어렵게 만든다.
그래서 등장한 것이 Strategy Pattern이다. 해당 패턴을 적용하여 개선된 상황을 살펴보면 다음과 같다.
(다시 상기하자면, 전략패턴은 객체들이 할 수 있는 행위 각각에 대해 전략 클래스를 생성하고, 유사한 행위들을 캡슐화 하는 인터페이스를 정의하는 것이다)
구체적인 이동 방식과 공격 방식이 MovingStrategy, AttackStrategy 인터페이스에 의해 캡슐화 되어 향후 이동 방식, 공격 방식의 변화를 수용할 수 있게 되었다. (클래스를 추가하고 추후에 implements 해주면 끝)
또한 새로운 기능 추가 시, 기존의 코드에 영향을 미치지 않아 OCP(Open-Closed Principle)를 만족한다.
새로운 공격 방식 개발 시 AttackStrategy 인터페이스가 변화에 대한 일종의 방화벽 역할을 수행해 Robot 클래스 자체의 변경 차단이 가능하다.
전략 패턴의 장단점
장점
- 런타임에 한 객체 내부에서 사용되는 알고리즘들을 교환할 수 있다.
- 알고리즘을 사용하는 코드에서 알고리즘의 구현 세부 정보들을 고립할 수 있다.
- 상속을 합성으로 대체할 수 있다.
- 개방/폐쇄 원칙. 콘텍스트를 변경하지 않고도 새로운 전략들을 도입할 수 있다.
단점
- 알고리즘이 몇 개밖에 되지 않고 거의 변하지 않는다면, 패턴과 함께 사용되는 새로운 클래스들과 인터페이스들로 프로그램을 지나치게 복잡하게 만들 이유가 없다.
- 클라이언트들은 적절한 전략을 선택할 수 있도록 전략 간의 차이점들을 알고 있어야 한다.
- 현대의 많은 프로그래밍 언어에는 익명 함수들의 집합 내에서 알고리즘의 다양한 버전들을 구현할 수 있는 함수형 지원이 있으며, 클래스들과 인터페이스들을 추가하여 코드의 부피를 늘리지 않으면서도 전략 객체를 사용했을 때와 똑같이 이러한 함수들을 사용할 수 있다
전략 패턴과 의존성 주입
간단한 RPG 게임을 하나 만들어본다고 가정해보자. 우리가 만들 게임의 직업군은 전사, 도적, 마법사 총 3가지가 있다. 우리는 게임에서 캐릭터가 공격할 때 직업군에 따라 다른 공격 메시지가 출력되도록 해볼 것 이다. 공격시 전사는 대검을 휘두르고, 도적은 단도로 빠르게 두번 찌르고, 마법사는 파이어볼을 발사한다.
이를 전략 패턴을 사용하여 구현 해보면, 먼저 공격 메시지를 반환하는 getAttackMessage 메소드를 갖는 AttackStrategy 인터페이스를 먼저 아래와 같이 정의한다.
interface AttackStrategy {
String getAttackMessage();
}
그 다음 각 직업군 별 공격 전략을 클래스로 구현한다. 각각의 클래스는 모두 AttackStrategy 전략의 구현체이다.
class WarriorAttackStrategy implements AttackStrategy {
public String getAttackMessage() {
return "커다란 대검을 휘두른다!";
}
}
class ThiefAttackStrategy implements AttackStrategy {
public String getAttackMessage() {
return "작은 단도로 빠르게 두번 찌른다!";
}
}
class MagicianAttackStrategy implements AttackStrategy {
public String getAttackMessage() {
return "파이어볼을 발사한다!";
}
}
class ArcherAttackStrategy implements AttackStrategy {
public String getAttackMessage() {
return "활을 발사한다!";
}
}
이렇게 전략에 대한 구현체를 작성했다면, Character 클래스는 아래와 같이 변경된다.
class Character {
private final AttackStrategy attackStrategy;
Character(AttackStrategy attackStrategy) {
this.attackStrategy = attackStrategy;
}
void attack() {
System.out.println(attackStrategy.getAttackMessage());
}
}
Character 의 인스턴스가 생성될 때, 인자를 통해 외부로부터 전략을 주입받고, attack 메소드에서는 AttackStrategy 인터페이스가 제공하는 getAttackMessage 를 호출하여 공격 메시지를 받아올 수 있다.
이 코드를 보면, 의존성주입과 유사하다는 생각이 들 것이다.
의존성주입이나 전략패턴 모두 "무언가를 쉽게 교체하기 위한 디자인패턴"이고, 같은 방식으로 작동한다.
그러나 둘은 결과적으로 둘은 다른 디자인 패턴이며,
- 전략패턴 : 의도에 초점을 맞추고 동일한 행동 계약(결제, 인증 등)을 준수하는 다양한 구현(전략)으로 인터페이스를 만들도록 권장하는 것을 포함
- 의존성주입 : 단지 일부 동작을 구현하고 의존성을 주입하기만 하는 패턴
이라는 차이가 있다.
vs 템플릿 메서드 패턴
템플릿 메서드 패턴이란?
템플릿 메소드 패턴은 특정 작업을 처리하는 일부분을 서브 클래스로 캡슐화하여 전체적인 구조는 바꾸지 않으면서 특정 단계에서 수행하는 내용을 바꾸는 패턴이다. 상위 클래스에 공통적인 메소드가 정의되어 있고, 세부적인 메소드는 추상 메소드로 두어 하위클래스에서 구현한다.
장점
- 중복코드를 줄일 수 있다.
- 자식 클래스의 역할을 줄여 핵심 로직의 관리가 용이하다.
- 좀더 코드를 객체지향적으로 구성할 수 있다.
단점
- 추상 메소드가 많아지면서 클래스 관리가 복잡해진다.
- 클래스간의 관계와 코드가 꼬여버릴 염려가 있다.
이 패턴은 스프링 내부에서는 DispatcherServlet (HTTP 프로토콜로 들어오는 모든 요청을 가장 먼저 받아 적합한 컨트롤러에 위임해주는 프론트 컨트롤러) 에서 사용되고 있다.
DispatcherServlet의 doService() 메소드는 http 요청에 대해 처리하는 메소드인데, FrameworkServlet을 상속 받아 메소드 오버라이딩을 통해 기능을 구현한다.
또한 다른 예시로, JDBCTemplate 클래스를 이용하여 DB 연동은 해당 클래스 내의 템플릿 메소드를 사용하고, 개발자는 달라지는 SQL문과 설정값을 따로 세팅하여 활용할 수도 있다.
전략 패턴과의 비교
템플릿 메서드 는 상속을 기반으로 하여 자식 클래스들에서 알고리즘의 부분들을 확장하여 변경할 수 있도록 한다.그러나 전략 패턴은 합성을 기반으로 하여 객체 행동의 일부분들을 이러한 행동에 해당하는 다양한 전략들을 제공하여 변경할 수 있게 만든 것이다. 둘의 차이는 다음과 같다
- 템플릿 메서드는 클래스 수준에서 작동하므로 정적이다. 그러나 전략 패턴은 객체 수준에서 작동하므로 런타임에 행동들을 전환할 수 있도록 하여 동적이다.
- 전략 패턴은 클라이언트와 객체 간의 결합이 느슨한 반면, 템플릿 메서드 패턴에서는 두 모듈이 더 밀접하게 결합된다.
- 전략 패턴에서는 전체 전략 알고리즘을 변경할 수 있지만, 템플릿 메서드 패턴에서는 알고리즘의 일부만 변경되고 나머지는 변경되지 않은 상태로 유지된다. (템플릿에 종속)
출처