ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [디자인패턴] 스트래티지 패턴 (Strategy Pattern)
    IT, 프로그래밍/Design Patterns 2017. 9. 23. 01:25

    스트래티지 패턴 (Strategy Pattern) = 알고리즘군을 정의하고 각각을 캡슐화하여 사용할 수 있도록 만든다. 스트래티지를 활용하면 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경 할 수 있다.


    상당히 설명이 어렵습니다. 처음 듣는 사람은 한 번에 이해 하기가 어려운 설명입니다. 위키 백과에서 한 번 볼까요? 


    In computer programming, the strategy pattern (also known as the policy pattern) is a behavioural software design pattern that enables selecting an algorithm at runtime. The strategy pattern

    • defines a family of algorithms,
    • encapsulates each algorithm, and
    • makes the algorithms interchangeable within that family.

    Strategy lets the algorithm vary independently from clients that use it.[1] Strategy is one of the patterns included in the influential book Design Patterns by Gamma et al. that popularized the concept of using design patterns to describe how to design flexible and reusable object-oriented software.


    조금 더 자세하게 나와있습니다. 위키 백과에서는 아주 심플하게 나와있네요.


    '알고리즘을 런 타임 중에 선택할 수 있는' 이라는 말로 정의를 했습니다.


    여기서 제 나름대로 풀어서 해석해 보자면, 


    1. 알고리즘군은 어떤 일을 해야 하는지를 정의해 놓은, 즉 연관성 있는 기능들을 모아놓은 그룹 입니다. 이 그룹을 정의 해야 한다고 합니다. 
    2. 각각의 알고리즘을 캡슐화 시킵니다. 캡슐화 한다는 건 클래스로 만들어서 관리한다는 것이구요.
    3. 그리고 그 알고리즘들은 그룹들 내에서 서로 바꿀 수 있게 한다고 합니다.
    알고리즘군은, 비슷한 알고리즘을 모아놓은 그룹입니다. 예를 들면 걷기(walk) 라는 알고리즘이 있습니다. 발로 어떠한 방향으로 걷는 행위라는 공통적인 기능 아래  팔을 흔들면서 걷기, 뒤로 걷기, 발 보폭을 좁게 해서 걷기, 발 끝을 들고 걷기 등 아주 다양한 걷는 행동 들이 있습니다. 이것을 모으면 '걷기' 알고리즘 군이라고 할 수 있겠습니다. 각각의 걷는 행위들은 공통적인 행위인 '걷기' 인터페이스로 묶여져 있구요.


    민수 라는 객체를 하나 정의 했다고 합시다. 이 민수라는 객체는 원래는 팔을 흔들면서 걸을 수 있었습니다. 그런데 하루는 이렇게 걷다 보니 너무 팔이 아파서 팔을 흔들지 않으면서 걷기로 했습니다. 


    이때, 클래스화 된 알고리즘들은 인터페이스로 묶여있고 그것은 곧 공통적으로 구현하고 있는 인터페이스인 '걷기'를 동적 바인딩(Dynamic Binding)을 사용해 쉽고 편리하게 다른 알고리즘으로 교체할 수 있습니다.


    이제부터는 예시를 통해서 좀 더 자세하게 설명하겠습니다.


    설명의 주제는 한국인이 가장 사랑한다는 치킨입니다.




    치킨 회사인 OO치킨사는 고객들이 쉽게 치킨의 정보를 알 수 있게 하기 위해서 치킨 정보 프로그램을 구축하였습니다.


    이 프로그램은 치킨 메뉴 번호를 선택하면, 그에 맞는 치킨의 정보를 보여줍니다.


    보여주는 치킨의 정보는 조리법, 맛, 치킨의 모습, 들어간 닭 마리 수 입니다.


    알아둘점은, OO치킨은 사람들이 제일 좋아하는 3가지 메뉴인 후라이드 치킨, 양념치킨, 갈릭 치킨을 판매합니다. 또한 모든 치킨을 튀기는 시간은 10분 30초 입니다.




    이 프로그램을 처음 개발할 때는 상속을 통해서 개발을 하였습니다.


    그런데 어느 날, OO치킨사에서 새로운 메뉴를 출시합니다.


    "우리 OO치킨은 고객들의 기호의 변화에 맞춰 XX치킨사 처럼 구운 치킨도 만들어서 제공할 것입니다!"


    이 프로그램의 유지 및 보수 업무를 맡은 개발자 A씨는 큰 고민에 빠졌습니다.


    왜냐하면 상속을 통해서 구현하였기 때문에 모든 치킨은 공통의 성질을 가지고 있어야 하는데, 그것이 깨진것입니다! (부모 클래스에 굽는다는 메소드가 추가한다면, 모든 치킨은 구워도 지고 튀겨도 진다는 해괴망측한 결론이 나옵니다.)


    A씨는 생각해보니, 앞으로 더 다양한 치킨들이 나올 것이라는 생각이 들었습니다. (XX치킨은 훈제 치킨까지 만들어서 팔고 있습니다.) 그래서 인터페이스로 알고리즘을 분리해서 각각 구현시키기로 하였습니다.



    이로써 문제는 해결된 것 처럼 보이지만, 결국 조리법을 객체마다 일일히 다 적어줘야 하며 이는 코드의 중복으로 이어 집니다. (상대적으로 튀겨진 치킨이 훨씬 많겠죠?) 


    또한 튀겨지는 방법이 약간이라도 달라진다면 (30초가 추가된다면?) 인터페이스를 구현한 모든 객체로 들어가서 또 다시 메소드를 수정 해야 합니다.


    지금은 객체가 몇 개 없지만, 차후에 OO치킨이 성장을 해서 많은 가짓수의 치킨을 판매한다면, 이것은 큰 문제가 될 것 입니다.


    A씨는 많은 시간동안 고민하고, 구글을 전전하면서 좋은 아이디어를 얻었습니다.


    지금 달라지는 부분은 조리법(Fried) 부분이므로, 이 부분을 나눠서, 클래스로 캡슐화 하여 구현하는 것입니다.


    여기서 중요한 건, 달라지는 부분을 골라내어 캡슐화 한다는 부분입니다.


    위에서 언급한 알고리즘군을 정의 하는 것이죠. 이렇게 하면 나중에 수정 사항이 생기면 이 클래스에 접근해서 해당하는 메소드만 바꿔 주면 됩니다.


    이제 동적 바인딩을 사용하기 위해 Chicken 클래스에 인터페이스 변수를 추가 합니다. 그리고 이 cooking() 이라는 메소드를 하나 만들어, Fried 인터페이스에 구현 되는 객체가 메소드를 실행 시킬 수 있도록 위임(delegate) 합니다. 



    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public abstract class Chicken {
     
        Fried fried;
        
        public abstract void flavor();
        public abstract void  display();
        public void number() {
            System.out.println("한마리");        
        };
        public void cooking() {fried.fried();}
        
        public void setFried(Fried fried) {
            this.fried = fried;
        }
        
            
    }
     
    cs


    Chicken 클래스


    1
    2
    3
    4
    5
    6
    public interface Fried {
        
        public void fried();
     
    }
     
    cs


    Fried 인터페이스


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class CookFried implements Fried {
     
        @Override
        public void fried() {
            // TODO Auto-generated method stub
            
            System.out.println("튀겨짐");
            
        }
     
        
    }
     
    cs


    CookFried 클래스


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class CookGrill implements Fried {
     
        @Override
        public void fried() {
            // TODO Auto-generated method stub
            
            System.out.println("구워짐");
     
        }
     
    }
     
    cs


    CookGrill 클래스


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class FriedChicken extends Chicken {
     
        public FriedChicken() {
            
            fried = new CookFried();
            
        }
        
        @Override
        public void flavor() {
            // TODO Auto-generated method stub
            
            System.out.println("고소한 일반 후라이드 치킨맛");
            
        }
     
        @Override
        public void display() {
            // TODO Auto-generated method stub
            
            System.out.println("노란 튀김옷을 입은 김이 모락모락 나는 치킨");
            
        }
     
    }
    cs


    후라이드 치킨 클래스


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public class SpicyChicken extends Chicken {
     
        public SpicyChicken() {
            
            fried = new CookFried();
            
        }
        
        @Override
        public void flavor() {
            // TODO Auto-generated method stub
            System.out.println("매콤하면서 달콤한 맛");
     
        }
     
        @Override
        public void display() {
            // TODO Auto-generated method stub
            System.out.println("빨갛고 매워보이는 모습");
     
            
        }
     
        
    }
     
    cs


    양념 치킨 클래스


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    public class GarlicChicken extends Chicken {
     
        public GarlicChicken() {
            
            fried = new CookFried();
            
        }
        
        @Override
        public void flavor() {
            // TODO Auto-generated method stub
            System.out.println("마늘향이 나는 치킨");
     
        }
     
        @Override
        public void display() {
            // TODO Auto-generated method stub
            System.out.println("갈색에 아몬드가 뿌려진 치킨");
     
            
        }
     
        
        
    }
     
    cs


    갈릭 치킨 클래스


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    public class OvenChicken extends Chicken {
        
        public OvenChicken(){
            
            fried = new CookGrill();
        }
     
        @Override
        public void flavor() {
            // TODO Auto-generated method stub
            
            System.out.println("바베큐 향이 가득한 맛");
            
        }
     
        @Override
        public void display() {
            // TODO Auto-generated method stub
            
            System.out.println("격자 무늬로 구워진 자국이 보이는 먹음직 스러운 모습");
            
        }
        
        
     
    }
     
    cs


    오븐 치킨 클래스 (구워진 치킨)


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
     
    import java.util.Scanner;
     
    public class ChickenInfoEngine {
     
        Chicken chicken;
        int token;
        String exitToken;
     
        public void chickenChooser() {
     
            Scanner sc = new Scanner(System.in);
     
            System.out.println("치킨 정보 시스템");
            System.out.println("---------------------");
            System.out.println("");
     
            do {
                System.out.println("원하는 치킨의 정보를 입력하세요.");
                System.out.println("메뉴 : 1)후라이드 치킨    2)마늘 치킨  3)양념 치킨    4)오븐 치킨");
                token = sc.nextInt();
     
                switch (token) {
     
                case 1:
     
                    chicken = new FriedChicken();
                    break;
     
                case 2:
     
                    chicken = new GarlicChicken();
                    break;
     
                case 3:
     
                    chicken = new SpicyChicken();
                    break;
     
                case 4:
                    chicken = new OvenChicken();
                    break;
     
                }
     
                chicken.cooking();
                chicken.flavor();
                chicken.display();
                chicken.number();
                System.out.println("");
                System.out.println("------------------");
                System.out.println("");
                System.out.println("계속 진행하시겠습니까? y/N");
                exitToken = sc.next();
     
            } while (exitToken.equals("y"));
            
            System.out.println("종료되었습니다.");
     
        }
     
    }
     
    cs


    ChickenInfoEngine 클래스 (조건문을 실행)


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    public class ChickenInformation {
     
        public static void main(String[] args) {
     
            ChickenInfoEngine chick = new ChickenInfoEngine();
            chick.chickenChooser();
     
        }
     
    }
     
    cs


    ChickenInformation 클래스 ( 메인 ) 




    후라이드 치킨, 양념 치킨, 갈릭 치킨, 오븐 치킨 의 생성자에 보시면 상속 받고 있는 부모 클래스인 Chicken에 있는 인터페이스 변수인 Fried에 각각 CookFried와 CookGrill 클래스를 초기화 시키고 있습니다. 동적 바인딩을 이용한 부분이죠. 이것은 CookFried와 CookGrill 모두 Fried 인터페이스를 구현 했기에 가능한 부분입니다.


    실행 결과를 한 번 보겠습니다.








    결과가 아주 잘 나오는 것을 확인할 수 있습니다. 그렇다면, 아까 Chicken 클래스에 만든 인터페이스를 set 하는 메소드를 사용해 볼까요? 



    set 메소드를 사용해서 조리법을 튀기는 걸로 바꾸도록 하였습니다. 결과는요?



    오븐 치킨의 조리법이 바뀐 것을 확인할 수 있습니다.


    최종적으로 이러한 UML 다이어그램이 나오게 됩니다. 





    디자인 원칙 


    • 애플리케이션에서 달라지는 부분을 찾아 내고, 달라지지 않는 부분으로 부터 분리 시킨다. (= 바뀌는 부분은 따로 뽑아서 캡슐화 시킨다. 그렇게 하면 나중에 바뀌지 않는 부분에는 영향을 미치지 않은 채로 그 부분만 고치거나 확장할 수 있다.)

    • 구현이 아닌 인터페이스에 맞춰서 프로그래밍 한다. (=상위 형식에 맞춰서 프로그래밍 하여 다형성을 활용한다. )

    • 상속 보다는 구성을 활용한다. ("A는 B이다" 보다 "A에는 B가 있다"가 나을 수도 있다/ 인터페이스에 행동을 위임시켜서 두 클래스를 합치는 방식을 구성 이라고 한다.)



    참고자료 : 헤드퍼스트 디자인 패턴

Designed by Tistory.