스트래티지 패턴
- 전략패턴이라고도 불리는 스트래티지 패턴은 가튼 문제를 해결하는 여러 코드가 클래스별로 캡슐화 되어있고 이들이 필요할 때 교체할 수 있도록 함으로써 동일한 문제를 다른 코드로 해결할 수 이쎅 하는 디자인 패턴이다.
기존 코드
- 우선 다음 구조의 클래스가 있다고 보자
public abstract class Guitar {
private String name;
public Guitar(String name) {
this.name = name;
}
public String getName() {
return name;
}
public abstract void play();
public abstract void pickupSelect();
}
- 아래는
Guitar
를 상속(extends) 받는 두 클래스다.
public class Strat extends Guitar {
public Strat(String name) {
super(name);
}
@Override
public void play() {
System.out.println("딩~ 디리리리딩~");
}
@Override
public void pickupSelect() {
System.out.println("3 싱글의 5단 픽업 셀랙터가 있어요.");
}
}
public class SuperStrat extends Guitar {
public SuperStrat(String name) {
super(name);
}
@Override
public void play() {
System.out.println("좌좡~ 좌우지좡지징~");
}
@Override
public void pickupSelect() {
System.out.println("2 험버커의 5단 픽업 셀렉터가 있어요.");
}
}
- 아래는 간단한 기타 플레이어에 대한 테스트다.
public class GuitarPlayer {
public static void main(String[] args) {
Guitar strat = new Strat("미팬 스트랫");
Guitar superStrat = new SuperStrat("ESP E-II");
System.out.println("이 기타의 이름은 " + strat.getName());
strat.play();
strat.pickupSelect();
System.out.println("-------------");
System.out.println("이 기타의 이름은 " + superStrat.getName());
superStrat.play();
superStrat.pickupSelect();
}
}
- 아래는 실행결과다
이 기타의 이름은 미팬 스트랫
딩~ 디리리리딩~
3 싱글의 5단 픽업 셀랙터가 있어요.
-------------
이 기타의 이름은 ESP E-II
좌좡~ 좌우지좡지징~
2 험버커의 5단 픽업 셀렉터가 있어요.
별로 중요한 이야기는 아니지만 스트랫이라고 모두 ‘디리링’이 아니고 슈퍼트스트랫이라고 ‘좌좌좡’ 소리가 나는게 아니다 모든것은 다 이펙터와 앰프의 채널 상태가 좌지우지 한다. 이 코드에서는 해당 장르에서 대표격으로 등장하는 간판 기타를 뜻한다. 반례로 slipknot의 짐 루트는 밝고 경쾌한 음악에서 많이 쓰는 telecaster에 험버커 두개를 박아서 기타가 부서지도록 근음을 조진다.
문제점
- 기존 기타의 소리를 수정하려면? 예를들어 딩 디리링이 아니라 우왕우왕이라면?
- 더 심각한 것은 일반 피킹이 아닌 팜뮤트 등의 연주법을 변화한다면?
OCP 위배
- 새로운 기능을 추가하기 위해 기존 코드의 내용을 수정하게 되면 OCP(개방폐쇄원칙)에 위배된다.
중복 코드
- 연주법의 변화에 따라
Strat
과SuperStrat
에 중복코드가 발생할 수 있다.
해결책
- Guitar의 설계에서 문제를 해결하려면 무엇이 변화되었는지 찾아야 한다.
- 변화된 것을 찾은 후에는 이를 클래스로 캡슐화 해야한다. Guitar에서 내부 코드를 바꾸면서 문제를 발생시키는 요인은 Guitar의 play와 pickupSelector의 변화다.
- 이를 캡슐화 하려면 외부에서 구체적인 play방식과 pickup구성을 담은 구체적인 클래스들을 은닉해야 한다.
- 다음의 코드는
play
와pickupSelect
를 인터페이스로 구현했다.
public abstract class Guitar {
private String name;
private PlayStrategy playStrategy;
private PickupStrategy pickupStrategy;
public Guitar(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setPlayStrategy(PlayStrategy playStrategy) {
this.playStrategy = playStrategy;
}
public void play() {
playStrategy.play();
}
public void setPickupStrategy(PickupStrategy pickupStrategy) {
this.pickupStrategy = pickupStrategy;
}
public void pickupSelect() {
pickupStrategy.pickupSelect();
}
}
- 아래는
play
와pickupSelect
에 대한 인터페이스와 그 구현이다.
Play와 그 구현체
public interface PlayStrategy {
public void play();
}
public class PickingStrategy implements PlayStrategy {
@Override
public void play() {
System.out.println("딩딩디리링~");
}
}
public class PalmmuteStrategy implements PlayStrategy {
@Override
public void play() {
System.out.println("즁즁즁-");
}
}
- 일반적인 피킹과 팜뮤트에 대한 구현(implement)이다.
pickupSelect와 그 구현체
public interface PickupStrategy {
public void pickupSelect();
}
public class SingleStrategy implements PickupStrategy {
@Override
public void pickupSelect() {
System.out.println("이 기타는 3싱글 픽업을 갖고 있어요");
}
}
public class HumberckerStrategy implements PickupStrategy {
@Override
public void pickupSelect() {
System.out.println("이 기타는 2험버커 픽업을 갖고 있어요.");
}
}
- 일반적으로 부드러운 모던락 계열에서 많이 쓰는 싱글코일 픽업과 하드락, 메탈같은 강렬한 음악에서 많이 쓰는 험버커 픽업으로 만들었다.
Guitar extends
public class Strat extends Guitar {
public Strat(String name) {
super(name);
}
}
public class SuperStrat extends Guitar {
public SuperStrat(String name) {
super(name);
}
}
- 기타에 대한 코드는 추상클래스에 구현되어있으므로 기존 메소드는 제거한다.
Test Guitar play
public class GuitarPlayer {
public static void main(String[] args) {
Guitar strat = new Strat("미팬 스트랫");
Guitar superStrat = new SuperStrat("ESP E-II");
strat.setPickupStrategy(new SingleStrategy());
superStrat.setPickupStrategy(new HumberckerStrategy());
strat.setPlayStrategy(new PickingStrategy());
superStrat.setPlayStrategy(new PalmmuteStrategy());
System.out.println("이 기타의 이름은 " + strat.getName());
strat.pickupSelect();
strat.play();
System.out.println("---------------");
System.out.println("이 기타의 이름은 " + superStrat.getName());
superStrat.pickupSelect();
superStrat.play();
}
}
Guitar
인스턴스를 생성하는 과정까지는 같다.- 이후
Guitar
에 구현되어있는PickupStrategy
와PlayStrategy
의 setter에 각각 알맞은 연주법과 픽업을 주입한다. - 이렇게 하면 기존 코드를 변경하지 않으면서도 기능을 전략적으로 추가하는데 용이하다.
- 아래는 실행결과다
이 기타의 이름은 미팬 스트랫
이 기타는 3싱글 픽업을 갖고 있어요
딩딩디리링~
---------------
이 기타의 이름은 ESP E-II
이 기타는 2험버커 픽업을 갖고 있어요.
즁즁즁-
스트래티지 패턴의 구조 해석
- Strategy →
PickupStrategy
,PlayStrategy
- 인터페이스나 추상 클래스로 외부에서 동일한 방식으로 알고리즘을 호출하는 방법을 명시한다.
- ConcreteStrategy →
HumberckerStrategy
,SingleStrategy
/PickingStrategy
,PalmmuteStrategy
- 스트래티지 패턴에서 명시한 알고리즘을 실제로 구현한 클래스다.
- Context →
Guitar
- 스트래티지 패턴을 이용하는 역할을 수행한다. 필요에 따라 동적으로 구체적인 전략을 바꿀 수 있도록 setter를 제공한다.