設計模式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();
    }
}

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