《HeadFirst设计模式》读书笔记-第9章v2-组合模式

定义

组合模式(composite pattern)允许你将对象组合成树形结构来表现“整体/部分“层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。

这里写图片描述

代码实现

本章使用组合模式来实现下图的树状菜单。

这里写图片描述

首先为菜单和菜单项创建一个共同的接口作为组件接口,让我们能够用统一的做法来处理菜单和菜单项。换句话说,客户(Waitress)可以针对菜单或菜单项调用相同的方法。

组件接口MenuComponent.java

import java.util.*;
// MenuComponent对每个方法都提供了默认的实现,
// 菜单和菜单项的角色不同,所以有些方法可能并不适合某种节点。
// 面对这种情况,默认实现抛出UnsupportedOperationException,当菜单或菜单项不支持某个操作时,继承默认实现即可
public abstract class MenuComponent {
    // Composite应该实现的方法
    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();
    }
}

Leaf节点MenuItem.java

import java.util.Iterator;
import java.util.ArrayList;

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());
    }
}

Composite节点Menu.java

import java.util.Iterator;
import java.util.ArrayList;

public class Menu extends MenuComponent {
    ArrayList menuComponents = new ArrayList();
    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;
    }

    // 对菜单的每一项进行遍历:
    // 使用menuComponents生成迭代器,
    //     通过迭代器遍历menuComponents的每一项,
    //     直接调用MenuItem.print或者递归调用Menu.print
    public void print() {
        System.out.print("\n" + getName());
        System.out.println(", " + getDescription());
        System.out.println("---------------------");

        Iterator iterator = menuComponents.iterator();
        while (iterator.hasNext()) {
            MenuComponent menuComponent = 
                (MenuComponent)iterator.next();
            menuComponent.print();
        }
    }
}

客户Waitress的代码

import java.util.Iterator;

public class Waitress {
    MenuComponent allMenus;

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

    public void printMenu() {
        allMenus.print();
    }
}

测试驱动代码

import java.util.*;

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 allMenus = new Menu("ALL MENUS", "All menus combined");

        allMenus.add(pancakeHouseMenu);
        allMenus.add(dinerMenu);
        allMenus.add(cafeMenu);

        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.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(
            "Hotdog",
            "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.add(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.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));

        Waitress waitress = new Waitress(allMenus);

        waitress.printMenu();
    }
}

该模式体现了哪些OO原则

  1. 原则2: 针对接口编程,而不是针对实现编程

    Waitress和MenuTestDrive都是针对MenuComponent的接口编程,不关心具体实现。

本章总结

  1. 组合模式以单一责任设计原则换取透明性(transparency)。透明性是指通过让组件接口Component同时包含一些管理子节点和叶节点操作,客户就可以将组合和叶节点一视同仁。也就是说,一个元素究竟是组合还是叶节点,对客户是透明的。现在MenuComponent类中同时具有两种类型的操作。因为客户有机会对一个元素做一些不恰当或者没有意义的操作(如试图把菜单添加到菜单项),所以我们失去了一些安全性。这是设计上的抉择,我们也可以采用另外一种方向的设计,将责任区分开来放在不同的接口中。这样一来,设计上就比较安全,但是我们失去了透明性,客户的代码将必须用条件语句和instanceof操作符处理不同类型的节点。这是一个典型的折衷案例。尽管我们受到设计原则的指导,但是我们总是需要观察某原则对我们的设计所造成的影响。

  2. 组合结构内任意对象称为组件,组件可以是组合,也可以是叶节点

猜你喜欢

转载自blog.csdn.net/rex_nie/article/details/79947292
今日推荐