【一起学系列】之迭代器&组合:虽然有点用不上啦

1,369 阅读7分钟

迭代器模式

意图

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

迭代器模式的诞生

【产品】:嘿,有一个好消息,咱们旗下的餐厅把月巴克的咖啡店吞并了!太棒了!年终奖稳了!

【开发】:Yeah!Yeah!Yeah!

【产品】:但是他们好像反应一个问题,月巴克的点餐系统好像不兼容我们的体系,怎么回事?不就是一个菜单吗?

【开发】:Oh!No!一定它们的 数据结构 不一样导致的,遍历出现了问题!

【产品】:那怎么办?BOSS,你们一起想想办法吧!

【开发】:老大,我们能不能把遍历方法抽取出来啊?我们遍历操作就可以不用考虑各种细节了,只需要管遍历类就好了。

【BOSS】:什么遍历类的,这叫 迭代器 好吗!其实JDK对于迭代器已经维护的很好了,但是咱们这业务也有一点特殊性,就按你说的办吧,办不好的话,刚才说的年终奖就没了。

【开发】:哦,好的(脸上笑嘻嘻,心里MMP)

HeadFirst 核心代码

自己整一个迭代器

定义迭代器持有者 非必须代码

/**
 * ******************************
 * description:  迭代器持有者
 * ******************************
 */
public interface MyContainer {

    MyIterator getIterator();
}

迭代器接口

/**
 * ******************************
 * description:  迭代器接口
 * ******************************
 */
public interface MyIterator {

    boolean hasNext();

    Object next();
}

迭代器工作类 食物菜单

public class FoodRepository implements MyContainer {

    String[] names = {"宫保鸡丁", "麻辣香锅", "油闷大虾"};

    @Override
    public MyIterator getIterator() {
        return new NameIterator();
    }

    private class NameIterator implements MyIterator {

        private int index;

        @Override
        public boolean hasNext() {
            return index < names.length;
        }

        @Override
        public Object next() {
           return hasNext() ? names[index++] : null;
        }

        NameIterator() {
            index = 0;
        }
    }
}

迭代器工作类 咖啡菜单

public class CoffeeRepository implements MyContainer {

    List<String> names = Arrays.asList("雀巢咖啡", "黑糖玛奇朵", "半点寂寞");

    @Override
    public MyIterator getIterator() {
        return new NameIterator();
    }

    private class NameIterator implements MyIterator {

        private int index;

        @Override
        public boolean hasNext() {
            return index < names.size();
        }

        @Override
        public Object next() {
           return hasNext() ? names.get(index++) : null;
        }

        NameIterator() {
            index = 0;
        }
    }
}

测试类 观察调用的表现形式

public class App {

    public static void main(String[] args){
        // 餐厅菜单
        FoodRepository food = new FoodRepository();
        MyIterator foodIterator = food.getIterator();
        while (foodIterator.hasNext()) {
            System.out.println("Food: -> " + foodIterator.next());
        }

        CodeUtils.spilt();

        // 咖啡菜单
        CoffeeRepository coffee = new CoffeeRepository();
        MyIterator coffeeIterator = coffee.getIterator();
        while (coffeeIterator.hasNext()) {
            System.out.println("Coffee: -> " + coffeeIterator.next());
        }
    }
}

JDK中的迭代

public class App {
    
    public static void main(String[] args){
        // JDK
        List<String> names = Arrays.asList("Han", "John", "Tomams");
        Iterator<String> iterable = names.iterator();
        while (iterable.hasNext()) {
            System.out.println("JDK Iterator: -> " + iterable.next());
        }

        CodeUtils.spilt();

        // JDK
        names.forEach(s -> System.out.println("JDK forEach: -> " + s));
    }
}

因此对于业务上没有什么要求且常见的数据结构,我们不再需要自行定义迭代器

迭代器模式的设计思路:

  • Iterator 迭代器
  • Concretelterator 具体迭代器
  • Aggregate 集合
  • ConcreteAggregate 具体集合

简单来说,

  1. 我们需要明确集合的类型(数组,链表,Map,树结构或者普通List)
  2. 我们需要定义迭代器的行为,是否有下一个(遍历完成),取值,移除等等
  3. 遍历的行为或者算法在具体的迭代器中实现,根据不同的数据结构和业务要求完成编码,实现访问一致,但细节不同的效果

如果看着有点模棱两可,就看完本文后,访问专题设计模式开源项目,里面有具体的代码示例,链接在最下面

遵循的设计原则

  • 单一职责原则

说明:迭代器类在设计中仅仅包含集合迭代的作用,它是把原本数据结构中的遍历抽取出来,达到 高内聚 的效果。

所谓高内聚:当一个模块或一个类被设计成只支持一组相关功能时,我们说它具有 高内聚 的特征。

什么场景适合使用

  • 访问一个聚合对象的内容而无需暴漏它的内部表示
  • 支持对聚合对象的多种遍历
  • 为遍历不同的聚合结构提供一个统一的接口

Code/生活中的实际应用

举一个不是很恰当的例子,我们都用自动贩卖机买过水,付钱之后它会自动滚出来,大家有没有想过它是怎么实现这个效果的呢?它支持瓶装的,罐装的,甚至还支持袋装的,方便面,口红等等五花八门的产品,它的内部结构可能都各不相同,但是最终的表现效果就是我们直接从出口处拿即可,这是不是迭代器模式的一种体现呢?

迭代器模式UML图

组合模式

意图

将对象组合成树形结构以表示 “部分-整体” 的层次结构,Composite使得用户对单个对象和组合对象的使用具有一致性

说人话:想想Java里的File类

组合模式的误区

组合模式 不是 一堆模式的组合!

组合模式的诞生

【开发】:老大,我在写菜单类的时候感觉好痛苦啊!

【BOSS】:怎么了?

【开发】:菜单有真正的菜品,还有父级菜单啊,它们俩得维护两套逻辑,混在一起好难受!

【BOSS】:你在操作文件的时候怎么不觉得难受?你咋不动动脑子想着抽象一下啊!

【开发】:对啊!我去改代码!

HeadFirst 核心代码

定义抽象行为

/**
 * ******************************
 * description:  定义抽象行为
 * ******************************
 */
public abstract class MenuComponent {

    public String name;

    /***
     * 添加
     */
    public abstract void add(MenuComponent component) throws Exception;

    /***
     * 移除
     */
    public abstract void remove(MenuComponent component) throws Exception;

    /***
     * 获取菜单名
     */
    public abstract String getName();

    /***
     * 获取子菜单
     */
    public abstract MenuComponent getChild(int i) throws Exception;

    /***
     * 打印菜单
     */
    public abstract void print();
}

实现 “整体”

public class Menu extends MenuComponent{

    List<MenuComponent> menuComponents = new ArrayList<>();

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

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

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

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

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

    @Override
    public void print() {
        System.out.println("当前菜单项: " + getName());
        for (MenuComponent component : menuComponents) {
            component.print();
        }
    }
}

实现 "部分"

public class MentItem extends MenuComponent{

    public MentItem(String name) {
        this.name = name;
    }

    @Override
    public void add(MenuComponent component) throws Exception {
        throw new Exception("无法添加");
    }

    @Override
    public void remove(MenuComponent component) throws Exception {
        throw new Exception("无法移除");
    }

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

    @Override
    public MenuComponent getChild(int i) throws Exception {
        throw new Exception("无子节点");
    }

    @Override
    public void print() {
        System.out.println("    食物名: " + getName());
    }
}

测试类

public class App {

    /***
     * 推荐代码阅读顺序:
     *
     * @see MenuComponent
     * @see Menu
     * @see MentItem
     */
    public static void main(String[] args) {
        Menu meat = new Menu("炒菜类");

        MentItem item1 = new MentItem("宫保鸡丁");
        MentItem item2 = new MentItem("剁椒鸡蛋");
        MentItem item3 = new MentItem("鱼香肉丝");

        Menu vegetable = new Menu("素食");
        MentItem v1 = new MentItem("酸辣土豆丝");
        MentItem v2 = new MentItem("爆炒包菜");

        meat.add(item1);
        meat.add(item2);
        meat.add(item3);

        vegetable.add(v1);
        vegetable.add(v2);

        meat.add(vegetable);

        meat.print();
    }
}


/***
 * 输出内容:
 *
 * 当前菜单项: 炒菜类
 *     食物名: 宫保鸡丁
 *     食物名: 剁椒鸡蛋
 *     食物名: 鱼香肉丝
 * 当前菜单项: 素食
 *     食物名: 酸辣土豆丝
 *     食物名: 爆炒包菜
 */

组合模式的设计思路:

  • Component 为组合的对象声明接口或抽象类
  • Leaf 叶子节点(最小单元)
  • Composite 组合节点(即还有子节点的节点)
  • Client 客户端,调用方

简单来说,

  1. 当我们需要树形结构时,抽象叶子节点和组合节点(有子节点的节点)的共同行为
  2. 让两者实现同一个接口

如果看着有点模棱两可,就看完本文后,访问专题设计模式开源项目,里面有具体的代码示例,链接在最下面

什么场景适合使用

  • 需要表示对象的部分-整体层次结构
  • 希望用户忽略组合对象与单个对象的不同,用户统一地使用组合结构中所有对象

Code/生活中的实际应用

依然是一个不太恰当的例子,我们在操作文件和文件夹的时候,都有其移动,复制,重命名,查看文件大小等等功能,对于Java来说,它的底层实现是有一个 是否是文件夹 的方法来区分,但实际上这也是组合模式的根本思想,即对于表示 部分 的对象,和 整体 的对象,拥有统一的操作行为

组合模式的UML图

总结

  • 迭代器模式:该模式在JDK中已经封装的非常好,我们其实不太需要再自行处理,不过在处理特殊数据结构时这种统一操作的思想仍然值得借鉴
  • 组合模式:组合模式仅在需要树形结构的场景下可发挥巨大的作用,同样的,它规范不同类型对象的行为,统一操作的思想,值得我们借鉴

相关代码链接

GitHub地址

  • 兼顾了《HeadFirst》以及《GOF》两本经典书籍中的案例
  • 提供了友好的阅读指导

本文使用 mdnice 排版