ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Design Pattern] Strategy Pattern
    Learn/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라고 부름. 

    - 인터페이스만 만족한다면 런타임때 알고리즘은 얼마든지 갈아낄 수 있다. 

     

    댓글

Designed by Tistory.