2023년 7월 3일 작성

Strategy Pattern - 상황에 맞추어 객체의 행동 바꾸기

Strategy Pattern은 객체의 행동을 정의하고, 각 행동을 캡슐화하여 교환할 수 있도록 만듭니다.

Strategy Pattern : 객체가 처한 상황에 맞추어 행동 바꾸기

  • Strategy Pattern을 이용하면 algorithm을 상황에 따라 변경해가며 사용할 수 있습니다.
    • 동일한 목적을 지닌 algorithm group을 정의하고 각각을 캡슐화(encapsulation)하여 group 내의 algorithm을 교환해서 사용할 수 있도록 합니다.
    • algorithm을 사용하는 client에서 algorithm을 분리하기 때문에 독립적으로 algorithm을 변경할 수 있습니다.
  • 같은 목적을 가진 class들의 algorithm을 상황에 따라 교체해야 하는 경우, Strategy Pattern을 사용하면 좋습니다.
    • 평소에는 한 가지 algorithm을 사용하더라도 algorithm을 변경해야 할 때가 있습니다.
      • 예를 들어, 참조하는 class가 변경/제거될 때(compile time).
      • 예를 들어, 사용하는 시점에 따라서 적용할 algorithm이 다를 때(runtime).

Strategy Pattern의 장점

  • code 중복이 줄어듭니다.
    • algorithm을 캡슐화(encapsulation)하여 각 algorithm을 독립적으로 구현했기 때문입니다.
    • client는 strategy interface에 의존하고 있기 때문에, concrete strategy를 교체하기 쉽습니다.
  • 전략 확장이 용이합니다.
    • 새로운 algorithm을 추가할 때 기존 code를 변경하지 않아도 됩니다.
    • 새로운 concrete strategy class를 추가하기만 하면 됩니다.
  • runtime에 전략 결정과 교체가 가능합니다.
    • 상속 대신 합성(composition)을 사용하기 때문에 runtime에 strategy(algorithm)를 변경할 수 있습니다.
    • client가 strategy interface에 의존하고 있기 때문에 strategy 구현체를 교체하기 쉽습니다.

Strategy Pattern의 단점

  • code 복잡도가 증가합니다.
    • logic이 늘어날 때마다 구현체 class가 늘어나기 때문에, algorithm이 늘어날 수록 객체도 무한히 늘어납니다.
      • strategy interface와 concrete strategy class가 필요하기 때문입니다.
      • 각 algorithm을 캡슐화(encapsulation)하기 위해서 추가적인 class가 필요합니다.
    • 따라서 logic을 단순히 client에 if-else로 분리해서 그 안에 구현하는 게 보기 편할 수도 있습니다.
      • 한 눈에 들어오는 짧은 code에서는 Strategy Pattern을 사용하는 것이 오히려 가독성을 떨어뜨립니다.
      • 예를 들어, 분기가 2개인 경우에는 if-else를 사용하는 것이 더 낫습니다.
  • client가 구체적인 전략(concrete strategy)에 대해 알고 있어야 합니다.
    • client는 모든 algorithm에 대한 성능과 효율을 알고 있어야 합니다.
      • client가 자신이 사용할 전략 객체를 직접 결정하기 때문입니다.
    • client와 strategy를 한번 조립하면 전략을 변경하기 힘들어집니다.
      • client가 algorithm을 알고 있기 때문입니다.

Diagram으로 이해하기

classDiagram

class Client {
    operation()
}

class Strategy {
    <<interface>>
    algorithm()
}

class ConcreteStrategyA {
    algorithm()
}
class ConcreteStrategyB {
    algorithm()
}
class ConcreteStrategyC {
    algorithm()
}

Client --> Strategy : strategy
Strategy <|.. ConcreteStrategyA
Strategy <|.. ConcreteStrategyB
Strategy <|.. ConcreteStrategyC

note for Client "Strategy를 사용하는 객체입니다.<br>변하지 않는 부분입니다."
note for Strategy "전략에 대한 interface를 담당하는 객체입니다.<br>변하는 부분입니다."
note for ConcreteStrategyA "전략 구현을 담당하는 객체입니다."
sequenceDiagram

Client ->> ConcreteStrategyA : algorithm()
ConcreteStrategyA ->> Client : return result
Client ->> Client : Strategy를 ConcreteStrategyA에서 ConcreteStrategyB로 변경합니다.
Client ->> ConcreteStrategyB : algorithm()
ConcreteStrategyB ->> Client : return result

Example : 오리 Class

  • client에서는 나는 행동꽥꽥거리는 행동 모두에 대해서 캡슐화(encapsulation)된 algorithm group을 활용합니다.
    • client는 setter로 행동 변수를 설정하고 perform 함수를 사용하면 됩니다.
      • 예를 들어, setFlyBehavior() -> performFly()
  • 각 행동의 집합은 각 algorithm group입니다.
---
title: Client
---

classDiagram

class Duck {
    FlyBehavior flyBehavior
    QuackBehavior quackBehavior
    swim()
    display()
    performFly()
    performQuack()
    setFlyBehavior()
    setQuackBehavior()
}

class MallarDuck {
    display() 물오리의 모양 표시
}

class RedheadDuck {
    display() 붉은머리 오리 모양 표시
}

class RubberDuck {
    display() 고무 오리 모양 표시
}

class DecoyDuck {
    display() 가짜 오리 모양 표시
}

class ModelDuck {
    display() model 오리 모양 표시
}

Duck <|-- MallarDuck
Duck <|-- RedheadDuck
Duck <|-- RubberDuck
Duck <|-- DecoyDuck
Duck <|-- ModelDuck
---
title: Strategy (나는 행동)
---

classDiagram

class FlyBehavior {
    <<interface>>
    fly()
}

class FlyWithWings {
    fly() 오리가 나는 것을 구현
}

class FlyNoWay {
    fly() 아무것도 하지 않음 - 날 수 없음
}

class FlyRocketPowered {
    fly() rocket으로 나는 것을 구현
}

FlyBehavior <|.. FlyWithWings
FlyBehavior <|.. FlyNoWay
FlyBehavior <|.. FlyRocketPowered
---
title: Strategy (꽥꽥거리는 행동)
---

classDiagram

class QuackBehavior {
    <<interface>>
    quack()
}

class Quack {
    quack() 꽥꽥 소리를 내는 것을 구현
}

class Squeak {
    quack() 고무 오리가 내는 삑삑 소리 구현
}

class MuteQuack {
    quack() 아무것도 하지 않음 - 소리 내기 못함
}

class FakeQuack {
    quack() 가짜 꽥꽥 소리를 내는 것을 구현
}

QuackBehavior <|.. Quack
QuackBehavior <|.. Squeak
QuackBehavior <|.. MuteQuack
QuackBehavior <|.. FakeQuack

Main

public class MiniDuckSimulator {
    public static void main(String[] args) {
 
        Duck mallard = new MallardDuck();
        mallard.performQuack();
        mallard.performFly();
   
        Duck model = new ModelDuck();
        model.performFly();
        model.setFlyBehavior(new FlyRocketPowered());
        model.performFly();
    }
}
public class MiniDuckSimulator {
    public static void main(String[] args) {
 
        MallardDuck mallard = new MallardDuck();
        FlyBehavior cantFly = () -> System.out.println("I can't fly");
        QuackBehavior squeak = () -> System.out.println("Squeak");
        RubberDuck rubberDuckie = new RubberDuck(cantFly, squeak);
        DecoyDuck decoy = new DecoyDuck();
 
        Duck model = new ModelDuck();

        mallard.performQuack();
        rubberDuckie.performQuack();
        decoy.performQuack();
   
        model.performFly();    
        model.setFlyBehavior(new FlyRocketPowered());
        model.performFly();
    }
}

Client

public abstract class Duck {
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;

    public Duck() {
    }

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

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

    abstract void display();

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

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

    public void swim() {
        System.out.println("All ducks float, even decoys!");
    }
}
public class MallardDuck extends Duck {
    public MallardDuck() {
        quackBehavior = new Quack();
        flyBehavior = new FlyWithWings();
    }

    public void display() {
        System.out.println("I'm a real Mallard duck");
    }
}
public class RedHeadDuck extends Duck {
    public RedHeadDuck() {
        flyBehavior = new FlyWithWings();
        quackBehavior = new Quack();
    }
 
    public void display() {
        System.out.println("I'm a real Red Headed duck");
    }
}
public class RubberDuck extends Duck {
    public RubberDuck() {
        flyBehavior = new FlyNoWay();
        // quackBehavior = new Squeak();
        quackBehavior = () -> System.out.println("Squeak");
    }
    
    public RubberDuck(FlyBehavior flyBehavior, QuackBehavior quackBehavior) {
        this.flyBehavior = flyBehavior;
        this.quackBehavior = quackBehavior; 
    }
 
    public void display() {
        System.out.println("I'm a rubber duckie");
    }
}
public class DecoyDuck extends Duck {
    public DecoyDuck() {
        setFlyBehavior(new FlyNoWay());
        setQuackBehavior(new MuteQuack());
    }

    public void display() {
        System.out.println("I'm a duck Decoy");
    }
}
public class ModelDuck extends Duck {
    public ModelDuck() {
        flyBehavior = new FlyNoWay();
        quackBehavior = new Quack();
    }

    public void display() {
        System.out.println("I'm a model duck");
    }
}

Strategy : 나는 행동

public interface FlyBehavior {
    public void fly();
}
public class FlyWithWings implements FlyBehavior {
    public void fly() {
        System.out.println("I'm flying!!");
    }
}
public class FlyNoWay implements FlyBehavior {
    public void fly() {
        System.out.println("I can't fly");
    }
}
public class FlyRocketPowered implements FlyBehavior {
    public void fly() {
        System.out.println("I'm flying with a rocket");
    }
}

Strategy : 꽥꽥거리는 행동

public interface QuackBehavior {
    public void quack();
}
public class Quack implements QuackBehavior {
    public void quack() {
        System.out.println("Quack");
    }
}
public class Squeak implements QuackBehavior {
    public void quack() {
        System.out.println("Squeak");
    }
}
public class MuteQuack implements QuackBehavior {
    public void quack() {
        System.out.println("<< Silence >>");
    }
}
public class FakeQuack implements QuackBehavior {
    public void quack() {
        System.out.println("Qwak");
    }
}

Reference


목차