Design Pattern

01. Strategy Pattern

kangwook 2022. 3. 12. 23:41
객체가 할 수 있는 행위들 각각을 전략으로 만들어 놓고, 동적으로 행위의 수정이 필요한 경우 전략을 바꾸는 것 만으로 행위의 수정이 가능하도록 만든 패턴

Strategy Pattern

Strategy Pattern은 알고리즘의 그룹들을 정의하고 각 그룹을 캡슐화하며 상호 호환되게 한다. Strategy는 알고리즘을 사용하는 클라이언트와 독립적으로 알고리즘을 변화시킨다.

  • Context : Strategy Pattern을 이용하는 역할 수행. 동적으로 구체적인 Strategy(전략)를 바꿀 수 있도록 setter 메서드를 제공할 수도 있음
  • Strategy : 인터페이스나 추상 클래스로 외부에서 동일한 방식으로 알고리즘을 호출하는 방법 명시
  • ConcreteStrategy : Strategy에서 명시한 알고리즘을 실제로 구현한 클래스

  • 캡슐화
    • Strategy들은 캡슐화 되어 실제 구현 내용은 ConcreteStrategy에 감추어 은닉
    • Context는 사용할 알고리즘을 인터페이스를 이용하여 캡슐화하고, 필요에 따라 동적으로 변경하여 사용할 수 있음
    • Strategy를 구현하는 알고리즘들은 해당 계열 안에서 상호 교체가 가능

Strategy Pattern 예제

  • 여러 종류의 오리를 특징에 따라 Strategy 패턴을 이용해서 분류
    • Strategy를 이용하는 Duck 클래스
    • Duck의 행동에 대한 Strategy인 FlyBehavior, QuackBehavior

  • Context : Duck클래스
    • Duck 클래스는 추상 클래스로 이를 상속받는 여러 하위 클래스(ex. MallardDuck, RubberDuck 등)들이 Strategy 패턴의 context가 됨
public abstract class Duck {
	QuackBehavior quackBehavior;
	FlyBehavior flyBehavior;
	
	public Duck() {}
	
	public abstract void display();
	
	public void performQuack() {
		quackBehavior.quack();
	}

	public void perfomrFly() {
		flyBehavior.fly();
	}
}
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");
	}
}
  • Strategy : FlyBehavior, QuackBehavior
    • Duck 클래스가 이용할 알고리즘을 호출하는 방법이 있는 인터페이스
    • Strategy와 이를 구현하는 ConcreteStrategy가 존재
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 interface QuackBehavior {
	public void quack();
}

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

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

public class Squeak implements QuackBehavior {
	public void quack() {
		System.out.println("Squeak");
	}
}
  • Main : MiniDuckSimulator
public class MiniDuckSimulator {
	public static void main(Stirng [] args) {
		Duck mallard = new MallardDuck();
		mallard.performQuack();
		mallard.performFly();
	}
}

출력결과

Quack
I'm flying!!

추가적인 내용

  • Context에 Setter메서드를 추가해서 동적으로 사용하는 방법
  • 언제든지 Context가 원하는 Strategy로 코드 수정없이 변경이 가능함

  • Duck 클래스에 setter클래스를 추가해주고 이를 상속받는 하위클래스를 선언
public abstract class Duck {
	...
	public void setFlyBehavior(FlyBehavior fb) {
		flyBehavior = fb;
	}

	public void setQuackBehavior(QuackBehavior qb) {
		quackBehavior = qb;
	}
}
public class RubberDuck extends Duck {
	public RubberDuck() {
		flyBehavior = new FlyNoWay();
		quackBehavior = new MuteQuack();
	}

	public void display() {
		System.out.println("I'm a rubber duck");
	}
}
  • Main 클래스에서 실행
public class MiniDuckSimulator {
	public static void main(String [] args) {
		Duck rubber = new RubberDuck();
		rubber.performFly();
		rubber.performQuack();
		rubber.setFlyBehavior(new FlyWithWings());
		rubber.performFly();
	}
}

출력결과

I can’t fly
<<Silence>>
I’m flying!!

결론

  • Strategy 패턴은 행위 패턴(Behavioral Pattern) 중 하나로 알고리즘을 별도로 분리해서 실행 중에 Context가 사용할 알고리즘을 선택할 수 있게 하는 디자인 패턴
  • 행위에 대한 상속 보다는 분리 후 합성을 하는 것이 디자인 원칙에 맞는 좋은 방법
  • 장점
    • Context 코드의 변경 없이 새로운 Strategy를 추가할 수 있음 → Context에 대한 OCP원칙을 만족

OCP(Open-Close Principle) : “확장에 대해서는 열려 있고 수정에 대해서는 닫혀 있어야 한다.”

  • 단점
    • 사용하는 Strategy가 적다면 복잡성만 늘어나게 됨
    • Strategy 객체에 전달된 인자의 일부가 사용되지 않을 수 있어 통신 오버헤드가 클 수 있음 → 통신 오버헤드 : 통신을 하기 위해 들어가는 간접적인 처리 시간, 메모리 등

본문은 Head First Design Pattern (2004) 를 참고하여 공부하고 작성하였음