ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [디자인패턴] 팩토리 메소드 패턴 (Factory-Method Pattern)
    IT, 프로그래밍/Design Patterns 2017. 12. 10. 02:22



    팩토리 메서드 패턴(Factory method pattern)은 객체지향 디자인 패턴이다. Factory method는 부모(상위) 클래스에 알려지지 않은 구체 클래스를 생성하는 패턴이며. 자식(하위) 클래스가 어떤 객체를 생성할지를 결정하도록 하는 패턴이기도 하다. 부모(상위) 클래스 코드에 구체 클래스 이름을 감추기 위한 방법으로도 사용한다.




    팩토리 패턴을 시작하기 전에..


    "new" 라는 것은 구상 객체를 뜻합니다. 구상 객체는 구상 클래스의 인스턴스를 만드는 것이고 공통적인 인터페이스가 아닌 특정 구현을 사용하는 것이죠. 이렇게 구상 클래스를 통해서 객체를 구현 하게 되면 나중에 수정해야 할 상황이 닥치면 모든 구상 클래스를 확인해서 바꾸어야 하는 불상사가 생길 수 있습니다. 


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    Footwear footwear;
        
        if(daily) {
     
            footwear = new RunningShoes();
            
        }else if(business) {
            
            footwear = new Shoes();
            
        }else if(soccer) {
            
            footwear = new FootballShoes();
        }
    cs


    이런 코드가 있다는 것은 나중에 이 신발들에 대해서 무언가 변화가 생기면 코드를 다시 확인하고 추가하거나 제거 해야 합니다. 


    만약 코드를 이런식으로 만든다면 변화가 있을 때 마다 수정을 위해서 여기에 들락 날락 거려야 겠죠. (이 라인에 따로 주석처리를 하거나 todo 등을 통한, 아주 귀찮고 비효율적인 방식으로 말입니다) 


    인터페이스에 맞춰서 코딩을 한다면, 다형성 덕분에 시스템에서 일어날 수 있는 여러 변화에 대처할 수 있게 됩니다. 왜냐하면 구현 해야 하는 클래스에 implements를 해주면 사용할 수 있게 되기 때문이죠. 


    근데, 이 말은 곧 코드에서 구상 클래스 (여기서는 인터페이스를 구현 하는 클래스)들이 많아지면 새로운 구상 클래스를 추가할 때 마다 코드를 고쳐야 하기 때문에 많은 문제가 발생 하게 됩니다.


    --> 변화에 대해 닫혀 있는 코드가 되는 것! 

    (확장에 대해서는 열려 있고 변경에 대해서는 닫혀 있어야 함)



    팩토리 패턴 본격 시작하기




    우리가 수제화 매장을 운영하고 있다고 생각 해 봅시다.


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    Shoes orderShoes(String name) {
         
        Shoes shoes;
        //바뀔 수 있는 부분 (if문)
        
        if (name.equals("blackShoes")) {
     
            shoes = new BlackShoes();
     
        } else if (name.equals("brownShoes")) {
     
            shoes = new BrownShoes();
     
        } else if (name.equals("redShoes")) {
     
            shoes = new RedShoes();
        }
     
        //바뀌지 않는 부분
        shoes.prepare(); // 신발을 준비하고
        shoes.packing(); // 종이 가방에 넣음
     
        return shoes;
    }
    cs



    현재 이 매장에는 3개의 신발 밖에 팔고 있지 않지만, 앞으로 판매되는 제품이 늘어나거나 지금 있는 제품이 더이상 판매 되지 않을 수 있습니다. 언제든 변경이 가능 한 부분 이라는 것이죠.


    그러나 밑에 있는 prepare()과 packing() 메소드는 앞으로도 거의 변하지 않을 겁니다. 이제 변하는 부분을 캡슐화 해 봅니다.


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class ShoesFactory {
     
        public Shoes makingShoes(String name) {
     
           Shoes shoes = null;
     
        if (name.equals("blackShoes")) {
     
                shoes = new BlackShoes();
     
        } else if (name.equals("brownShoes")) {
     
                  shoes = new BrownShoes();
     
        } else if (name.equals("redShoes")) {
     
                  shoes = new RedShoes();
        }
            return shoes;
     
        }
     
    }
     
    cs



    이렇게 팩토리로 분리하면,


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class ShoesStore {
         
        ShoesFactory factory;
     
        //생성자
        public ShoesStore(ShoesFactory factory) {
            this.factory = factory;
        }
       
        //주문하기
        Shoes orderShoes(String name) {
     
            Shoes shoes;
     
            shoes = factory.makingShoes(name)
            
            shoes.prepare(); // 신발을 준비하고
            shoes.packing(); // 종이 가방에 넣음
     
            return shoes;
        }
    }
    cs



    고객에게 주문이 들어 왔을 때 공장에 오더를 넣어서 받기만 하면 되는 것이죠! 판매 되는 신발에 변화가 생겼을때는 공장에서 처리하면 됩니다.


    위에서 살펴본 코드는, 디자인 패턴이라고 할 수 없고 프로그래밍에 사용하는 관용구에 가깝습니다. 


    이제 워밍업이 끝났으니 본격적인 팩토리 메소드 패턴에 대해서 알아 보겠습니다. 



    우리가 운영하는 수제화 가게는, 유명세를 타고 다른 나라로 진출하기 시작했습니다. 


    일본과 프랑스에도 진출을 하고, 매장을 지었습니다.



    1
    2
    3
    4
    5
        JapanShoesStore jpStore= new JapanShoeStore (new JapanShoesFactory());
        jpStore.order("blackShoes");
        
        FranceShoesStore  frStore = new FranceShoesStore (new FranceShoesFactory());
        frStore.order("blackShoes");
    cs


    그런데 이 매장들을 운영하다 보니, 각자 본사에서 준 가이드라인 대로 만들기는 하는데, 일본에서는 구두 옆에 용이 그려진 자수를 넣고, 프랑스에는 포장지를 다른 것을 쓰기 시작했습니다.


    많은 회의 끝에 본사는, 매장과 구두 생산 과정 전체를 묶어주는 프레임 워크를 만들기로 결정 하였습니다.



    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public abstract class ShoesStore {
     
        public ShoesStore orderShoes(String name) {
     
            Shoes shoes;
     
            shoes = makeShoes(name);
     
            shoes.prepare();
            shoes.packing();
     
            return shoes;
     
        }
     
        abstract Shoes makeShoes(String name);
     
    }
    cs

     

    ShoesStore 추상 클래스를 선언합니다. 이것으로 모든 분점들에 똑같은 시스템이 진행 될 수 있습니다. 달라지는 부분은 신발의 스타일 뿐입니다.


    일본 사람들은 상대적으로 발이 작고 깔끔한 구두를 선호합니다. 그에 비해 프랑스 사람들은 발이 크고 패턴이 있는 구두를 좋아 하죠. ( 현실에서는 아닙니다. 제가 예시를 위해 든 것 뿐입니다 )


    그것을 어디에서 구현해 줄까요? 바로 이 클래스를 상속 받는 서브 클래스에서, 추상 메소드로 선언한 makeShoes 메소드를 오버라이드 하여 그 작업을 구현합니다.


    이 메소드가, 신발을 만드는 것을 책임 지게 됩니다.


    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
     
    class JapanShoesStore extends ShoesStore {
     
        @Override
        Shoes makeShoes(String name) {
            // TODO Auto-generated method stub
            if (name.equals("blackShoes")) {
                return new JPStyleBlackShoes();
     
            } else if (name.equals("brownShoes")) {
     
                return new JPStyleBrownShoes();
     
            } else if (name.equals("redShoes")) {
     
                return new JPStyleRedShoes();
     
            } else {
     
                return null;
            }    }
     
        
     
    }
     
    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
    class FranceShoesStore extends ShoesStore {
     
        @Override
        Shoes makeShoes(String name) {
            // TODO Auto-generated method stub
     
            if (name.equals("blackShoes")) {
                return new FRStyleBlackShoes();
     
            } else if (name.equals("brownShoes")) {
     
                return new FRStyleBrownShoes();
     
            } else if (name.equals("redShoes")) {
     
                return new FRStyleRedShoes();
     
            } else {
     
                return null;
            }
     
        }
     
        
    }
    cs



    프랑스 신발 매장 클래스


    중요한 점은, 슈퍼클래스에 있는 orderShoes 메소드에서는 어떤 신발이 만들어 지는지 전혀 모르고 있다는 것입니다. 그 메소드에서는 주는 신발을 받아서 준비하고 포장할 뿐이죠.




    이제 구두를 만들어 볼까요?


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    abstract class Shoes {
     
        String name;
        String bottom;
        String leather;
        boolean hasPattern;
     
        void prepare() {
     
            System.out.println("완성된 신발을 준비 중 입니다..");
        }
     
        void packing() {
     
            System.out.println("신발을 포장 하고 있습니다..");
        }
     
        public String getName() {
     
            return name;
        }
     
    }
    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
    class JPStyleBlackShoes extends Shoes {
     
        public JPStyleBlackShoes() {
            // TODO Auto-generated constructor stub
     
            name = "일본 스타일의 검은 구두";
            bottom = "검은색 고무 밑창";
            leather = "소가죽";
            hasPattern = false;
     
        }
     
    }
     
    class FRStyleBlackShoes extends Shoes {
     
        public FRStyleBlackShoes() {
            // TODO Auto-generated constructor stub
     
            name = "프랑스 스타일의 검은 구두";
            bottom = "옅은 검은색의 플라스틱과 고무 혼용";
            leather = "양가죽";
            hasPattern = true;
     
     
        }
     
    }
    cs



    이제 메인 클래스를 만들어서 테스트 신발을 주문 해 봅시다.


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class ShoesTest {
     
        public static void main(String[] args) {
     
            ShoesStore jpStore = new JapanShoesStore();
            ShoesStore frStore = new FranceShoesStore();
     
            Shoes shoes = jpStore.orderShoes("blackShoes");
            System.out.println("일본 매장에서 산 구두는 ? --> " + shoes.getName());
            System.out.println();
     
            shoes = frStore.orderShoes("blackShoes");
            System.out.println("프랑스 매장에서 산 구두는 ? --> " + shoes.getName());
     
        }
     
    }
    cs


    신발을 어떻게 주문 하는 지 한 번 살펴봅시다.


    1) 일본에 놀러간 민수는 수제화를 맞추기로 하고 매장에 방문합니다. 

    (민수는 JPShoesStore의 인스턴스를 생성 해야 합니다)


    2) 가게를 찾았으면 주문을 합니다. 어떤 구두를 살 것인지 말해줍니다.

    (생성한 인스턴스를 통해 orderShoes 메소드를 호출합니다)


    3) 직원이 민수의 발 정보를 재고, 신발을 만드는 작업자에게 주문을 전달합니다.

    (orderShoes 메소드에서 makeShoes 메소드를 호출 합니다.)


    4) 완성이 되었지만 아직 포장 되지 않은 신발을 받아서, 깨끗하게 포장하여 민수에게 건내줍니다.

    (prepare 메소드와 packing 메소드를 호출합니다.)



    다이어그램으로 보면, 이런 형식으로 표현이 됩니다.



    생산자 (Creator) 클래스



    제품 (Product) 클래스




    위에서 살펴 볼 수 있다시피, 하위 클래스에서 어떤 클래스를 만들지 결정하게 합니다. 상위 클래스는 어떤 제품이 만들어 지는지 전혀 알지 못하죠. 아무런 지식이 없습니다. 이것 때문에 "결정한다" 라는 단어로 표현을 합니다. 


    정확히는, 사용하는 서브클래스에 따라 생산되는 객체 인스턴스가 결정 할 것 입니다.




    -- 예제 전체 작동 코드 --



    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
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
     
    public class ShoesTest {
     
        public static void main(String[] args) {
     
            ShoesStore jpStore = new JapanShoesStore();
            ShoesStore frStore = new FranceShoesStore();
     
            Shoes shoes = jpStore.orderShoes("blackShoes");
            System.out.println("일본 매장에서 산 구두는 ? --> " + shoes.getName());
            System.out.println();
     
            shoes = frStore.orderShoes("blackShoes");
            System.out.println("프랑스 매장에서 산 구두는 ? --> " + shoes.getName());
     
        }
     
    }
     
     
    /*-------------- 신발 매장 추상 클래스 ---------------*/
    abstract class ShoesStore {
     
        public Shoes orderShoes(String name) {
     
            Shoes shoes;
     
            shoes = makeShoes(name);
     
            shoes.prepare();
            shoes.packing();
     
            return shoes;
     
        }
     
        abstract Shoes makeShoes(String name);
     
    }
     
     
    /*-------------- 구두 추상 클래스 ---------------*/
     
    abstract class Shoes {
     
        String name;
        String bottom;
        String leather;
        boolean hasPattern;
     
        void prepare() {
     
            System.out.println("완성된 신발을 준비 중 입니다..");
        }
     
        void packing() {
     
            System.out.println("신발을 포장 하고 있습니다..");
        }
     
        public String getName() {
     
            return name;
        }
     
    }
     
     
    /*-------------- 구체화 시킨 확장 매장 클래스 ---------------*/
     
     
    class JapanShoesStore extends ShoesStore {
     
        @Override
        Shoes makeShoes(String name) {
            // TODO Auto-generated method stub
            if (name.equals("blackShoes")) {
                return new JPStyleBlackShoes();
     
            } else if (name.equals("brownShoes")) {
     
                return new JPStyleBrownShoes();
     
            } else if (name.equals("redShoes")) {
     
                return new JPStyleRedShoes();
     
            } else {
     
                return null;
            }    }
     
        
     
    }
     
    class FranceShoesStore extends ShoesStore {
     
        @Override
        Shoes makeShoes(String name) {
            // TODO Auto-generated method stub
     
            if (name.equals("blackShoes")) {
                return new FRStyleBlackShoes();
     
            } else if (name.equals("brownShoes")) {
     
                return new FRStyleBrownShoes();
     
            } else if (name.equals("redShoes")) {
     
                return new FRStyleRedShoes();
     
            } else {
     
                return null;
            }
     
        }
     
        
    }
     
     
    /*-------------- 구체화 시킨 구두 클래스 ---------------*/
    class JPStyleBlackShoes extends Shoes {
     
        public JPStyleBlackShoes() {
            // TODO Auto-generated constructor stub
     
            name = "일본 스타일의 검은 구두";
            bottom = "검은색 고무 밑창";
            leather = "소가죽";
            hasPattern = false;
     
        }
     
    }
     
    class FRStyleBlackShoes extends Shoes {
     
        public FRStyleBlackShoes() {
            // TODO Auto-generated constructor stub
     
            name = "프랑스 스타일의 검은 구두";
            bottom = "옅은 검은색의 플라스틱과 고무 혼용";
            leather = "양가죽";
            hasPattern = true;
     
     
        }
     
    }
     
    class JPStyleBrownShoes extends Shoes {
     
        public JPStyleBrownShoes() {
            // TODO Auto-generated constructor stub
     
            name = "일본 스타일의 갈색 구두";
            bottom = "진 갈색 고무 밑창";
            leather = "소가죽";
            hasPattern = false;
     
     
        }
     
    }
     
    class FRStyleBrownShoes extends Shoes {
     
        public FRStyleBrownShoes() {
            // TODO Auto-generated constructor stub
     
            name = "프랑스 스타일의 검은 구두";
            bottom = "밝은 갈색에 플라스틱과 고무 혼용";
            leather = "양가죽";
            hasPattern = true;
     
     
        }
     
    }
     
    class JPStyleRedShoes extends Shoes {
     
        public JPStyleRedShoes() {
            // TODO Auto-generated constructor stub
     
            name = "일본 스타일의 빨간색과 와인색 중간의 구두";
            bottom = "와인색 고무 밑창";
            leather = "소가죽";
            hasPattern = false;
     
     
        }
     
    }
     
    class FRStyleRedShoes extends Shoes {
     
        public FRStyleRedShoes() {
            // TODO Auto-generated constructor stub
     
            name = "프랑스 스타일의 버건디 색에 가까운 구두";
            bottom = "빨간 검은색의 플라스틱과 고무 혼용";
            leather = "양가죽";
            hasPattern = true;
     
     
        }
     
    }
    cs





Designed by Tistory.