Head First 设计模式总结(十) 组合模式

组合模式——

允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。
本文的例子沿用了前一节《Head First 设计模式总结(九) 迭代器模式》中菜单和菜单项的例子。
组合模式能够创建一个树形结构:
在这里插入图片描述
本例是准备在同一个结构中处理嵌套菜单和菜单项组,实现为菜单项添加子菜单,比如说给餐厅菜单(DinnerMenu)添加一个甜点子菜单。通过将菜单和项放在相同的结构中,我们创建了一个“整体/部分”层次结构,即由菜单和菜单项组成的对象树。但是可以将它视为一个整体,像是一个丰富的大菜单。
在这里插入图片描述
下面是组合模式的类图:
在这里插入图片描述

下面我们利用组合模式来设计新的菜单,为之前存在的菜单添加子菜单:
在这里插入图片描述
[] 这里的MenuComponent接口就是Component(组件接口),MemuItem是leaf(叶节点),它只覆盖了MemuComponent中对它有意义的方法(一些用于描述菜品信息的方法)。Menu是Composite(组合),它也只覆盖了MenuComponent中对它自己有意义的方法(用于增加或者减少菜单项的方法)。
所有组件都必须实现MenuComponent接口,然而叶子节点和组合节点的角色不同,所以有些方法可能并不适合某种节点,面对这种情况,有时候最好是抛出运行时异常(throw new UnsupportedOperationException()),通过抛出该异常,如果菜单项或菜单不支持某个操作,它们就不需要做任何事,直接继承默认实现即可。

先给出MenuComponent抽象类的代码:(它是所有composite和leaf都必须实现的接口,即组合模式类图中的component接口)

public abstract class MenuComponent {
    public void add(MenuComponent menuComponent){
        throw new UnsupportedOperationException();
    }

    public void remove(){
        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();
    }
}

接下来,实现菜单项MenuItem,它是组合类图里的leaf。它实现component类元素的行为。

public class MenuItem extends MenuComponent {
    String name;
    String description;
    boolean isVegetarian;
    double price;

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

    @Override
    public String getName() {
        return name;
    }

    @Override
    public String getDescription() {
        return description;
    }

    @Override
    public boolean isVegetarian() {
        return isVegetarian;
    }

    @Override
    public double getPrice() {
        return price;
    }

    public void print(){
        System.out.println(" " + getName());
        if (isVegetarian){
            System.out.println("V");
        }
        System.out.println(", " + getPrice());
        System.out.println("    --" + getDescription());
    }
}

现在已经有了leaf,下面我们需要弄一个composite,composite可以持有leaf或者其他composite,在本实例中,它是Menu,它可以包含MenuItem和其他Menu,下面是实现的一个Menu类:

import java.util.ArrayList;
import java.util.Iterator;
/*
*   this is the composite node
* */
public class Menu extends MenuComponent {
    //创建一个ArrayList当作容器,用于装MenuItem或者其他子菜单,MenuItem和其他菜单项都是MenuComponent类型
    ArrayList menuComponents = new ArrayList();
    String name;
    String description;

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

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


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

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

    @Override
    public String getName() {
        return name;
    }

    @Override
    public String getDescription() {
        return description;
    }

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

        /*
        *这是递归的思想
        * 这里用到了迭代器,用它遍历所有组件,遍历的时候可能遇到MenuItem,也可能遇到其他菜单,但是它们都有print()方法,当遍历到它们时,不管是谁,在那调用print()方法准没错
        * */
        Iterator iterator = menuComponents.iterator();
        while (iterator.hasNext()){
            MenuComponent menuComponent = (MenuComponent) iterator.next();
            menuComponent.print();
        }
    }
}

采用了递归之后,打印菜品就非常方便了,和Iterator配合使用可以很方便的打印MenuItem或者子菜单中的MenuItem,现在Waitress的代码变得非常简单:

 public class Waitress {
    MenuComponent allMenus;

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

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

    public static void main(String[] args){
        //创建所有的 Menu 对象
        MenuComponent pancakeHouseMenu = new Menu("Pancake House Menu","Breakfast");
        MenuComponent dinnerMenu = new Menu("Dinner Menu","Lunch");
        MenuComponent cafeMenu = new Menu("Cafe Menu","Dinner");
        MenuComponent dessertMenu = new Menu("Dessert Menu","Dessert of course!");

//我们需要一个最顶层的菜单,它是allMenus
        MenuComponent allMenus = new Menu("All Menus","Include all menus");

        //这里的add()方法是composite的add()方法,即Menu类的add()方法,它将所有Menu都添加到顶层菜单allMenus的容器中
        allMenus.add(pancakeHouseMenu);
        allMenus.add(dinnerMenu);
        allMenus.add(cafeMenu);

        //给dinnerMenu添加一个菜单项 Pasta
        dinnerMenu.add(new MenuItem("Pasta","Spaghetti with Marinara Sauce , and a slice of sourdough bread",true,3.89));
        //给dinnerMenu添加一个子菜单
        dinnerMenu.add(dessertMenu);
        //给子菜单dessertMenu添加一个菜单项 Apple Pie
        dessertMenu.add(new MenuItem("Apple Pie","Apple Pie with a flakey crust",true,1.59));
        //构建服务员对象,让她打印菜品信息
        Waitress waitress = new Waitress(allMenus);
        waitress.printMenu();
    }
}

整个项目运转起来之后,菜单组合的类图如下:
在这里插入图片描述
也许这里会有一些疑问,之前有个说法是“一个类,一个责任”,组合模式却是一个类有两个责任。组合模式不但要管理层次结构,还要执行菜单的操作。

实际上,组合模式用单一责任设计原则换取了透明性。通过让组件的接口同时包含一些管理子节点和叶节点的操作,客户就可以将composite和leaf一视同仁,一个元素究竟是composite还是leaf,对于客户是透明的。

MenuComponent类中同时具有两种类型的操作。因为客户有机会对一个元素做一些不恰当或者无意义的操作(例如试图将菜单添加到菜单项中),所以我们失去了一些“安全性”。这是设计上的抉择;我们也可以采用另一种设计:将责任区分开来放在不同的接口中,这样一来,设计上就比较安全,但我们也因此失去了透明性,客户的代码将必须用条件语句和instanceof操作符处理不同类型的节点。所以这是一个典型的折衷案例。

尽管我们收到设计原则的指导,但是我们总是需要观察某原则对我们的设计所造成的影响。有时候我们会故意做一些看似违反原则的事情。然而,在某些例子中,这是观点的问题;比方说,让管理孩子的操作(例如add()、remove()、getChild())出现在叶节点中,似乎很不恰当,但是换个视角来看,你可以把叶节点视为没有孩子的节点。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值