设计模式9——迭代器和组合模式

如果饿了就吃,困了就睡,渴了就喝,人生就太无趣了
代码地址:https://github.com/keer123456789/MY_STUDY_LIFE/tree/master/src/main/java/Head_First/Module/Module_Iterator


一、迭代器模式

1.概念

1.1 定义

提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。

1.2 类图

如图1,接口Iterator是一个迭代器接口,所有迭代器需要实现的接口,一般使用java.util.Iterator接口,也可以自己构造接口。接口Aggregate为所有聚合所使用,提供一个返回迭代器的方法createIterator();各个具体的聚合ConcreteAggregate实现这个接口。从而实现顺序访问聚合中的对象。

在这里插入图片描述

2.实例(餐饮店合并)

现在有一家提供早餐的蛋糕店和提供正餐的餐厅需要合并,他们的菜单系统中的每一个菜单项都是使用MenuItem.java实现的。如图2:一共有四个成员变量和相应的getter方法。

  • name:菜品的名字,类型是String

  • description:菜品描述,类型是String

  • vegetarian:是否为素食,类型是boolean

  • price:菜品价格,类型是double

    在这里插入图片描述

现在需要将两家餐厅的菜单合并成为一个,并进行打印输出,出现了问题:
因为菜单是菜品的集合形式,两家采用的集合不同:

  • 早餐店的菜单是使用ArrayList实现的
  • 餐厅使用的是数组实现的

由于合并,服务员代码中打印菜单的方法printMenu()是要把所有菜品全都列出来,因为两种集合遍历的方式不同,需要两个不同的循环。
如图3:ArrayList集合的遍历,通过内置的get()方法获取某个位置的元素

在这里插入图片描述

如图4:数组的遍历通过下标来实现获取元素

在这里插入图片描述
现在的做法如果采用两种循环来遍历集合,但是抽象出来就是遍历集合的动作,所以采用了迭代器模式,将不同的遍历动作抽象出相同的动作。这样以后还有别的菜单集合加入,不用修改太多的代码。

2.1 DIY迭代器

2.1.1 类图

如图5,

  • 迭代器接口Iterator只有hasNext()next()两个函数
  • 接口Iterator有两个实例分别是PancakeHouseMenuIteratorDinerMenuItator,是两家餐厅的菜单迭代器。
  • PancakeHouseMenuDinerMenu是两家餐厅的菜单,可以看到,他们都有MenuItem的集合,只是表现形式不同,都有一个共同的方法createIterator(),都是用来创建相应的迭代器。
  • 这两个菜单有这么多的相似之处,在后面会将其改进。

在这里插入图片描述

2.1.2 代码

1.迭代器接口和实现,连个实例实现相应的hasNext()next()方法

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


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

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

    public Object next() {
        MenuItem menuItem = items[position];
        position = position + 1;
        return menuItem;
    }

    public boolean hasNext() {
        if (position >= items.length || items[position] == null) {
            return false;
        } else {
            return true;
        }
    }
}

public class PancakeHouseIterator implements Iterator {
    ArrayList<MenuItem> list;
    int position = 0;

    public PancakeHouseIterator(ArrayList<MenuItem> list) {
        this.list = list;
    }

    @Override
    public boolean hasNext() {
        if (position >= list.size() || list.get(position) == null) {
            return false;
        }else{
            return true;
        }
    }

    @Override
    public Object next() {
        MenuItem menuItem = list.get(position);
        position = position + 1;
        return menuItem;
    }
}

2.菜单项和两个菜单代码

public class MenuItem {
    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 class DinerMenu {
    static final int MAX_ITEMS = 6;
    int numberOfItems = 0;
    MenuItem[] menuItems;

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

        addItem("Vegetarian BLT",
                "(Fakin') Bacon with lettuce & tomato on whole wheat", true, 2.99);
        addItem("BLT",
                "Bacon with lettuce & tomato on whole wheat", false, 2.99);
        addItem("Soup of the day",
                "Soup of the day, with a side of potato salad", false, 3.29);
        addItem("Hotdog",
                "A hot dog, with saurkraut, relish, onions, topped with cheese",
                false, 3.05);
        addItem("Steamed Veggies and Brown Rice",
                "Steamed vegetables over brown rice", true, 3.99);
        addItem("Pasta",
                "Spaghetti with Marinara Sauce, and a slice of sourdough bread",
                true, 3.89);
    }

    public void addItem(String name, String description, boolean vegetarian, double price) {
        MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
        if (numberOfItems >= MAX_ITEMS) {
            System.err.println("Sorry, menu is full!  Can't add item to menu");
        } else {
            menuItems[numberOfItems] = menuItem;
            numberOfItems = numberOfItems + 1;
        }
    }

    public Iterator createIterator() {
        return new DinerMenuIterator(menuItems);
        //return new AlternatingDinerMenuIterator(menuItems);
    }

    // other menu methods here
}


public class PancakeHouseMenu {
    ArrayList<MenuItem> menuItems;

    public PancakeHouseMenu() {
        menuItems = new ArrayList<MenuItem>();

        addItem("K&B's Pancake Breakfast",
                "Pancakes with scrambled eggs, and toast",
                true,
                2.99);

        addItem("Regular Pancake Breakfast",
                "Pancakes with fried eggs, sausage",
                false,
                2.99);

        addItem("Blueberry Pancakes",
                "Pancakes made with fresh blueberries",
                true,
                3.49);

        addItem("Waffles",
                "Waffles, with your choice of blueberries or strawberries",
                true,
                3.59);
    }

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

    public Iterator createIterator() {
        return new PancakeHouseIterator(menuItems);
        //return new AlternatingDinerMenuIterator(menuItems);
    }
}

3.服务员代码和测试

public class Waitress {
    PancakeHouseMenu pancakeHouseMenu;
    DinerMenu dinerMenu;

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

    public void printMenu() {
        Iterator pancakeIterator = this.pancakeHouseMenu.createIterator();
        Iterator dinerIterator = this.dinerMenu.createIterator();

        System.out.println("MENU\n--------\nBREAKFAST");
        printMenu(pancakeIterator);
        System.out.println("\nLUNCH");
        printMenu(dinerIterator);

    }

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

public class MenuTestDrive {
    public static void main(String[] args) {
        PancakeHouseMenu pancakeHouseMenu=new PancakeHouseMenu();
        DinerMenu dinerMenu=new DinerMenu();

        Waitress waitress=new Waitress(pancakeHouseMenu,dinerMenu);
        waitress.printMenu();
    }
}

2.2 使用内部Iterator实现

java内部提供了一个迭代器接口,比上一个迭代器接口多了一个remove()方法。该方法是用来删除当前位置的元素。这让菜单的菜品增加和删除提高了灵活性。

2.2.1 类图

如图6:

  • 早餐店的菜单是用ArrayList实现的,ArrayList已经实现了Iterator接口,所以不需PancakeHouseMenuIterator这个类。
  • 午餐店的菜单是数组实现的,没有实现迭代器接口,所以还是需要DinerMenuIterator类实现Interator接口。只是这次需要多实现一个remove方法。
  • 因为刚才发现每个菜单都需要使用createIterator(),所以将其抽象出来,形成接口Menu,此接口只有一个createIterator()方法。

在这里插入图片描述

2.2.2 代码

1.菜单接口和实现类

public interface Menu {
    public Iterator<?> createIterator();
}

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

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

        addItem("Vegetarian BLT",
                "(Fakin') Bacon with lettuce & tomato on whole wheat", true, 2.99);
        addItem("BLT",
                "Bacon with lettuce & tomato on whole wheat", false, 2.99);
        addItem("Soup of the day",
                "Soup of the day, with a side of potato salad", false, 3.29);
        addItem("Hotdog",
                "A hot dog, with saurkraut, relish, onions, topped with cheese",
                false, 3.05);
        addItem("Steamed Veggies and Brown Rice",
                "Steamed vegetables over brown rice", true, 3.99);
        addItem("Pasta",
                "Spaghetti with Marinara Sauce, and a slice of sourdough bread",
                true, 3.89);
    }

    public void addItem(String name, String description, boolean vegetarian, double price) {
        MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
        if (numberOfItems >= MAX_ITEMS) {
            System.err.println("Sorry, menu is full!  Can't add item to menu");
        } else {
            menuItems[numberOfItems] = menuItem;
            numberOfItems = numberOfItems + 1;
        }
    }

    public Iterator createIterator() {
        return new DinerMenuIterator(menuItems);
        //return new AlternatingDinerMenuIterator(menuItems);
    }

    // other menu methods here
}


public class PancakeHouseMenu  implements Menu{
    ArrayList<MenuItem> menuItems;

    public PancakeHouseMenu() {
        menuItems = new ArrayList<MenuItem>();

        addItem("K&B's Pancake Breakfast",
                "Pancakes with scrambled eggs, and toast",
                true,
                2.99);

        addItem("Regular Pancake Breakfast",
                "Pancakes with fried eggs, sausage",
                false,
                2.99);

        addItem("Blueberry Pancakes",
                "Pancakes made with fresh blueberries",
                true,
                3.49);

        addItem("Waffles",
                "Waffles, with your choice of blueberries or strawberries",
                true,
                3.59);
    }

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

    public Iterator createIterator() {
        return menuItems.iterator();
        //return new AlternatingDinerMenuIterator(menuItems);
    }
}

public class MenuItem {
    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;
    }
}

2.DinerMenuIterator

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

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

    public Object next() {
        MenuItem menuItem = items[position];
        position = position + 1;
        return menuItem;
    }

    public boolean hasNext() {
        if (position >= items.length || items[position] == null) {
            return false;
        } else {
            return true;
        }
    }

    public void remove() {
        if (position <= 0) {
            throw new IllegalStateException("You can't remove an item until you've done at least one next()");
        }
        if (items[position] != null) {
            for (int i = position - 1; i < (items.length - 1); i++) {
                items[i] = items[i + 1];
            }
            items[items.length - 1] = null;
        }
    }
}

3.服务员和测试代码


public class Waitress {
    Menu pancakeHouseMenu;
    Menu dinerMenu;

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

    public void printMenu() {
        Iterator pancakeIterator = this.pancakeHouseMenu.createIterator();
        Iterator dinerIterator = this.dinerMenu.createIterator();

        System.out.println("MENU\n--------\nBREAKFAST");
        printMenu(pancakeIterator);
        System.out.println("\nLUNCH");
        printMenu(dinerIterator);

    }

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

public class MenuTestDrive {
    public static void main(String[] args) {
        Menu pancakeHouseMenu=new PancakeHouseMenu();
        Menu dinerMenu=new DinerMenu();

        Waitress waitress=new Waitress(pancakeHouseMenu,dinerMenu);
        waitress.printMenu();
    }
}

二、组合模式

1.概念

1.1 定义

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

如图7:

  • Node节点时带有子元素的节点
  • 没有子元素的节点成为叶子节点
  • 这样就忽略了组合项和组合的差异

在这里插入图片描述

1.2 类图

如图8,

  • 客户使用Component接口操作组合中的对象
  • 定义Component抽象类,适用于组合和组合项的一个接口,提供默认实现。
  • 叶子结点LeafComposite组合对于接口中的某些方法没有实现,可能对它本身没有意义。
    在这里插入图片描述

2. 引例(继续餐厅合并例子)

2.1 合并咖啡厅

刚刚合并的餐厅因为资金到位,又合并一家咖啡厅,咖啡厅的菜单使用的Hashtable集合实现,这种集合的是一种key-value键值对的数据格式实现的。如图9:

在这里插入图片描述

咖啡厅的菜单同样实现Menu接口就可以,但是实现createIterator()的代码如下:

public Iterator createIterator(){
	return menuItems.values().iterator();
}

这样服务员中的print()方法如下:

	public void printMenu() {
        Iterator pancakeIterator = this.pancakeHouseMenu.createIterator();
        Iterator dinerIterator = this.dinerMenu.createIterator();
		Iterator cafeIterator = this.cafeMenu.createIterator();

        System.out.println("MENU\n--------\nBREAKFAST");
        printMenu(pancakeIterator);

        System.out.println("\nLUNCH");
        printMenu(dinerIterator);

		System.out.println("\nCafe");
        printMenu(cafeIterator);

    }

出现问题:
每一次有新的菜单加入,就会改动服务员代码,违反了开放-关闭原则,每一次增加和删除菜单,就会改动print()方法。

2.2 优化餐厅菜单

将餐厅的三种菜单都放入ArrayList集合中。这样就解决了三次打印的菜单的重复操作,也解决了每一次增加菜单的对服务员代码的修改。优化之后的服务员代码

public class Waitress{
	ArrayList menus;
	public Waitress(ArrayList menus){
		this.menus = menus;
	}

	public void printMenu(){
		Iterator menuIterator = menus.iterator;
		while(menuIterator.hasNext()){
			Menu menu = (Menu)menuIterator.next();
			printMenu(menu.createIterator());
		}
	}

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

2.3 增加甜品菜单

现在需要在餐厅的菜单中将甜点菜品单独列出来,形成甜点菜单,加入餐厅菜单中。如图10:

在这里插入图片描述

因为餐厅菜单使用的数组实现的,数组元素是菜品MenuItem,现在加入一个数组肯定是行不通的。类型不同。

这是就需要改动现有设计了,加入组合模式。将菜单设计成为树形结构,将菜单项和菜单差异抹去,突出其共同点,如图11:
在这里插入图片描述

2.4新设计类图

如图12:

  • 菜单和菜单项都实现MenuComponent抽象类,根据自己的需要,实现相应的方法。
  • 菜品MenuItem作为叶子节点,叶子节点就只实现getName(),getDescription(),getPrice(),isVegetarian()print()五个方法
  • 菜单Menu作为一个子节点,实现了与其相关的6个方法:getName(),getDescription(),print(),add(Component),remove(Component)getChild(int)

在这里插入图片描述

2.5 代码

1.抽象类MenuComponent类中每一个方法都有默认实现,抛出UnsupportedOperationException异常

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

}

2.菜单的实现

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

3.菜单项的实现

public class MenuItem extends MenuComponent {
    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;
    }

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

4.服务员和测试代码

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章