设计之禅——迭代器模式

时间:2022-07-24
本文章向大家介绍设计之禅——迭代器模式,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

前言

迭代器想必大家不会陌生,作为Java中内置的API,平时我们使用的也是非常多的。但你是否曾想过它和迭代器模式有什么关联?并且Java中已经有for循环遍历,为什么还会需要这样一个类?

定义

Java中大部分的数据结构都有返回iterator的方法,且都实现自Java.util.Iterator接口,这就是迭代器模式的实现和应用。那迭代器模式是什么?有什么用呢?

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

通过定义我们可以发现关键 “不暴露内部表示”,意思是在某些我们不愿意暴露我们内部结构的场合,for循环就无法使用了,那我们就需要提供一个遍历的工具,而其他人在使用迭代器遍历该聚合对象的内部元素时,就不用关心其内部是用何种数据类型来存储数据的,也就将遍历和元素类型解耦了。那么当有多个聚合对象,并且其内部存储结构各不相同时,客户端也不必再为数据类型而纠结。说了这么多,下面就用代码来演示吧。

Coding

普通实现

假设现在有A、B两家餐厅,A只售卖早餐,B则只售卖正餐,现在有个外卖平台将会接入两家餐厅的数据展示给客户,但是A是用数组存储的,B是用ArrayList存储的。先来看看原始的实现方法:

public class A {

    private int count;
    private MenuItem[] menuItems = new MenuItem[10];

    public A() {
        add("包子", 43);
        add("馒头", 32);
        add("豆浆", 34);
        add("牛奶", 26);
    }

    public void add(String name, double price) {
        MenuItem menuItem = new MenuItem(name, price);
        if (count >= menuItems.length) {
            System.out.println("Sorry!You can't add the new menuItem!");
        } else {
            menuItems[count++] = menuItem;
        }
    }

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

}

public class B {

    private ArrayList<MenuItem> menuItems = new ArrayList<>();

    public B() {
        add("红烧茄子", 53);
        add("青椒肉丝", 46);
        add("佛跳墙", 35);
        add("红烧鲫鱼", 46);
    }

    public void add(String name, double price) {
        menuItems.add(new MenuItem(name, price));
    }

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

}

A和B都提供了get操作用于获取数据,

public class Takeaway {

    private A a = new A();
    private B b = new B();

    public void print() {
        MenuItem[] menuItems = a.getMenuItems();
        for (int i = 0; i < menuItems.length; i++) {
            MenuItem menuItem = menuItems[i];
            if (menuItem == null) {
                break;
            }
            System.out.println("name:" + menuItem.getName() + ", price:" + menuItem.getPrice());
        }

        ArrayList<MenuItem> tickets1 = b.getMenuItems();
        for (int i = 0; i < tickets1.size(); i++) {
            MenuItem menuItem = tickets1.get(i);
            System.out.println("name:" + menuItem.getName() + ", price:" + menuItem.getPrice());
        }
    }

}

外卖平台在显示菜单的时候就需要使用两次for循环来处理,因为他们的数据类型不一致,而且外卖平台必须要清楚原商家是如何存储数据的,在将来也还会有很多商家加入进来,难道都用for循环去遍历么,这显然是一个糟糕的设计。

迭代器实现

首先我们需要创建一个迭代器接口,并提供公共的方法:

public interface Iterator {

    boolean hasNext();

    Object next();

}

接着为每个商户创建一个迭代器类,也就是将遍历封装,让每个类值承担自己的责任,也就是单一职责原则(让类保持只有一个被改变的原因,如果将遍历过程放到商家类内部,那么就有了两个使它改变的理由)

public class AIterator implements Iterator {

    private int ind;
    private MenuItem[] menuItems;

    public AIterator(MenuItem[] menuItems) {
        this.menuItems = menuItems;
    }

    @Override
    public boolean hasNext() {
        if (ind >= menuItems.length || menuItems[ind] == null) {
            return false;
        }
        return true;
    }

    @Override
    public Object next() {
        return menuItems[ind++];
    }
}

B商家的迭代器应该难不倒你,将迭代器创建好后,我们还需要对商家类进行修改,即增加一个返回迭代器的方法供外卖平台调用:

public class A {

    private int count;
    private MenuItem[] menuItems = new MenuItem[10];

    public A() {
        add("包子", 43);
        add("馒头", 32);
        add("豆浆", 34);
        add("牛奶", 26);
    }

    public void add(String name, double price) {
        MenuItem menuItem = new MenuItem(name, price);
        if (count >= menuItems.length) {
            System.out.println("Sorry!You can't add the new menuItem!");
        } else {
            menuItems[count++] = menuItem;
        }
    }

    public Iterator createIterator() {
        return new AIterator(menuItems);
    }
}

最后再来看看外卖平台的变化:

public class Takeaway {

    private A a;
    private B b;

    public Takeaway(A a, B b) {
        this.a = a;
        this.b = b;
    }

    public void print() {
        Iterator iterator = a.createIterator();
        print(iterator);

        Iterator iterator1 = b.createIterator();
        print(iterator1);
    }

    private void print(Iterator iterator) {
        while (iterator.hasNext()) {
            MenuItem item = (MenuItem) iterator.next();
            System.out.println("name:" + item.getName() + ", price:" + item.getPrice());
        }
    }


}

我们可以发现第三方平台不用在关注商家内部的具体实现了,只需要使用Iterator提供的方法即可,不过这里需要注意的是,虽然我们将商家内部实现与第三方平台解耦,但是如果加入新的商家,这里的代码又需要改变,并且多次调用print显示菜单依然看起来不怎么雅观,那这个问题该如何解决呢? 出现这个问题的原因主要在于我们是针对商家的具体实现编程,而不是接口,所以我们创建一个接口,让所有的商家实现它即可,同时也需要对外卖类做一些修改。

public class Takeaway {

    // 这里不一定适用数组,list等也可
    private Menu[] menus;

    public Takeaway(Menu... menus) {
        this.menus = menus;
    }

    public void print() {
        for (Menu menu : menus) {
            print(menu.createIterator());
        }
    }

    private void print(Iterator iterator) {
        while (iterator.hasNext()) {
            MenuItem item = (MenuItem) iterator.next();
            System.out.println("name:" + item.getName() + ", price:" + item.getPrice());
        }
    }


}

这样不论在加入多少商家,也不管它们的数据结构是如何的,我们都不用再修改代码了,perfect!不过上面的实现我没有使用Java内置的API,主要是能更直观的看到迭代器的创建过程,帮助理解,在以后使用Java的Iterator时也能更加的得心应手。

总结

迭代器是一种很简单也很常用的模式,它利用多态的机制允许在不暴露内部结构的情况下顺序地访问聚合的元素,同时我们也从中学习到了一个设计原则——单一职责原则,在设计类时应该尽量保证类只做自己范围职责内的事情。