행동 관련 : Strategy(전략) 패턴

    들어가기 전

    이 글은 인프런 백기선님의 GOF 강의를 공부하며 작성한 글입니다. 

     


    전략(Strategy) 패턴

    • GOF : 여러 알고리즘을 캡슐화하고 상호 교환 가능하게 만드는 패턴.
      • 작업을 수행하는 방법이 여러 가지일 때, 각 방법을 개별적인 클래스로 캡슐화한다.
      • 캡슐화 되어 있는 것을 공통된 인터페이스로 추상화 한다.
      • 로직을 수행하는 곳에서는 추상화된 인터페이스만 사용한다.
      • 로직을 수행하는 코드는 바뀌지 않지만, Concrete Strategy만을 갈아끼면서 코드 변경 없이 다른 알고리즘을 실행할 수 있도록 해줌. 
    • 적용하면 좋은 경우
      • 파라미터의 값에 따라 If - else 문으로 분기하면서 서로 다른 행위들을 하는 코드는 전략 패턴을 적용하기 좋음.  
      • Strategy 패턴을 사용하면, 새로운 종류의 전략을 추가하더라도 기존 코드를 건드리지 않고 추가할 수 있음.
    • GOF Component
      • Context 
        • 기존 로직을 수행하던 클래스. (전체적인 뼈대)
        • 모든 로직 중에서 상황에 따라 달라지는 로직을 Strategy라는 인터페이스로 뽑아냄. 
        • 여기서 Context는 Strategy 인터페이스를 참조.
      • Strategy
        • Context에서 의존할 Interface를 제공함. 
        • ConcreteStrategy는 각 알고리즘의 구현체임. (특정 값에 따라 달라지는 동작) 
    • Context는 Strategy 클래스에게 특정 로직을 위임하는 형태가 된다. 파라메터에 따라 달라지는 동작을 Concrete Strategy 클래스로 모두 옮겨둬서 가독성을 올릴 수 있고, 새로운 Strategy를 손쉽게 추가할 수도 있다. 

     

     


    디자인 패턴이 필요한 코드

    public class BlueLightRedLight {
    
        private int speed;
    
        public BlueLightRedLight(int speed) {
            this.speed = speed;
        }
    
    	// speed 값에 따른 동작있음.
        public void blueLight() {
            if (speed == 1) {
                System.out.println("무 궁 화    꽃   이");
            } else if (speed == 2) {
                System.out.println("무궁화꽃이");
            } else {
                System.out.println("무광꼬치");
            }
    
        }
    
    	// speed 값에 따른 동작있음.
        public void redLight() {
            if (speed == 1) {
                System.out.println("피 었 습 니  다.");
            } else if (speed == 2) {
                System.out.println("피었습니다.");
            } else {
                System.out.println("피어씀다");
            }
        }
    }
    • BlueLightRedLight 클래스를 사용하는 클래스는 Speed 값을 전달하고, 그 Speed 값에 따라 blueLight(), redLight() 메서드는 다른 오퍼레이션을 수행한다. 

    만약 Speed에 새롭게 4가 추가된다면 BlueLightRedLight의 메서드 내의 코드를 직접 수정해야만 한다. 즉, BlueLightRedLight는 OCP를 지키지 못하고 있다. 이 부분은 Strategy 패턴을 적용해서 Speed 값에 따른 동작을 분리하도록 하면 OCP 문제를 해결할 수 있다. 

     


    디자인 패턴 적용하기 

    BlueLightRedLight 클래스에서는 Speed의 값에 따라 서로 다르게 동작을 했었고, 새로운 Speed의 값이 들어오면 이에 대한 코드를 반영해야해서 OCP를 지키지 못했었다. Speed 부분을 Strategy로 분리하고, BlueLightRedLight는 Strategy 객체에게 위임하는 방식으로 코드를 개선할 수 있다. 

    // Speed 파라메터를 인터페이스로 추상화.
    public interface Speed {
        void blueLight();
        void redLight();
    }
    
    // ConcreteStrategy 구현.
    public class Fast implements Speed {
    
        @Override
        public void blueLight() {
            System.out.println("무광꼬치");
        }
    
        @Override
        public void redLight() {
            System.out.println("피어씀다");
        }
    }

    Speed에 따라 blueLight(), redLight()가 서로 다른 동작을 하기 때문에 다음과 같이 처리해 볼 수 있다.

    • Speed를 인터페이스로 뽑는다. 
    • Speed 인터페이스는 blueLight(), redLight()를 제공한다.
    • Speed의 구현체(ConcreteStrategy)는 기존 If-Else 문에 있던 로직에서 해당되는 부분만 가져온다.
    public class BlueLightRedLight {
        public void blueLight(Speed speed) {
            speed.blueLight();
        }
    
        public void redLight(Speed speed) {
            speed.redLight();
        }
    }
    • 기존 BlueLightRedLight 클래스는 If - Else문으로 처리가 되었었는데, 그 부분은 모두 ConcreteStrategy로 분리되었다. 
    • 바뀐 BlueLightRedLight 클래스는 필요한 Strategy를 받은 후, Operation을 Strategy에게 위임하는 방식으로 동작한다. 

    이렇게 코드를 변경하면 새로운 Speed 값이 들어오더라도 기존 코드에 영향을 주지 않고 추가할 수 있다. 예를 들어 Fastest를 하나 정의하고 싶다면 Speed 인터페이스만 구현한 코드를 하나 추가하기만 하면 된다. 

     


    Strategy 패턴의 장/단점

    • 장점
      • 새로운 전략을 추가하더라도 기존 코드를 변경하지 않음. (OCP)
      • 상속 대신에 위임을 사용할 수 있음. 
      • 런타임 중에 전략을 변경할 수 있음.
        • game.blueLight(new Normal()) / game.redLight(new Fast()),
    • 단점
      • 클래스가 많아져서 복잡도가 증가함. 
      • 클라이언트 코드가 구체적인 전략을 알아야 함. 

    Strategy 패턴의 장점은 상속 대신에 위임을 사용할 수 있다는 점도 있다. 상속은 하나의 상속 밖에 할 수 없기 때문에 미리 상속을 사용해버리면, 정말 필요한 경우에 상속을 사용하지 못할 수 있다. 또한 부모 클래스의 모든 변경에 민감하게 반응한다. 

    game.redLight(new Fast());

    단점 중 하나는 클라이언트 코드가 ConcreteStrategy에 대해서 알아야 한다는 부분이 있다. 예를 들어 위 코드는 클라이언트 코드가 구체적인 전략에 대해서 알고 있고, 이것은 클라이언트-Concrete Strategy에 강한 의존 관계가 생기는 것을 의미한다. 

     


    Strategy 패턴 vs State 패턴

    현재 위 코드는 Strategy 패턴으로 작성되어있으나, 약간의 변경으로 State 패턴처럼 동작할 수도 있다. 

    public class BlueLightRedLight {
        
        private Speed speed;
        
        public void blueLight() {
            this.speed.blueLight();
        }
    
        public void redLight() {
            this.speed.redLight();
        }
        
        public void changeSpeed(Speed speed){
            this.speed = speed;
        }
        
    }

    예를 들어 Strategy 패턴을 적용한 BlueLightRedLight에서 위 코드처럼 Speed를 상태로 가지고 있고, 그 상태에 전적으로 처리하는 방법을 위임하면 State 패턴으로 볼 수 있다. 왜냐하면 '상태에 따라 개별적인 동작'을 하도록 되어있기 때문이다. 

    그러나 기존의 Strategy 패턴은 상태에 따라 다르게 동작하는 것이 아니라 어떤 Strategy(구체적인 알고리즘)이 전달되느냐에 따라 다르게 동작했었다. 

    '디자인 패턴' 카테고리의 다른 글

    행동 관련 : 커맨드 패턴  (0) 2023.12.02
    구조 관련 : Composite 패턴  (0) 2023.11.30
    행동 관련 : State 패턴  (0) 2023.11.30
    행동 관련 : 템플릿 메서드 패턴  (0) 2023.11.30
    구조 관련 : 프록시 패턴  (0) 2023.11.26

    댓글

    Designed by JB FACTORY