LXX的网络日志
人因梦想而伟大
聊聊迭代器模式与组合模式

介绍

迭代器模式(Iterator Pattern),它用来遍历集合对象。可以把它看作成一个“容器”,它里面装了很多的对象。

它,主要是用来解决不同的方式遍历整个对象的问题。比如一个,是用数组来存储对象,另一个是用ArrayList存储。

两个不同的对象集合,采用遍历的方式也不同。所以,会写很多重复性代码。而使用迭代器模式,可以把具体的遍历方式给隐藏起来。统一使用使用迭代器遍历。

例子

现在给一个例子来理解它,现在有两个菜单,一个是煎饼菜单、另一个是餐厅的菜单。煎饼菜单采用的是集合来存储对象,而另一个采用的数组来存储对象。

public class MenuItem {
    private String name;
    private String description;
    private boolean vegetarian;
    private double price;

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

// 菜单1
public class PancakeHouseMenu {
    private final ArrayList<MenuItem> menuItems;

    public PancakeHouseMenu() {
        menuItems = new ArrayList<>();
        addItem("煎蛋早餐饼", "外焦里嫩的早餐饼", true, 2.99);
        addItem("煎蛋早餐饼2", "外焦里嫩的早餐饼2", false, 3.99);
        addItem("煎蛋早餐饼3", "外焦里嫩的早餐饼3", true, 3.49);
        addItem("煎蛋早餐饼4", "外焦里嫩的早餐饼4", true, 4.99);
        // 继续添加其他的项目
    }

    public void addItem(String name, String description, boolean vegetarian, double price) {
        MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
        menuItems.add(menuItem);
    }

    public ArrayList<MenuItem> getMenuItems() {
        return menuItems;
    }
}

// 菜单2
public class DinerMenu {
    private static final int MAX_ITEMS = 6;
    private final MenuItem[] menuItems;
    private int numberOfItems = 0;

    public DinerMenu() {
        menuItems = new MenuItem[MAX_ITEMS];

        addItem("热狗", "好吃的热狗", false, 3.05);
        addItem("老火靓汤", "味道鲜满的老火靓汤", true, 2.95);
        addItem("晚餐套餐", "balabalabala......", false, 3.05);
        addItem("晚餐套餐2", "balabalabala......", false, 3.05);
        addItem("晚餐套餐3", "balabalabala......", false, 3.05);
        addItem("晚餐套餐4", "balabalabala......", false, 3.05);
    }


    public void addItem(String name, String description, boolean vegetarian, double price) {
        MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
        if (numberOfItems >= MAX_ITEMS) {
            System.out.println("菜单已满!");
        } else {
            menuItems[numberOfItems] = menuItem;
            numberOfItems += 1;
        }
    }

    public MenuItem[] getMenuItems() {
        return menuItems;
    }
}

循环打印出菜单:

public static void main(String[] args) {
    PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
    ArrayList<MenuItem> breakfastItems = pancakeHouseMenu.getMenuItems();
    for (int i = 0; i < breakfastItems.size(); i++) {
        MenuItem menuItem = breakfastItems.get(i);
        System.out.println(menuItem.getName());
        System.out.println(menuItem.getPrice());
        System.out.println(menuItem.getDescription());
    }

    DinerMenu dinerMenu = new DinerMenu();
    MenuItem[] lunchItems = dinerMenu.getMenuItems();
    for (int i = 0; i < lunchItems.length; i++) {
        MenuItem menuItem = lunchItems[i];
        System.out.println(menuItem.getName());
        System.out.println(menuItem.getPrice());
        System.out.println(menuItem.getDescription());
    }
}

可以看到,两种不同的菜单,我们必须要采用两种不同的遍历方式。嗯,重复性代码太多。要是三种菜单、四种菜单,岂不是要写四次比便利的方式吗?

需要把它封装起来。

使用迭代器

定义迭代器接口:

public interface Iterator {
    boolean hasNext();
    MenuItem next();
}

实现迭代器与改造菜单:

public class DinerMenuIterator implements Iterator {
    private final MenuItem[] items;
    private int position = 0;

    public DinnerMenuIterator(MenuItem[] items) {
        this.items = items;
    }

    @Override
    public boolean hasNext() {
        // 判断组数里是否还有对象
        return position < items.length && items[position] != null;
    }

    @Override
    public MenuItem next() {
        MenuItem menuItems = items[position];
        position += 1;
        return menuItems;
    }
}

public class DinerMenu {
    private static final int MAX_ITEMS = 6;
    private final MenuItem[] menuItems;
    private int numberOfItems = 0;

    // 初始化

    // 添加菜单的方法

    // 创建迭代器
    public Iterator createIterator() {
        return new DinerMenuIterator(menuItems);
    }
}

public class PancakeHouseMenuIterator implements Iterator {
    private final ArrayList<MenuItem> items;
    private int position = 0;

    public PancakeHouseMenuIterator(ArrayList<MenuItem> items) {
        this.items = items;
    }

    @Override
    public boolean hasNext() {
        return position < items.size() && items.get(position) != null;
    }

    @Override
    public MenuItem next() {
        MenuItem item = items.get(position);
        position += 1;
        return item;
    }
}
public class PancakeHouseMenu {
    private final ArrayList<MenuItem> menuItems;

    // 初始化
    
    // 添加菜单的方法

    // 创建迭代器
    public Iterator createIterator() {
        return new PancakeHouseMenuIterator(menuItems);
    }
}

由于,我们的菜单都是服务员提供给客户看的,所以,还需要添加一个服务员类:

public class Waitress {
    private final PancakeHouseMenu pancakeHouseMenu;
    private final DinerMenu dinerMenu;

    public Waitress(PancakeHouseMenu pancakeHouseMenu, DinerMenu dinerMenu) {
        this.pancakeHouseMenu = pancakeHouseMenu;
        this.dinerMenu = dinerMenu;
    }

    public void printMenu() {
        Iterator pancakeHouseMenuIterator = pancakeHouseMenu.createIterator();
        Iterator dinerMenuIterator = dinerMenu.createIterator();
        printMenu(pancakeHouseMenuIterator);
        System.out.println("-----菜单1-----");
        printMenu(dinerMenuIterator);
        System.out.println("-----菜单2-----");
    }

    private void printMenu(Iterator iterator) {
        while (iterator.hasNext()) {
            MenuItem menuItem = iterator.next();
            System.out.println(menuItem.getName());
            System.out.println(menuItem.getPrice());
            System.out.println(menuItem.getDescription());
        }
    }
}

// 测试一下

public static void main(String[] args) {
    Waitress waitress = new Waitress(new PancakeHouseMenu(), new DinerMenu());
    waitress.printMenu();
}

就这样,我们利用迭代器模式,简化了一些代码,尽管增加了一些代码,但它并不会让我们重复性的去写循环便利的代码,因为这实在太麻烦了。就算新增一种菜单,对代码的改动也不会很大。

但目前代码的缺点是Waitress类与菜单的耦合性很高,新增菜单,我们又要对这个类进行改动。

还有,在Java中已经有了一个java.util.Iterator迭代,我们无需再去重新实现,将现有的Iterator改成java.util.Iterator之后,我们来优化Waitress类。

// 菜单接口
public interface Menu {
    Iterator<MenuItem> createIterator();
    void addItem(String name, String description, boolean vegetarian, double price);
}

// 更改现有的菜单类,我们通过面向接口来编程

public class PancakeHouseMenu implements Menu {
    // ...
}

public class DinerMenu implements Menu {
    // ...
}

// 优化Waitress类
public class Waitress {
    private ArrayList<Menu> menus;

    public Waitress(ArrayList<Menu> menus) {
        this.menus = menus;
    }

    public void printMenu() {
        // 遍历
        for (Menu menu : menus) {
            printMenu(menu.createIterator());
        }
    }

    private void printMenu(Iterator<MenuItem> iterator) {
        while (iterator.hasNext()) {
            // ...
        }
    }
}

// 我们来测试一下
public static void main(String[] args) {
    ArrayList<Menu> menus = new ArrayList<>();
    menus.add(new PancakeHouseMenu());
    menus.add(new DinerMenu());

    Waitress waitress = new Waitress(menus);
    waitress.printMenu();
}

这样,就可以让我们的Waitress类变得更加灵活,新增的菜单,只需要添加到菜单集合中即可。

组合模式

现在,要求加上子菜单,还要支持菜单的选中。但,现在的实现,无法支持我们的需求。重写现在的实现,显然是不太可能的。

但我们,可以用用组合模式,它可以解决我们的问题。

组合模式的定义:组合模式允许你将对象组合成树形结构,来表现“整体/部分”。组合能让客户以一致的方式处理一个对象以及对象集合。

虽然,我们重写所有的代码不太可能。但我们只需要会退几步,稍稍改一下。

菜单组合类:

// 组合类
public abstract class MenuComponent {
    public void add(MenuComponent component) {
        // 先不提供具体的实现
        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();
    }
}

实现组合菜单:

public class Menu extends MenuComponent {
    private ArrayList<MenuComponent> menuComponents = new ArrayList<>();
    private String name;
    private String description;

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

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

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

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

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


    @Override
    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 = iterator.next();
            menuComponent.print();
        }
    }
}

public class PancakeHouseMenu extends Menu {

    public PancakeHouseMenu(String name, String description) {
        super(name, description);
        // 菜单项
    }

    public void addItem(String name, String description, boolean vegetarian, double price) {
        MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
        super.add(menuItem);
    }
}

public class DinerMenu extends Menu {

    public DinerMenu(String name, String description) {
        super(name, description);
       // 菜单项
    }

    private void addItem(String name, String description, boolean isVegetarian, double price) {
        MenuItem menuItem = new MenuItem(name, description, isVegetarian, price);
        super.add(menuItem);
    }

}

实现了,组合菜单后测试一下,看看结果如何:

 public static void main(String[] args) {
    MenuComponent pancakeHouseMenu = new PancakeHouseMenu("PANCAKE HOUSE MENU", "breakfast");
    MenuComponent diner = new DinerMenu("DINER MENU", "diner");

    MenuComponent allMenus = new Menu("ALL MENUS", "ALL menus combined");

    allMenus.add(pancakeHouseMenu);
    allMenus.add(diner);

    // 加入具体的菜单项
    diner.add(new MenuItem("意大利面","balabala...", true, 3.89));
    // 加入其他的菜单...

    Waitress waitress = new Waitress(allMenus);
    waitress.printMenu();
}

结果如下:

output

好了,这样就已经完成,我们的需求了。

当,有数个对象时,它们彼此之间有“整体/部分”的关系,并且想要一致对待这些对象时,就要使用组合模式。