본문 바로가기

책리뷰/헤드퍼스트 디자인패턴

헤드퍼스트 디자인패턴 - 전략 패턴

728x90
반응형

목차

     

    전략 패턴 (Strategy Pattern) 이란?


    알고리즘군을 정의하고 캡슐화해서 각각의 알고리즘군을 수정해서 쓸 수 있게 해주는 패턴

    전략패턴을 사용하면 클라이언트로부터 알고리즘을 분리해서 독립적으로 변경할 수 있음.

     

     

    오리 시뮬레이션 게임(SimUduck)


     

    오리 시뮬레이션 게임회사에서는 표준 객체지향 기법을 사용해서 Duck이라는 슈퍼클래스를 만든 후 

    그 클래스를 확장해서 다른 종류의 오리를 만들었다.

     

    오리 시뮬레이션게임 차별화하기


    기존 클래스에 오리가 나는 기능을 추가해서 게임을 차별화해야할 경우 

    ⚠️ 경고!

    몇몇 서브 클래스만 날아야하는데, 적합하지 않은 일부 클래스에 나는 행동이 추가됨(ex: 고무 오리들이 날아다님)

     

    상위클래스에 행동을 추가할 경우 코드를 재사용하는 점에서는 상속을 활용한 것이지만 유지보수를 생각하면 좋은 생각이아니다.

     

    상속생각하기

     


    quack() 

    fly()

    같은 행동을 상위클래스에 두고 하위클래스를 상속받을 경우 위의 행동이 필요없는 가짜오리의 경우 필요없는 함수를 

    오버라이드 해야한다.(로직은 아무것도 들어가지 않는..)

     

    👎위와같이 상속할 때 단점이 될 수 있는 요소

    • 서브 클래스에서 코드가 중복됨
    • 모든 오리의 행동을 알기 힘듬
    • 실행시 특징을 바꾸기 힘듬 
    • 코드 변경했을 때 다른 오리들에게 영향을 끼칠 수 있음 

     

    인터페이스 설계하기


    앞으로 계속 Duck의 규격이 바뀔 경우 상속을 사용하면 바뀔 때 마다 Duck의 서브클래스 fly(), quack() 메소드를 

    살펴본 후 상황에 따라 오버라이드 해야한다. 영원히

     

    인터페이스를 만드는 해결방법

    fly()를 Duck 슈퍼 클래스에서 뺀 뒤 Flyable 인터페이스를 만들고 날 수 있는 오리들만 이 인터페이스를 구현

    quack() 메소드도 Quackable 인터페이스를 만들어 꽥꽥소리 내는 오리들만 인터페이스를 구현 

     

     😡 문제점

    코드 중복이 발생 -> 나는 오리가 많아질 경우 같은 코드를 반복해서 구현해야함 

    날아가는 동작 조금 바꾸기 위해서 Duck의 서브클래스에서 날아다닐 수 있는 모든 소스를 전부 고쳐야함(인터페이스를 구현한 모든 서브클래스)

    public abstract class Duck {
        public void swin() {System.out.println("swim..");}
    
        public abstract void disply();
    
    }
    public interface Flyable {
        void fly();
    }
    
    public interface Quackable {
        void quack();
    }
    public class MallardDuck extends Duck implements Flyable, Quackable{
    
        @Override
        public void disply() {
            System.out.println("청둥오리에요");
        }
    
        @Override
        public void quack() {
            System.out.println("Quack Quack");
        }
    
        @Override
        public void fly() {
            System.out.println("하늘을 날아요!");
        }
        
    }
    
    public class ReadheadDuck extends Duck implements Flyable, Quackable{
        @Override
        public void disply() {
            System.out.println("빨간머리 오리에요");
        }
    
        @Override
        public void quack() {
            System.out.println("Quack Quack");
        }
    
        @Override
        public void fly() {
            System.out.println("하늘을 날아요!");
        }
    }

     

    MallardDuck과 ReadheadDuck은 둘 다 꽥꽥 거릴 수 있고 날 수 있는데 코드를 반복해서 넣어줘야 한다.

    만약 오리 울음 소리가 바뀌면 코드를 일일이 찾아다니면서 바꿔주어야 한다.

     

    문제 명확히 파악하기


    ☝🏻 디자인 원칙 

    애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분과 분리한다.

    달라지는 부분을 찾아서 나머지 코드에 영향을 주지 않도록 '캡슐화'한다.

     

    바뀌는 부분은 따로 뽑아서 캡슐화하면 바뀌지 않는 부분에는 영향을 미치지 않고 그 부분만 고치거나 확장할 수 있다.

     

     

     

    바뀐부분과 그렇지 않은 부분 분리하기


    변화하는 부분과 그대로있는 부분을 분리하려면 

    Duck클래스 (fly(), quack()을 제외하면 잘동작하고 있음) 는 그대로 두고 

    2개의 클래스 집합을 만들어야한다.(나는것, 꽥꽥거리는 것)

     

     

    오리의 행동을 디자인하는 방법


    ☝🏻 디자인 원칙 

    구현보다는 인터페이스에 맞춰서 프로그래밍한다.

    나는 행동, 꽥꽥거리는 행동은 Duck에서 구현하지 않고 행동만을 목적으로 하는 클래스 집합을 만든다. 

    이전 방법은 특정구현에 의존했다면 새로운 디자인은 Duck서브클래스는 인터페이스(FlyBehavior나 QuackBehavior)로 

    표현되는 행동을 사용한다. 

    실제 행동 구현(FlyBehavior나 QuackBehavior를 구현하는 클래스에 코딩되어있는 구체적인 행동)은 

    Duck서브클래스에 국한되지 않는다.

     

     

     

    인터페이스에 맞춰서 프로그래밍한다.

    위와 같이 디자인하면 다른 형식의 객체에서도 나는 행동과 꽥꽥거리는 행동을 재사용할 수 있다.

    기존의 행동 클래스를 수정하거나 날아다니는 행동을 사용하는 Duck클래스를 전혀 건드리지 않고도 

    새로운 행동을 추가할 수 있다. 

     

     

    오리코드 테스트


    public abstract class Duck {
    
        FlyBehavior flyBehavior;
        QuackBehavior quackBehavior;
        
        public Duck() {}
    
        public abstract void disply();
    
        public void performFly() {
            flyBehavior.fly();
        }
    
        public void performQuack() {
            quackBehavior.quack();
        }
    
        public void swim() {
            System.out.println("모든 오리는 물에 떠요.");
        }
    
    }
    ----------------------------------------
    
    public class MallardDuck extends Duck {
    
        public MallardDuck() {
            quackBehavior = new Quack();
            flyBehavior = new FlyWithWings();
        }
    
        @Override
        public void disply() {
            System.out.println("청둥오리에요");
        }
    
    }

     

    public interface FlyBehavior {
        void fly();
    }
    ---------------------------------------------------
    public class FlyWithWings implements FlyBehavior {
    
        @Override
        public void fly() {
            System.out.println("날고 있어요!");
        }
        
    }
    ----------------------------------------------------
    public class FlyNoWay implements FlyBehavior{
    
        @Override
        public void fly() {
            System.out.println("저는 못 날아요");
        }
        
    }
    public interface QuackBehavior {
        void quack();
    }
    
    ----------------------------------------------------
    public class Quack implements QuackBehavior{
    
        @Override
        public void quack() {
            System.out.println("꽥");
        }
        
    }
    
    ----------------------------------------------------
    public class MuteQuack implements QuackBehavior{
    
        @Override
        public void quack() {
            System.out.println("<<~조용~>>");
        }
        
    }
    -----------------------------------------------------
    public class Squeak implements QuackBehavior{
    
        @Override
        public void quack() {
            System.out.println("삑");
        }
        
    }
    public class MiniDuckSimulator {
    
        public static void main(String[] args) {
            Duck mallard = new MallardDuck();
            mallard.performFly();
            mallard.performQuack();
        }
        
    }
    ---------------------------------------------------------------
    날고 있어요!
    꽥

     

     

    동적으로 행동 지정하기


     public void setFlyBehavior(FlyBehavior fb) {
        flyBehavior = fb;
    }
    
    public void setQuackBehavior(QuackBehavior qb) {
        quackBehavior = qb;
    }

    위의 두 메소드를 호출하면 언제든지 오리 행동을 즉석에서 바꿀 수 있다.

    public class ModelDuck extends Duck {
    
        public ModelDuck() {
            flyBehavior = new FlyNoWay();
            quackBehavior = new Quack();
        }
    
        @Override
        public void disply() {
            System.out.println("저는 모형 오리에요.");
        }
    }

    모형오리 서브클래스를 만든다.

    public class FlyRocketPowered implements FlyBehavior{
        public void fly() {
            System.out.println("로켓 추진으로 날아가요.");
        }
    }

    로켓 추진으로 날아가는 클래스를 만든다.

    public class MiniDuckSimulator {
    
        public static void main(String[] args) {
            Duck model = new ModelDuck();
            model.performFly();
            model.setFlyBehavior(new FlyRocketPowered());
            model.performFly();
        }
        
    }
    
    
    -----------------------------------------------------
    저는 못 날아요
    로켓 추진으로 날아가요.

    상속받은 세터 메서드가 호출 되면 로켓추진력으로 나는 능력으로 바뀐다.

     

     

    두 클래스를 합치는 방법


    ☝🏻 디자인 원칙 

    상속보다는 구성을 활용한다.

     

    오리의 FlyBehavior와 QuackBehavior가 각각의 행동을 위임받는다.

    이렇게 두 클래스를 합치는 것을 구성이라고 한다.

    오리클래스에서는 행동을 상속받지 않고 올바른 행동객체로 구성되어 있는 행동을 부여받는다.

     

     

    전략패턴 다시 정리

    알고리즘군을 정의하고 캡슐화해서 각각의 알고리즘을 수정해서 쓸 수 있게 해주는 전략.

    클라이언트로부터 알고리즘을 분리해서 독립적으로 변경가능.

    728x90
    반응형