반응형

자바 디자인 패턴 6 - Strategy

1. Strategy 패턴은..

Template Method 패턴이 상속을 이용해서 어떤 구현을 했다면, Strategy 패턴은 구성을 이용합니다. Template Method와 마찬가지로 바뀌는 부분과 바뀌지 않는 부분을 나눠서 생각할 수 있습니다. Template Method가 하위 클래스에서 바뀌는 부분을 처리한다면 Starategy는 바뀌는 부분을 인터페이스로 분리하여 처리합니다. 그 인터페이스의 구현체를 바꿈으로서 로직을 변경하는 것입니다. 또 Template Method와 크게 다른 점은 Template Method에서는 외부로 공개되는 것이 Template Method를 가지고 있는 상위 클래스였지만, Strategy에서는 인터페이스를 사용하는 클래스(그 클래스를 Context라고 합니다.)입니다.

2. 예제

------------------------ 상위 인터페이스 --------------------
package ch06_Strategy;

public interface Seller {
    public void sell();
}

------------------------- 인터페이스 구현체1 -----------------
package ch06_Strategy;

public class CupSeller implements Seller {
    public void sell() {
        System.out.println("컵을 팔아요.");
    }
}
------------------------- 인터페이스 구현체2 -----------------
package ch06_Strategy;

public class PhoneSeller implements Seller {
    public void sell() {
        System.out.println("전화기를 팔아요.");
    }
}
------------------------- 인터페이스 사용하는 클래스 -----------------
package ch06_Strategy;

public class Mart {
    private Seller seller;
    public Mart(Seller seller) {
        this.seller = seller;
    }
    public void order(){
        seller.sell();
    }
}
------------------------- 테스트 클래스 -----------------
package ch06_Strategy;

public class Test {
    public static void main(String[] args) {
        Seller cupSeller = new CupSeller();
        Seller phoneSeller = new PhoneSeller();
        Mart mart1 = new Mart(cupSeller);
        mart1.order();
        Mart mart2 = new Mart(phoneSeller);
        mart2.order();
    }
}

위에서 보시다 시피 테스트 클래스에서는 Seller의 sell()을 호출하지 않습니다. Mart의 order()를 호출합니다. Seller의 메쏘드는 외부로 공개되지 않습니다. 
Mart 클래스가 여기서는 외부로 공개되는 Context가 됩니다. Mart는 멤버 변수로 Seller를 가집니다. Mart에서 가지는 Seller를 바꿔치기함으로써 Mart의 order()에서 실제 실행되는 로직이 달라질 수 있습니다.

3. Strategy의 유용성

예제에서는 Context 클래스가 한 개의 Strategy 인터페이스만을 가집니다. Seller 외에 여러가지 인터페이스를 가질 수도 있습니다. 예를 들어 만드는 사람, 운반하는 사람, 파는 사람은 각각 다를 수 있습니다. 예제에서는 코드를 줄이기 위해 파는 사람만 2가지 종류의 클래스를 만들었습니다. 그러나, 만드는 사람 인터페이스와 운반하는 사람 인터페이스 등을 만들고 그 구현체 들을 만들면, 상당히 다양한 로직이 나올 수 있습니다. 만드는 사람의 구현체가 3종류, 운반하는 사람의 구현체가 3종류, 파는 사람의 구현체가 3종류라하면, 만들어서 운반해서 파는 로직은 총 3*3*3= 27가지가 나옵니다. 이를 상속을 이용한 클래스를 제작하면, 27가지의 구현체가 필요합니다. Strategy를 쓰면, 9개의 구현체만 필요하며, 또 인터페이스를 이용한 프로그램이 가능합니다.


4. JAVA API에 있는 Strategy

java.util.Collections 에 sort(List<T> list, Comparator<? super T> c) 라는 메쏘드가 있습니다. List를 Comparator에서 지정한 방법으로 정렬하는 메쏘드입니다. Comparator는 compare(T o1, T o2) 메쏘드 하나만 있는 인터페이스 입니다. 이 인터페이스를 구현하는 방법에 따라서 정렬된 결과가 달라집니다. "101"이 먼저일까요, "11"이 먼저일까요? 일반적인 순서에서는 "101"이 먼저입니다. 그러나 이게 숫자라면, 정렬 방법이 달라져야 합니다. Comparator를 구현함으로써 해결할 수 있습니다.

출처 : http://iilii.egloos.com/3826810


반응형

'Design Pattern' 카테고리의 다른 글

abstract factory pattern  (0) 2013.07.09
facade패턴  (0) 2013.07.05
반응형

참고 및 출처 ::

 - Head First Design Pattern

 - http://www.gurubee.net/pages/viewpage.action?pageId=1507401

 - http://www.hoons.kr/Lecture/LectureMain.aspx?BoardIdx=50097&kind=62&view=0

 - http://donxu.tistory.com/89

 - http://goo.gl/zX5yO



* Abstract Factory Pattern (추상 팩토리 패턴)

 - 팩토리 : 객체 생성을 처리하는 클래스. 

 - 추상 팩토리 패턴의 본질은 "관련된 객체들의 집단(family)을 생성하는 인터페이스를 제공하되, 생성되는 객체의 구체적인

    클래스를 알 필요 없다는 것"이다.

 - Factory를 사용하는 Client가 많을 경우 여러 클래스에서 사용할 수 있다.

 - 생성할 클래스가 변하면 인터페이스 메서드 전체를 수정해야 하는 단점이 있으나 다수의 클래스가 존재하고 상황에 맞게

   적당한 클래스를 골라서 생성할 때 유용하다.

 - 객체 생성을 위한 메소드 실행을 위해 객체 인스턴스를 만들지 않아도 된다.


Abstract Factory Pattern(이하 추상 팩토리 패턴)은 공통의 테마를 가진 팩토리의 그룹을 캡슐화하는 방법을 제공하는 디자인

패턴이다. 일반적으로 클라이언트 소프트웨어는 추상 팩토리의 구체적인 구현체를 생성하고 그 구현체의 인터페이스를 사용한다.

클라이언트는 생성된 객체의 인터페이스만 사용하기 때문에 각각의 내부 팩토리로부터 얻는 구체적인 객체에 대해 알지 못한다.

이 패턴은 어떤 객체들의 그룹에 대한 구체적인 구현을 일반적인 사용법(인터페이스)으로부터 분리시킨다.




* 간단한 팩토리 정의

 간단한 피자 팩토리를 만들어 보자.


1. 일반 피자 팩토리

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
//==============================================
//피자 가게 운영을 위한 객체생성
//==============================================
Pizza orderPizza() {
 
    Pizza pizza = new Pizza();
 
    Pizza.prepare();
    Pizza.bake();
    Pizza.cut();
    Pizza.box();
 
    return pizza;
}
 
//==============================================
//여러 종류 피자를 만들기 위한 수정
//==============================================
Pizza orderPizza(String type){
    Pizza pizza;
   
    //피자 종류가 바뀔 때 마다 코드를 고쳐야함.
    if(type.equals("cheese")){
        pizza = new CheesePizza();
    }
    else if(type.equals("pepperoni")){
        pizza = new PepperoniPizza();
    }
    else if(type.equals("clam")){
        pizza = new ClamPizza();
    }
    else if(type.equals("veggie")){
        pizza = new VeggiePizza();
    }
 
    //피자 종류에 상관없이 바뀌지 않는 부분.
    Pizza.prepare();
    Pizza.bake();
    Pizza.cut();
    Pizza.box();
 
    return pizza;
}


2. 간단한 피자 피자 팩토리 (Simple Factory)

상황에 따라 코드가 변경되기 때문에 바뀌는 부분과 바뀌지 않는 부분을 분리한 후 캡슐화를 적용한다.
Simple Factory를 적용해서 위와 같은 문제점을 수정하자.
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
//==============================================
//피자 가게 객체 생성에 캡슐화 적용
//==============================================
Pizza orderPizza(String type){
    Pizza pizza;
   
    Pizza.prepare();
    Pizza.bake();
    Pizza.cut();
    Pizza.box();
 
    return pizza;
}
 
//==============================================
//피자를 만들기 위한 객체생성
//==============================================
public class SimplePizzaFactory{
 
    //클라이언트에서 새로운 객체의 인스턴스를 만들때 호출
    public Pizza createPizza (String type){
        Pizza pizza = null;
 
        //orderPizza() 메소드에서 뽑아낸 코드(피자의 종류를 결정)
        if(type.equals("cheese")){
            pizza = new CheesePizza();
        }
        else if(type.equals("pepperoni")){
            pizza = new PepperoniPizza();
        }
        else if(type.equals("clam")){
            pizza = new ClamPizza();
        }
        else if(type.equals("veggie")){
            pizza = new VeggiePizza();
        }
     
        return pizza;
    }
}


간단한 피자 팩토리의 장점은 아래와 같다.
 - Factory를 사용하는 클라이언트가 많을 경우, 여러 클래스에서 사용할 수 있다.
   (예를 들어, 피자를 생성하는 작업을 한 클래스에 캡슐화시켜 놓으면 구현을 변경해야하는 경우, 해당 팩토리 클래스 하나만 수정하면 된다.)

정적 팩토리(Static Factory)

 - 장점 : 객체 생성을 위한 메소드 실행을 위해 객체 인스턴스를 만들지 않아도 된다.

 - 단점 : 서브 클래스를 만들어 객체 생성 메소드의 행동을 변경 시킬 수 없다.

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
//==============================================
//팩토리를 이용한 피자 객체를 생성
//==============================================
public class PizzaStore{
    //Simplepizzafactory에 대한 레퍼런스를 저장
    SimplePizzaFactory factory;
   
    //PizzaStore의 생성자에 팩토리 객체 전달
    public Pizza createPizza (SimplePizzaFactory factory){
        this.factory = factory;
    }
   
    public Pizza orderPizza (String type){
        Pizza pizza;
 
        /*orderPizza() 매소드에서는 팩토리를 써서 피자 객체 생성.
            new 연산자 대신 팩토리 객체에 있는 create 메소드를 사용.
        구상 클래스의 인스턴스를 만들 필요가 없다.*/
        pizza = faactory.createPizza(type);
 
        Pizza.prepare();
        Pizza.bake();
        Pizza.cut();
        Pizza.box();
     
        return pizza;
    }
}
 


피자 가게의 클래스 다이어그램은 아래와 같다.




* 피자가게 프레임워크

 문제점이 생겼다. 각 분점마다 독자적인 방법을 사용하기 시작했기 때문이다. 그래서 피자 가게와 피자 제작 과정을 하나로 묶어주는 프레임워크를 만들어야 겠다는 생각에 도달했다. 피자를 만드는 활동 자체는 전부 PizzaStore 클래스에 국한 시키면서도 분점마다 고유의 스타일을 살릴 수 있도록 하는 방법을 적용해보자.



1. Factory Method (팩토리 메소드) 선언
 - 객체 생성을 처리한다.
 - 객체 생성 작업을 서브 클래스에 캡슐화한다.
 - 슈퍼 클래스의 클라이언트 코드와 서브 클래스의 객체 생성 코드를 분리한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//==============================================
//추상클래스 (PizzaStore) 생성
//==============================================
public abstract class PizzaStore {
 
    public Pizza orderPizza(String type) {
        Pizza pizza;
     
        //PizzaStore의 createPizza를 호출
        pizza = createPizza(type);
     
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
     
        return pizza;
    }
   
    //팩토리 객체대신 사용
    abstract createPizza(String type);
 
}




* Factory Method Pattern (팩토리 메서드 패턴)

정의

 객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브 클래스에서 결정하게 만드는 것.

특징

 - 모든 팩토리 패턴에서는 객체 생성을 캡슐화한다.

 - 객체 생성 코드를 전부 한 객체 또는 메소드에 집어 넣으면 코드에서 중복되는 내용을 제거할 수 있고, 나중에 관리할 때도 한 군데에만 신경 쓰면 된다.

 - 구현이 아닌 인터페이스를 바탕으로 프로그래밍을 할 수 있고, 그 결과 유연성과 확장성이 뛰어난 코드를 만들 수 있게 된다.




* 피자 팩토리 메소드를 이용한 피자 주문

 1. PizzaStore 인스턴스 확보

 2. orderPizza() 호출

 3. createPizza() 호출. (Pizza 객체가 OderPizza() 메소드로 리턴됨)

 4. orderPizza() 피자를 준비하고, 굽고, 자르고, 포장하는 작업 완료함.

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
public abstract class Pizza {
    String name;
    String dough;
    String sauce;
    ArrayList toppings = new ArrayList();
   
    void prepare() {
        System.out.println("Preparing " + name);
        System.out.println("Tossing dough..");
        System.out.println("Adding sauce... ");
        System.out.println("Adding toppings: ");
     
        for(int i=0; i< toppings.size(); i++) {
            System.out.println("  "+ toppings.get(i));
        }
    }
   
    void bake() {
        System.out.println("Bake for 25 miutes at 350");
    }
   
    void cut() {
            System.out.println("Cutting the pizza into diagonal slices");
    }
   
    void box() {
            System.out.println("Place pizza in official PizzaStore box");
    }
   
    public String getName() {
        return name;
    }
}
 
 
public class NYStyleCheesePizza extends Pizza {
    public NYStyleCheesePizza() {
        name = "NY Style Sauce and Cheese Pizza";
        dough = "Thin Crust Dough";
        sauce = "Marinara Sauce";
     
        toppings.add("Grated Reggiano Cheese");
    }
}
 
 
public class ChicagoStyleCheesePizza extends Pizza {
    public ChicagoStyleCheesePizza() {
        name = "Chicago Style Sauce and Cheese Pizza";
        dough = "Extra Thick Crust Dough";
        sauce = "Plum Tomato Sauce";
     
        toppings.add("Shredded Mozzarella Cheese");
    }
   
    void cut() {
        System.out.println("Cutting the pizza into square slices");
    }
}
 
 
public class PizzaTestDrive {
    public static void main(String[] args) {
        PizzaStore nyStore = new NYPizzaStore();
        PizzaStore chicagoStore = new ChicagoPizzaStore();
     
        Pizza pizza = nyStore.orderPizza("cheese");
        System.out.println("Ethan ordered a " + pizza.getName() + "\n");
     
        Pizza pizza = chicagoStore.orderPizza("cheese");
        System.out.println("Joel ordered a " + pizza.getName() + "\n");
    }
}



* 객체 의존성 살피기

객체 인스턴스를 직접 만들면 구상 클래스에 의존해야 한다.

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
public class DependentPizzaStore {
  
    public Pizza createPizza(String style, String type) {
        Pizza pizza = null;
     
        //뉴욕풍 피자를 처리하는 부분
        if (style.equals("NY")) {
            if (type.equals("cheese")) {
                pizza = new NYStyleCheesePizza();
            }
            else if (type.equals("veggie")) {
                pizza = new NYStyleVeggiePizza();
            }
            else if (type.equals("clam")) {
                pizza = new NYStyleClamPizza();
                        }
                        else if (type.equals("pepperoni")) {
                              pizza = new NYStylePepperoniPizza();
                        }
 
                  //시카고풍 피자를 처리하는 부분
                  } else if (style.equals("Chicago")) {
                        if (type.equals("cheese")) {
                              pizza = new ChicagoStyleCheesePizza();
                        }
                        else if (type.equals("veggie")) {
                              pizza = new ChicagoStyleVeggiePizza();
                        }
                        else if (type.equals("clam")) {
                              pizza = new ChicagoStyleClamPizza();
                        }
                        else if (type.equals("pepperoni")) {
                              pizza = new ChicagoStylePepperoniPizza();
                        }
            } else {
                  System.out.println("Error: invalid type of pizza");
                  return null;
            }
 
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
 
            return pizza;
      }
}



* 의존성 뒤집기 원칙 (Dependency Inversion Priciple)


 - 추상화 된 것에 의존하도록 만들어라.

 - 구상 클래스에 의존하도록 만들지 말라.

심하게 의존적인 PizzaStore() 의 가장 큰 문제점은 모든 종류의 피자에 의존한다는 점이다.


- 팩토리 메소드 패턴을 적용하고 나면 고수준 구성요소인 PizzaStore 와 저수준 구성요소인 피자 객체들이 모두 추상 클래스인 Pizza에 의존한다.

 - 팩토리 메소드 패턴은 의존성 뒤집기 원칙을 준수하기 위해 쓸 수 있는 가장 적합한 방법 가운데 하나이다.




* 개발자가 지향해야 하는 규칙

  
 - 어떤 변수에도 구상 클래스에 대한 레퍼런스를 저장하지 않는다.

 - 구상 클래스에서 유도된 클래스를 만들지 않는다.

 - 베이스 클래스에 이미 구현되어 있던 메소드를 오버라이드 하지 않는다.




* 원재료 공장 만들기

원재료를 생산할 팩토리를 위한 인터페이스 정의.

모든 팩토리 인스턴스에서 공통적으로 사용하는 부분이 있다면 인터페이스가 아닌 추상클래스로 만들어도 된다.

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
public interface PizzaIngredientFactory {
     
      //각 재료별로 생성 메소드 정의.
      public Dough createDough();
      public Sauce createSauce();
      public Cheese createCheese();
      public Veggies[] createVeggies();
      public Pepperoni createPepperoni();
      public Clams createClam();
}
 
 
//========================================================================
//모든 재료 공장에서 구현해야 하는 인터페이스를 구현
//========================================================================
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
  
      public Dough createDough() {
            return new ThinCrustDough();
      }       
            
      public Sauce createSauce() {
            return new MarinaraSauce();
      }       
            
      public Cheese createCheese() {
            return new ReggianoCheese();
      }       
            
      public Veggies[] createVeggies() {
            Veggies veggies[] = { new Garlic(), new Onion(), new Mushroom(), new RedPepper() };
            return veggies;
      }       
            
      public Pepperoni createPepperoni() {
            return new SlicedPepperoni();
      }       
            
      public Clams createClam() {
            return new FreshClams();
      }       
}


반응형

'Design Pattern' 카테고리의 다른 글

Strategy Pattern  (0) 2013.08.12
facade패턴  (0) 2013.07.05
반응형

퍼사드 패턴을 설명하기 앞서 이전의 다른 패턴을 기억해 보자.

    패턴                                                 용도
----------------------------------------------------------------------
데코레이터        한 인터페이스를 다른 인터페이스로 변환

어댑터              인터페스는 바꾸지 않고 책임(기능)만 추가

퍼사드              인터페이스를 간단하게 바꿈

1. 컨텍스트

Facade 패턴은 복잡한 서브 시스템에 통일된 인터페이스를 제공함으로써 복잡한 API를 단순화 시켜준다. 시스템을 서브 시스템 단위로 나누어 구성하는 것은 시스템의 복잡도를 낮춰주지만, 동시에 서브 시스템 사이에서의 통신 부하와 결합도가 증가하게 된다. 이러한 서브 시스템 사이의 의존도를 낮추고, 서브 시스템의 사용자 입장에서 사용하기 편리한 인터페이스를 제공하고자 하는 것이 facade 객체이다.

 

 

 

 

 

 

Facade 객체는 실생활에서의 고객 서비스 센터와 유사하다. 가령, 어떤 상품을 구매하는 과정에서 문제가 생겼다고 가정할 때, 고객이 문제의 성격에 따라 해당 부서에 직접 연락하는 것이 아니라 고객 서비스 센터를 통하는 것은 Facade 패턴에 대한 좋은 유추 사례가 될 수 있다.

2. 적용 영역

■ 복잡한 서브 시스템에 대해 간단한 인터페이스를 제공하기를 원하는 경우
■ 클라이언트와 인터페이스의 구현 클래스 사이에 의존도가 높은 경우
■ 서브 시스템을 레이어(layer)로 구분하고자 하는 경우

3. 구조




 

 

 

 




4. 적용 결과

■ 서브 시스템의 컴포넌트로부터 클라이언트를 격리하여, 클라이언트가 쉽게 서브 시스템을 이용할 수 있다.
■ 서브 시스템과 클라이언트 사이의 의존성을 낮춘다.
■ Facade 패턴을 사용한다고 해도, 필요한 경우 서브 시스템의 클래스에 직접 접근할 수도 있다. 즉, 일반화 정도(generality)와 개발의 편의성 사이에서의 적당한 합의점을 찾아야 한다.

5. 관련 패턴

■ Abstract Factory는 Facade와 함께 사용되어 서브 시스템에 독립적으로 서브 시스템의 객체를 생성하는 인터페이스를 제공한다. 또한, 특정 플랫폼에 국한된 클래스를 숨기기 위해 Facade 대신 사용할 수도 있다.
■ Mediator는 기존의 클래스의 기능을 추상화한다는 의미에서 Facade와 유사하다. 그러나, Mediator의 목적은 서로 직접적인 연관을 갖는 객체(colleague object) 사이에서의 커뮤니케이션을 추상화하는 것인데 반해, Facade는 서브 시스템의 인터페이스를 추상화하는 것이다.
■ 하나의 Facade 객체만이 요구되는 경우에는 Facade 객체가 Singleton 형태를 취하기도 한다.

반응형

'Design Pattern' 카테고리의 다른 글

Strategy Pattern  (0) 2013.08.12
abstract factory pattern  (0) 2013.07.09

+ Recent posts