본문 바로가기
CS/Design Pattern

[디자인 패턴] 전략 패턴 (Strategy Pattern)

by Nahwasa 2023. 6. 17.

스터디 메인 페이지

- ☆ 표시가 붙은 부분은 스터디 중 나온 얘기 혹은 제 개인적인 생각이나 제가 이해한 방식을 적어놓은 것으로, 책에 나오지 않는 내용입니다. 따라서 책에서 말하고자 하는 바와 다를 수 있습니다. 또한 책에는 따로 Step으로 나오지 않습니다. 설명의 편의를 위해 임의로 나눈 것 입니다.

- 모든 이미지의 출처는 헤드퍼스트 디자인패턴 개정판(한빛미디어) 입니다.

 


 

전략 패턴 (Strategy Pattern)

코드 링크 : github

 

GitHub - nahwasa/study-design-patterns: 헤드퍼스트 디자인패턴 스터디 진행하면서 각 패턴별 문제점이 있

헤드퍼스트 디자인패턴 스터디 진행하면서 각 패턴별 문제점이 있는 코드부터 개선되는 코드까지 짜보기 위한 레포 - GitHub - nahwasa/study-design-patterns: 헤드퍼스트 디자인패턴 스터디 진행하면서

github.com

 

 

Step 1. 초기 코드

 

class Duck {
    public void quack() {
        System.out.println("Quack!");
    }

    public void swim() {
        System.out.println("Swim!");
    }

    public void display() {
        System.out.println("Display!");
    }
}

class MallardDuck extends Duck {
    @Override
    public void display() {
        System.out.println("MallardDuck display!");
    }
}

class RedheadDuck extends Duck {
    @Override
    public void display() {
        System.out.println("RedheadDuck display!");
    }
}

class RubberDuck extends Duck {
    @Override
    public void quack() {
        System.out.println("Squeak!");
    }

    @Override
    public void display() {
        System.out.println("RubberDuck display!");
    }
}

 

 

Step 2. 오리가 날 수 있어야 한다!

일부 Duck들만 fly()를 추가하려고 하는데
Duck에 추가했더니 당연히 전부 적용되버림.
러버덕은 날면 안되니깐 아무것도 안하게 오버라이드를 해줘야 한다.

class Duck {
    public void quack() {
        System.out.println("Quack!");
    }

    public void swim() {
        System.out.println("Swim!");
    }

    public void display() {
        System.out.println("Display!");
    }

    public void fly() {
        System.out.println("Fly!");
    }
}

class MallardDuck extends Duck {
    @Override
    public void display() {
        System.out.println("MallardDuck display!");
    }
}

class RedheadDuck extends Duck {
    @Override
    public void display() {
        System.out.println("RedheadDuck display!");
    }
}

class RubberDuck extends Duck {
    @Override
    public void quack() {
        System.out.println("Squeak!");
    }

    @Override
    public void display() {
        System.out.println("RubberDuck display!");
    }

    @Override
    public void fly() {
        // do nothing
    }
}

 

 

Step 3. 가짜 오리 추가!

가짜 오리(DecoyDuck)을 추가하려고 했더니
quack, fly 둘 다 아무것도 안해야됨;
이렇게 상속으로 처리하는건 해결책이 아닌 것 같음.
이후에 오리가 추가될 때 마다 fly랑 quack 전부 살펴보고 상황에 따라 오버라이드 해야됨 ㅠ

 

class Duck {
    public void quack() {
        System.out.println("Quack!");
    }

    public void swim() {
        System.out.println("Swim!");
    }

    public void display() {
        System.out.println("Display!");
    }

    public void fly() {
        System.out.println("Fly!");
    }
}

class MallardDuck extends Duck {
    @Override
    public void display() {
        System.out.println("MallardDuck display!");
    }
}

class RedheadDuck extends Duck {
    @Override
    public void display() {
        System.out.println("RedheadDuck display!");
    }
}

class RubberDuck extends Duck {
    @Override
    public void quack() {
        System.out.println("Squeak!");
    }

    @Override
    public void display() {
        System.out.println("RubberDuck display!");
    }

    @Override
    public void fly() {
        // do nothing
    }
}

class DecoyDuck extends Duck {
    @Override
    public void display() {
        System.out.println("DecoyDuck display!");
    }

    @Override
    public void quack() {
        // do nothing
    }

    @Override
    public void fly() {
        // do nothing
    }
}

 

 

Step 4. 상속대신 인터페이스?

상속은 아닌 것 같으니 인터페이스를 추가해서 해결해보려 하는 과정임.
문제는 기존에 상속해서 fly랑 quack 잘 쓰고 있던 오리들도
구조가 바뀌면서 전부 fly랑 quack을 구현해야 됨;
따라서 전체적인 오리들을 전부 손대야 하는대다가 코드중복이 발생해버림.
이것도 아닌 것 같음.

 

class Duck {
        public void swim() {
            System.out.println("Swim!");
        }

        public void display() {
            System.out.println("Display!");
        }
    }

    interface Flyable {
        public void fly();
    }

    interface Quackable {
        public void quack();
    }

    class MallardDuck extends Duck implements Flyable, Quackable {
        @Override
        public void display() {
            System.out.println("MallardDuck display!");
        }

        @Override
        public void fly() {
            System.out.println("Fly!");
        }

        @Override
        public void quack() {
            System.out.println("Quack!");
        }
    }

    class RedheadDuck extends Duck implements Flyable, Quackable {
        @Override
        public void display() {
            System.out.println("RedheadDuck display!");
        }

        @Override
        public void fly() {
            System.out.println("Fly!");
        }

        @Override
        public void quack() {
            System.out.println("Quack!");
        }
    }

    class RubberDuck extends Duck implements Quackable {
        @Override
        public void quack() {
            System.out.println("Squeak!");
        }

        @Override
        public void display() {
            System.out.println("RubberDuck display!");
        }
    }

    class DecoyDuck extends Duck {
        @Override
        public void display() {
            System.out.println("DecoyDuck display!");
        }
    }

 

 

Step 5. 전략 패턴 적용! 바뀌는 부분과 바뀌지 않는 부분 분리.

변화하는 부분을 뽑아내서 별도의 클래스 집합으로 분리!

 

public static void main(String[] args) {
    new Step5().simulate();
}

public void simulate() {
    Duck mallard = new MallardDuck();
    mallard.performQuack();
    mallard.performFly();
}

abstract class Duck {
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;

    public Duck() {}

    public abstract void display();

    public void performFly() {
        flyBehavior.fly();
    }

    public void performQuack() {
        quackBehavior.quack();
    }

    public void swim() {
        System.out.println("모든 오리는 물에 뜹니다. 가짜 오리도 뜨죠.");
    }
}

interface FlyBehavior {
    public void fly();
}

class FlyWithWings implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("날고 있어요!~");
    }
}

class FlyNoWay implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("저는 못 날아요");
    }
}

interface QuackBehavior {
    public void quack();
}

class Quack implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("꽥");
    }
}

class MuteQuack implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("<< 조용~ >>");
    }
}

class Squeak implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("삑");
    }
}

class MallardDuck extends Duck {
    public MallardDuck() {
        quackBehavior = new Quack();
        flyBehavior = new FlyWithWings();
    }

    @Override
    public void display() {
        System.out.println("저는 물오리입니다");
    }
}

 

 

Step 6. 전략 패턴 적용! 동적으로 행동 지정 가능하게 하기

세터 메소드로 오리의 행동을 동적으로 바꿀 수 있도록 함.
이제 중간에 오리의 행동 변경 가능.

☆ 물론 동적으로 변경 가능하다는 점이 유효한지는 프로젝트마다 달라질 것이라 생각됨. 항상 좋은건 절대 아님.

 

public static void main(String[] args) {
    new Step6().simulate();
}

public void simulate() {
    Duck mallard = new MallardDuck();
    mallard.performQuack();
    mallard.performFly();

    Duck model = new ModelDuck();
    model.performFly();
    model.setFlyBehavior(new FlyRocketPowered());
    model.performFly();
}

abstract class Duck {
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;

    public Duck() {}

    public abstract void display();

    public void performFly() {
        flyBehavior.fly();
    }

    public void performQuack() {
        quackBehavior.quack();
    }

    public void swim() {
        System.out.println("모든 오리는 물에 뜹니다. 가짜 오리도 뜨죠.");
    }

    public void setFlyBehavior(FlyBehavior fb) {
        flyBehavior = fb;
    }

    public void setQuackBehavior(QuackBehavior qb) {
        quackBehavior = qb;
    }
}

interface FlyBehavior {
    public void fly();
}

class FlyWithWings implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("날고 있어요!~");
    }
}

class FlyNoWay implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("저는 못 날아요");
    }
}

class FlyRocketPowered implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("로켓 추진으로 날아갑니다!");
    }
}

interface QuackBehavior {
    public void quack();
}

class Quack implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("꽥");
    }
}

class MuteQuack implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("<< 조용~ >>");
    }
}

class Squeak implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("삑");
    }
}

class MallardDuck extends Duck {
    public MallardDuck() {
        quackBehavior = new Quack();
        flyBehavior = new FlyWithWings();
    }

    @Override
    public void display() {
        System.out.println("저는 물오리입니다");
    }
}

class ModelDuck extends Duck {
    public ModelDuck() {
        flyBehavior = new FlyNoWay();
        quackBehavior = new Quack();
    }

    @Override
    public void display() {
        System.out.println("저는 모형 오리입니다");
    }
}

 

 

전략 패턴?

  • 알고리즘군을 정의하고 캡슐화해서 각각의 알고리즘군을 수정해서 쓸 수 있게 해준다.
  • 클라이언트로부터 알고리즘을 분리해서 독립적으로 변경할 수 있음!
  • 즉, 객체가 할 수 있는 알고리즘(행위) 각각의 전략 클래스들을 만들어서 갈아끼울 수 있게 해줌.

 

최종 코드 : github