스트래티지 패턴

  • 전략패턴이라고도 불리는 스트래티지 패턴은 가튼 문제를 해결하는 여러 코드가 클래스별로 캡슐화 되어있고 이들이 필요할 때 교체할 수 있도록 함으로써 동일한 문제를 다른 코드로 해결할 수 이쎅 하는 디자인 패턴이다.

기존 코드

  • 우선 다음 구조의 클래스가 있다고 보자
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(개방폐쇄원칙)에 위배된다.

중복 코드

  • 연주법의 변화에 따라 StratSuperStrat에 중복코드가 발생할 수 있다.

해결책

  • Guitar의 설계에서 문제를 해결하려면 무엇이 변화되었는지 찾아야 한다.
  • 변화된 것을 찾은 후에는 이를 클래스로 캡슐화 해야한다. Guitar에서 내부 코드를 바꾸면서 문제를 발생시키는 요인은 Guitar의 play와 pickupSelector의 변화다.
  • 이를 캡슐화 하려면 외부에서 구체적인 play방식과 pickup구성을 담은 구체적인 클래스들을 은닉해야 한다.
  • 다음의 코드는 playpickupSelect를 인터페이스로 구현했다.
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();
    }

}
  • 아래는 playpickupSelect에 대한 인터페이스와 그 구현이다.

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에 구현되어있는 PickupStrategyPlayStrategy의 setter에 각각 알맞은 연주법과 픽업을 주입한다.
  • 이렇게 하면 기존 코드를 변경하지 않으면서도 기능을 전략적으로 추가하는데 용이하다.
  • 아래는 실행결과다
이 기타의 이름은 미팬 스트랫
이 기타는 3싱글 픽업을 갖고 있어요
딩딩디리링~
---------------
이 기타의 이름은 ESP E-II
이 기타는 2험버커 픽업을 갖고 있어요.
즁즁즁-

스트래티지 패턴의 구조 해석

  • StrategyPickupStrategy, PlayStrategy
    • 인터페이스나 추상 클래스로 외부에서 동일한 방식으로 알고리즘을 호출하는 방법을 명시한다.
  • ConcreteStrategyHumberckerStrategy,SingleStrategy / PickingStrategy, PalmmuteStrategy
    • 스트래티지 패턴에서 명시한 알고리즘을 실제로 구현한 클래스다.
  • ContextGuitar
    • 스트래티지 패턴을 이용하는 역할을 수행한다. 필요에 따라 동적으로 구체적인 전략을 바꿀 수 있도록 setter를 제공한다.

Comments