책너두 (헤드 퍼스트 디자인 패턴) 30일차 (~414p)

요약

  • 반복자 패턴과 컴포지트 패턴
    • 컴포지트 패턴으로 메뉴 디자인하기
    • 메뉴 구성 요소 구현하기
    • 메뉴 항목 구현하기
    • 메뉴 구현하기
    • 종업원 코드에 컴포지트 적용하기
    • 디자인 도구상자 안에 들어가야 할 도구들

메모

컴포지트 패턴으로 메뉴 디자인하기

  • 구성 요소 인터페이스를 먼저 만듦.
    • 메뉴와 메뉴 항목 모두에 적용되는 공통 인터페이스 역할을 함.
    • 메뉴와 메뉴 항목을 똑같은 방법으로 처리할 수 있음. → 같은 메소드를 호출할 수 있음.
  • Waitress 클래스는 MenuComponent 인터페이스를 사용해서 Menu와 MenuItem에 모두 접근함.
  • MenuComponent는 MenuItem과 Menu 모두에 적용되는 인터페이스임.
  • MenuItem과 Menu에서 모두 print()를 오버라이드 함.
  • MenuItem에서 쓰일 법한 메소드만 오버라이드하고, 나머지는 기본 구현을 그대로 사용함.
    • ex) add() 메소드는 구성 요소를 추가하는 메소드이므로 MenuItem에서는 쓸 필요 없음.
      • 구성 요소는 Menu에서만 추가할 수 있기 때문
  • Menu에서도 역시 쓰일 법한 메소드만 오버라이드 함.
    • 메뉴를 추가하거나 삭제하는 add(), remove() 메소드는 오버라이드해야 함.

메뉴 구성 요소 구현하기

  • MenuComponenet는 잎과 복합 노드 모두에서 쓰이는 인터페이스 역할을 함.
  • 모든 구성요소에서 MenuComponent 인터페이스를 구현해야만 함.
    • 하지만 잎과 노드는 각각 역할이 다르므로 모든 메소드에 알맞는 기본 메소드는 구현이 불가능 함.
    • 따라서 ,자기 역할에 맞지 않는 상황을 기준으로 예외를 던지는 코드를 기본 구현으로 함.
public abstract class MenuComponent {
    public void add(MenuComponent menuComponent) {
        throw new UnsupportedOperationException();
    }

    public void remove(MenuComponent menuComponent)
        throw new UnsupportedOperationException();
    }

    public MenuComponent getChild(int i) {
        throw new UnsupportedOperationException();    
    }

    public String getName() {
        throw new UnsupportedOperationException();    
    }

    public String getDescription() {
        throw new UnsupportedOperationException();
    }

    public double getPrice() {
        throw new UnsupportedOperationException();    
    }

    public boolean isVegetarian() {
        throw new UnsupportedOperationException();
    }

    public void print() {
        throw new UnsupportedOperationException();
    }
}
  • 모두 UnsupportedOperationExeption을 던지도록 하면, 자기 역할에 맞지 않는 메소드는 오버라이드하지 않고 기본 구현을 그대로 사용할 수 있음.

메뉴 항목 구현하기

  • 컴포지트 다이어그램에서 잎에 해당하는 클래스임.
    • 복합 객체의 원소에 해당하는 행동을 구현해야 함.
public class MenuItem extends MenuComponent {
    String name;
    String description;
    boolean vegetarian;
    double price;

    public MenuItem(String name,
                                    String description,
                                    boolean vegetarian,
                                    double price)
    {
        this.name name;
        this.description = description;
        this.vegetarian = vegetarian;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public String getDescription() {
        return description;
    }

    public double getPrice() {
        return price;
    }

    public boolean isVegetarian() {
        return vegetarian;
    }

    public void print() {
        System.out.print(" " + getName());
        if (isVegetarian()) {
            System.out.print("(v)");
        }
        System.out.println(", " + getPrice());
        System.out.println("    -- " + + getDescription());
    }
)
  • MenuComponent 인터페이스를 확장해야 함.
    • 게터 메소든느 기존에 사용하던 것과 거의 같음.
  • print() 메소드를 오버라이드함.
    • MenuItem에서 이 메소드를 호출하면, 메뉴에 수록해야할 모든 내용이 출력됨.

메뉴 구현하기

  • 복합 객체 클래스인 Menu만 준비하면 됨.
    • 복합 객체에는 MenuItem은 물론, 다른 Menu도 저장할 수 있음.
public class Menu extends MenuComponent {
    List<MenuComponent> menuComponents = new ArrayList<MenuComponent>();
    String name;
    String description;

    public Menu(String name, String description) {
        this.name = name;
        this.description= description;
    }

    public void add(MenuComponent menuComponent) {
        menuComponents.add(menuComponent);
    }

    public void remove(MenuComponent menuComponent) {
        menuComponents.remove(menuComponent);
    }

    public MenuComponent getChild(int i) {
        return menuComponents.get(i);
    }

    public String getName() {
        return name;
    }

    public String getDescription() {
        return description;
    }

    public void print() {
        System.out.print("\n" + getName());
        System.out.println(", " + getDescription());
        System.out.println("----------------------");

        for (MenuComponent menuComponent : menuComponents) {
            menuComponent.print();
        }
    }
}
  • Menu에는 MenuComponent 형식의 자식을 몇개든 저장할 수 있음.
  • print() 메소드의 경우, Menu에 있는 모든 구성요소를 대상으로 반복 작업을 처리할 수 있음.

종업원 코드에 컴포지트 적용하기

  • 종업원 코드는 다른 모든 메뉴를 포함하고 있는 최상위 메뉴 구성 요소만 넘겨주면 됨.
public class Waitress {
    MenuComponent allMenus;

    public Waitress(MenuComponent allMenus) {
        this.allMenus = allMenus;
    }

    public void printMenu() {
        allMenus.print();
    }
}
  • 여기서 allMenus에 최상위 메뉴를 지정하면 됨.
  • 이렇게 했을 때, 한 클래스에서 한 역할만 맡아야 한다고 했으나, 이 패턴에서는 한 클래스에 2가지 역할을 ㄴ헌는다고 생각할 수 있음.
    • 계층 구조를 관리하는 일과, 메뉴 관련 작업을 처리해야 함.
  • 컴포지트 패턴에서는 단일 역할 원칙을 깨는 대신, 투명성을 확보하는 패턴이라 할 수 있음.
    • 투명성(transparency) → Componenet 인터페이스에 자식들을 관리하는 기능과 잎으로써의 기능을 전부 넣어서 클라이언트가 복합 객체와 잎을 똑같은 방식으로 처리할 수 있도록 만듦.
      • 어떤 원소가 복합객체이고, 잎인지가 클라이언트에게 투명하게 보임.
  • 물론, 두 종류의 기능이 모두 들어가기에 안전성은 떨어짐
    • 이러한 문제를 디자인상의 결정 사항에 속한다고 함.
  • 따라서, 상황에 따라 원칙을 적절하게 사용해야 함.
    • 디자인 원칙에서 제시하는 가이드라인을 따르면 좋지만, 그 원칙이 디자인에 어떤 영향을 끼칠지 항상 고민하고 원칙을 적용해야 함.
  •  

디자인 도구상자 안에 들어가야 할 도구들

  • 반복자 패턴
    • 컬렉션의 구현 방법을 노출하지 않으면서 집합체 내의 모든 항목에 접근하는 방법을 제공함.
  • 컴포지트 패턴
    • 객체를 트리구조로 구성해서 부분-전체 계층 구조를 구현함. 컴포지트 패턴을 사용하면 클라이언트에서 개별 객체와 복합 객체를 또같은 방법으로 다룰 수 있음.

댓글

Designed by JB FACTORY