Composite Pattern


問題の状況

Pancake house menu、diner menuとそのサブメニューデザートmenu、cafe menuをプリントアウトしたいです!この場合、各menuのタイプは、タイル、List、HashMapとは異なります.
この場合、iterator patternを使用して異なるタイプのメニューの出力を抽象化しますが、サブメニューをより柔軟に処理したい場合はどうすればいいですか?
つまり、整理要求は以下の通りです.
ツリー構造、各メニューとそのサブメニューを収容可能
  • 構造
  • 、すべてのメニューに適用
    より柔軟な構成、
  • メニューを選択的に処理
    この場合、コンビネーションモードを使用できます.
    定義#テイギ#
    オブジェクトをツリー構造に整理して、部分と全体を表す階層にすることができます.
    クライアントでは、単一のオブジェクトと他のオブジェクトからなる複合オブジェクトを同じ方法で処理できます.
    上記の例では、diner menuのスイーツmenuのように、オブジェクト内の形状を単一のオブジェクトと同じ形状と見なすことができます.個々のオブジェクトを他のオブジェクトを含まない複合オブジェクトと見なします.

    コンポーネント:複合オブジェクト内のすべてのオブジェクトにinter彭を定義する
    Leaf:Componentがサポートする機能の実装
    Component:サブコンポーネントの動作を定義し、サブコンポーネントを保存します.Leaf関連機能の実装
    Componentにはコンポーネントが入っています.
    コンポーネントは、Composite、Leafの2つに分類されます.
    ->再帰構造
    ルートはComponent最後に逆ツリー構造,すなわちLeafである.

    MenuComponentでMenuItemとMenuで使用するメソッドを定義する
    MenuItemとMenuは、自分が使っている方法だけを上書きすることで再定義します
    メニューコンポーネント
    public abstract class MenuComponent {
    	// 메소드들은 하위 클래스에서 구현하지 않을 경우 기본적으로 오류 던지도록 구현
       
        // 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();
    	}
      
        // MenuItem 관련 메소드
    	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();
    	}
    }
    メニュー項目(Leaf)
    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());
    	}
    }
    メニュー(Component)
    public class Menu extends MenuComponent {
    	ArrayList<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 (MenuComponent)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("---------------------");
      		
            // 이터레이터 패턴 사용하여 반복
    		Iterator<MenuComponent> iterator = menuComponents.iterator();
    		while (iterator.hasNext()) {
    			MenuComponent menuComponent = 
    				(MenuComponent)iterator.next();
    			menuComponent.print();
    		}
    	}
    }
    サービス(クライアント)
    public class Waitress {
    	MenuComponent allMenus;
     	
        // 다른 모든 메뉴를 포함하고 있는 최상위 메뉴 구성요소 넘겨주기
    	public Waitress(MenuComponent allMenus) {
    		this.allMenus = allMenus;
    	}
     
     	// 최상위 메뉴 구성요소 출력하면 트리에 속한 모든 메뉴 출력 가능
    	public void printMenu() {
    		allMenus.print();
    	}
    }
    テスト
    public class MenuTestDrive {
    	public static void main(String args[]) {
    		MenuComponent pancakeHouseMenu = 
    			new Menu("PANCAKE HOUSE MENU", "Breakfast");
    		MenuComponent dinerMenu = 
    			new Menu("DINER MENU", "Lunch");
    		MenuComponent cafeMenu = 
    			new Menu("CAFE MENU", "Dinner");
    		MenuComponent dessertMenu = 
    			new Menu("DESSERT MENU", "Dessert of course!");
    		MenuComponent coffeeMenu = new Menu("COFFEE MENU", "Stuff to go with your afternoon coffee");
      
    		MenuComponent allMenus = new Menu("ALL MENUS", "All menus combined");
      
    		allMenus.add(pancakeHouseMenu);
    		allMenus.add(dinerMenu);
    		allMenus.add(cafeMenu);
      		
            // pancakeHouseMenu
    		pancakeHouseMenu.add(new MenuItem(
    			"K&B's Pancake Breakfast", 
    			"Pancakes with scrambled eggs and toast", 
    			true,
    			2.99));
    		pancakeHouseMenu.add(new MenuItem(
    			"Regular Pancake Breakfast", 
    			"Pancakes with fried eggs, sausage", 
    			false,
    			2.99));
    		pancakeHouseMenu.add(new MenuItem(
    			"Blueberry Pancakes",
    			"Pancakes made with fresh blueberries, and blueberry syrup",
    			true,
    			3.49));
    		pancakeHouseMenu.add(new MenuItem(
    			"Waffles",
    			"Waffles with your choice of blueberries or strawberries",
    			true,
    			3.59));
    		
            // dinerMenu
    		dinerMenu.add(new MenuItem(
    			"Vegetarian BLT",
    			"(Fakin') Bacon with lettuce & tomato on whole wheat", 
    			true, 
    			2.99));
    		dinerMenu.add(new MenuItem(
    			"BLT",
    			"Bacon with lettuce & tomato on whole wheat", 
    			false, 
    			2.99));
    		dinerMenu.add(new MenuItem(
    			"Soup of the day",
    			"A bowl of the soup of the day, with a side of potato salad", 
    			false, 
    			3.29));
    		dinerMenu.add(new MenuItem(
    			"Hot Dog",
    			"A hot dog, with saurkraut, relish, onions, topped with cheese",
    			false, 
    			3.05));
    		dinerMenu.add(new MenuItem(
    			"Steamed Veggies and Brown Rice",
    			"Steamed vegetables over brown rice", 
    			true, 
    			3.99));
     
    		dinerMenu.add(new MenuItem(
    			"Pasta",
    			"Spaghetti with marinara sauce, and a slice of sourdough bread",
    			true, 
    			3.89));
       
       		// dinerMenu에 dessertMenu 추가
    		dinerMenu.add(dessertMenu);
      
      		// dessertMenu
    		dessertMenu.add(new MenuItem(
    			"Apple Pie",
    			"Apple pie with a flakey crust, topped with vanilla icecream",
    			true,
    			1.59));
      
    		dessertMenu.add(new MenuItem(
    			"Cheesecake",
    			"Creamy New York cheesecake, with a chocolate graham crust",
    			true,
    			1.99));
    		dessertMenu.add(new MenuItem(
    			"Sorbet",
    			"A scoop of raspberry and a scoop of lime",
    			true,
    			1.89));
    
    		// cafeMenu
    		cafeMenu.add(new MenuItem(
    			"Veggie Burger and Air Fries",
    			"Veggie burger on a whole wheat bun, lettuce, tomato, and fries",
    			true, 
    			3.99));
    		cafeMenu.add(new MenuItem(
    			"Soup of the day",
    			"A cup of the soup of the day, with a side salad",
    			false, 
    			3.69));
    		cafeMenu.add(new MenuItem(
    			"Burrito",
    			"A large burrito, with whole pinto beans, salsa, guacamole",
    			true, 
    			4.29));
    
    		// cafeMenu에 coffeeMenu 추가
    		cafeMenu.add(coffeeMenu);
    
    		// coffeeMenu
    		coffeeMenu.add(new MenuItem(
    			"Coffee Cake",
    			"Crumbly cake topped with cinnamon and walnuts",
    			true,
    			1.59));
    		coffeeMenu.add(new MenuItem(
    			"Bagel",
    			"Flavors include sesame, poppyseed, cinnamon raisin, pumpkin",
    			false,
    			0.69));
    		coffeeMenu.add(new MenuItem(
    			"Biscotti",
    			"Three almond or hazelnut biscotti cookies",
    			true,
    			0.89));
     
    		Waitress waitress = new Waitress(allMenus);
       
    		waitress.printMenu();		// print
    	}
    }
    単一キャラクタの原則(SRP)と透明性
    複合モードは、上記の例に示すように、単一のロールの原則に違反し、コンポーネント内で階層を管理し、メニューに関連する操作を処理します.しかしながら、クライアントは、その要素がcompositeであるかleaf nodeであるかを透明に知ることができるため、透明性を確保するモードと考えられる.
    leafノードでコンビネーションに関連するメソッドを呼び出したり再定義したり、instanceofを使用して再検証する必要があるなどの欠点がありますが、leafノードをサブオブジェクトのないコンビネーションオブジェクトと見なす場合は問題ありません.
    状況によって決まる
    Iterator Pattern
    まだ未解決の要求条件があります.すなわち、所望のメニューは、ツリー構造全体のメニューにおいて選択的に操作処理することができる.そのため、Ipharter Patternを使用します.
    Menu
    public class Menu extends MenuComponent {
    	Iterator<MenuComponent> iterator = null;
        .
        .
        .
    
    	// Menu에 CompositeIterator 추가
        // 현재 복합 객체에 대한 반복자 리턴
    	public Iterator<MenuComponent> createIterator() {
    		if (iterator == null) {
    			iterator = new CompositeIterator(menuComponents.iterator());
    		}
    		return iterator;
    	}
    .
    .
    .
    MenuItem
    public class MenuItem extends MenuComponent {
     
    .
    .
    .
    	// NullItertor 추가
        // Leaf는 반복자가 존재하지 않으므로 아무 일도 하지 않는 null 반복자 리턴
    	public Iterator<MenuComponent> createIterator() {
    		return new NullIterator();
    	}
     .
     .
     .
    CompositeIterator
    public class CompositeIterator implements Iterator<MenuComponent> {
    	// 위의 코드에서는 leaf node 안에서 반복자를 써서 작업을 처리하고 composite인 경우 하위 메서드를 호출하여 반복
        // 하지만 해당 코드에서는 iterator를 사용하므로 외부에서 반복자 사용 -> 스택 사용해야 함
    	Stack<Iterator<MenuComponent>> stack = new Stack<Iterator<MenuComponent>>();
       
       // 최상위 composite의 iterator 전달
    	public CompositeIterator(Iterator<MenuComponent> iterator) {
    		stack.push(iterator);
    	}
       
    	public MenuComponent next() {
    		if (hasNext()) {
    			Iterator<MenuComponent> iterator = stack.peek();
    			MenuComponent component = iterator.next();
    			stack.push(component.createIterator());
    			return component;
    		} else {
    			return null;
    		}
    	}
      
    	public boolean hasNext() {
    		if (stack.empty()) {
    			return false;
    		} else {
    			Iterator<MenuComponent> iterator = stack.peek();
    			if (!iterator.hasNext()) {
    				stack.pop();
    				return hasNext();
    			} else {
    				return true;
    			}
    		}
    	}
    	
    	/*
    	 * No longer needed as of Java 8
    	 * 
    	 * (non-Javadoc)
    	 * @see java.util.Iterator#remove()
    	 *
    	public void remove() {
    		throw new UnsupportedOperationException();
    	}
    	*/
    }
    
    Waitress
    public class Waitress {
    	MenuComponent allMenus;
     
    	public Waitress(MenuComponent allMenus) {
    		this.allMenus = allMenus;
    	}
     
    	public void printMenu() {
    		allMenus.print();
    	}
      
    	public void printVegetarianMenu() {
    		Iterator<MenuComponent> iterator = allMenus.createIterator();
    
    		System.out.println("\nVEGETARIAN MENU\n----");
    		while (iterator.hasNext()) {	
            	// iterator 이용해서 채식주의자 메뉴만 출력
    			MenuComponent menuComponent = iterator.next();
    			try {
    				if (menuComponent.isVegetarian()) {
    					menuComponent.print();
    				}
    			} catch (UnsupportedOperationException e) {}
                // 여기서 try-catch를 사용한 이유
                // client에서 composite, leaf를 똑같이 취급하고 composite에서 지원하지 않는 메소드임을 나타내기 위해서 사용
    		}
    	}
    }
    シングルラインサマリー
    クライアントは、リーフノードまたは複合オブジェクトを考慮する必要がなく、最上位ノードを使用してツリー全体で作業できます.
    ->部分-オブジェクト関係のあるすべてのオブジェクトセットを同じ方法で処理する場合に使用しましょう.