-
[Design Pattern] Strategy PatternLearn/Architecture 2022. 8. 26. 00:34
# 개요
특정한 상황에서 swap될 수 있는 encapsulate된 알고리즘의 집합
(교수님 무슨말입니까 이게??)> 변경에 더 잘 대처하기 위해 인터페이스를 중심으로 소통하도록 알고리즘들을 encapsulation하는 방식
[예시]
- 오리의 울음소리(quack)는 같으며 같은 방식으로 수영한다고 가정
> 공통된 부분이므로 abstract에 구현
- display는 오리마다 다름.
> 구현하지 않고 abstract method로 남겨둠
요구사항이 변하지 않으면 위 방식으로 디자인해도 충분히 훌륭하다.
문제는 새로운 behavior인 fly를 새로 정의해야한다면... (요구사항 변경!)
> 상위 클래스에 fly를 구현하면 모든 오리가 날게 되어버린다. (심지어 rubber duck도)
> rubber duck에서 override하면 해결된다.
문제는 계속 변종이 생기면 곤란해진다. --> 디자인 변화가 필요하다.
# 패턴 적용하기
일단 변하는 것과 변하지 않는 것을 구분한다.
그리고 변하는 것은 변하지 않는 인터페이스로 보호해준다.
다른 예시를 살펴보면,,
구조가 위와 같을 때 아래와 같이 직접 bark를 Implementation 하기보다는
Dog d = new Dog(); d.bark();
아래와 같이 상위 클래스인 Animal에서 makeSound를 호출하여 Dog의 makeSound가 호출되도록 하는게 더 낫다.
Animal a = new Dog(); a.makeSound();
아래와 같이 한번 더 개선하면 직접 하위의 concrete 클래스를 부르지 않아도 된다.
Animal a = getAnimal("Dog"); a.makeSound()
# 구현
위의 Duck 케이스를 구현하면 아래와 같다.
public abstract class Duck { // Object composition FlyBehavior flyBehavior; // Object composition QuackBehavior quackBehavior; public Duck() { } // display는 각자가 구현하도록 abstract로 public abstract void display(); public void performFly() { // delegation flyBehavior.fly(); } public void performQuack() { // delegation quackBehavior.quack(); } public void swim() { System.out.println("All ducks float, even decoys!") } }
object composition : 다른 객체에 대한 레퍼런스를 가지고 있는 것
delegation : 해당 객체에 할 일을 대신 맡기는 것
위의 FlyBehavior와 QuackBehavior은 아래와 같이 인터페이스를 만들고 구현한다.
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"); } }
그러면 실제로 오리를 구현할때는 아래와 같다.
quackBehavior, flyBehavior 에서는 하위 클래스 중 해당되는 것의 인스턴스를 선택한다.
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"); } }
setter를 만들어서 behavior을 동적으로 할당할 수도 있다. (런타임때 바뀜)
public abstract class Duck { ... public void setFlyBehavior(FlyBehavior fb) { flyBehavior = fb; } public void setQuackBehavior(QuackBehavior qb) { quackBehavior = qb; } ... }
실제 setter 호출은 아래와 같이 FlyBehavior, QuackBehavior을 구현한 인스턴스가 들어간다.
... Duck model = new ModelDuck(); model.setFlyBehavior(new FlyRocketPowered()); ...
# Inheritance vs. Composition
Strategy pattern에서는 상속(inheritance)보다는 composition을 쓰길 권장한다.
> 위 개요의 예시에서 봤지만 상속받아서 override하는건 변종에 유연하게 대처하지 못한다. (재사용성 떨어짐)
Class Inheritance
- 보통 parent class의 구현이 subclass에 보이므로 white-box reuse라고 부른다. (커플링)
- 사용하긴 쉬운데 subclass가 parent class의 변경에 영향을 받고 런타임에 변경이 안된다는 단점이 있다.
Object Composition
- Interface를 통해서만 커뮤니케이션하므로 Inheritance와는 달리 black-box reuse라고 부른다.
- 런타임때 변경이 가능하고 구현시 고려해야하는 dependency도 적지만 이해가 어렵다는 단점이 있다.
# 정리
①변하는 것은 Encapsulate하고 ②implementation보다는 interface를 쓰고 ③inheritance보다는 composition을 선호하라는 디자인 패턴을 패턴화한게 Strategy pattern이다.
- 전략을 사용하고 있는 클래스를 Context Class라고 부름.
- 인터페이스만 만족한다면 런타임때 알고리즘은 얼마든지 갈아낄 수 있다.
'Learn > Architecture' 카테고리의 다른 글
[Design Pattern] Template Method Pattern (0) 2022.08.28 [Design Pattern] Observer Pattern (0) 2022.08.27 [Design Pattern] GRASP Principle (0) 2022.08.24 [Design Pattern] SOLID Principle (0) 2022.08.21 [Design Pattern] Object Oriented Paradigm (0) 2022.08.20