重学Java设计模式-行为型模式-迭代器模式

news2025/1/18 10:09:26

重学Java设计模式-行为型模式-迭代器模式

内容摘自:https://bugstack.cn/md/develop/design-pattern/2020-06-23-重学 Java 设计模式《实战迭代器模式》.html#重学-java-设计模式-实战迭代器模式「模拟公司组织架构树结构关系-深度迭代遍历人员信息输出场景」

迭代器模式介绍

迭代器模式,图片来自 refactoringguru.cn

  • 图片来自:https://refactoringguru.cn/design-patterns/iterator(opens new window)

迭代器模式,常见的就是我们日常使用的iterator遍历。虽然这个设计模式在我们的实际业务开发中的场景并不多,但却几乎每天都要使用jdk为我们提供的list集合遍历。另外增强的for循环虽然是循环输出数据,但是他不是迭代器模式。迭代器模式的特点是实现Iterable接口,通过next的方式获取集合元素,同时具备对元素的删除等操作。而增强的for循环是不可以的。

这种设计模式的优点是可以让我们以相同的方式,遍历不同的数据结构元素,这些数据结构包括;数组链表等,而用户在使用遍历的时候并不需要去关心每一种数据结构的遍历处理逻辑,从让使用变得统一易用。

案例场景模拟

场景模拟;公司树形组织架构

在本案例中我们模拟迭代遍历输出公司中树形结构的组织架构关系中雇员列表

大部分公司的组织架构都是金字塔结构,也就这种树形结构,分为一级、二级、三级等部门,每个组织部门由雇员填充,最终体现出一个整体的树形组织架构关系。

一般我们常用的遍历就是jdk默认提供的方法,对list集合遍历。但是对于这样的偏业务特性较大的树形结构,如果需要使用到遍历,那么就可以自己来实现。接下来我们会把这个组织层次关系通过树形数据结构来实现,并完成迭代器功能。

迭代器模式遍历组织结构

在实现迭代器模式之前可以先阅读下javalist方法关于iterator的实现部分,几乎所有的迭代器开发都会按照这个模式来实现,这个模式主要分为以下几块;

  1. Collection,集合方法部分用于对自定义的数据结构添加通用方法;addremoveiterator等核心方法。
  2. Iterable,提供获取迭代器,这个接口类会被Collection继承。
  3. Iterator,提供了两个方法的定义;hasNextnext,会在具体的数据结构中写实现方式。

除了这样通用的迭代器实现方式外,我们的组织关系结构树,是由节点和节点间的关系链构成,所以会比上述的内容多一些入参。

1. 工程结构

itstack-demo-design-15-00
└── src
    ├── main
    │   └── java
    │       └── org.itstack.demo.design
    │           ├── group
    │           │	├── Employee.java
    │           │	├── GroupStructure.java
    │           │	└── Link.java
    │           └──  lang
    │            	├── Collection.java
    │            	├── Iterable.java
    │            	└── Iterator.java
    └── test
        └── java
            └── org.itstack.demo.design.test
                └── ApiTest.java

迭代器模式模型结构

迭代器模式模型结构

  • 以上是我们工程类图的模型结构,左侧是对迭代器的定义,右侧是在数据结构中实现迭代器功能。
  • 关于左侧部分的实现与jdk中的方式是一样的,所以在学习的过程中可以互相参考,也可以自己扩展学习。
  • 另外这个遍历方式一个树形结构的深度遍历,为了可以更加让学习的小伙伴容易理解,这里我实现了一种比较简单的树形结构深度遍历方式。后续读者也可以把遍历扩展为横向遍历也就是宽度遍历。

2. 代码实现

2.1 雇员实体类

/**
 * 雇员
 */
public class Employee {

    private String uId;   // ID
    private String name;  // 姓名
    private String desc;  // 备注
    
    // ...get/set
}
  • 这是一个简单的雇员类,也就是公司员工的信息类,包括必要的信息;id、姓名、备注。

2.2 树节点链路

/**
 * 树节点链路
 */
public class Link {

    private String fromId; // 雇员ID
    private String toId;   // 雇员ID    
    
    // ...get/set
}
  • 这个类用于描述结构树中的各个节点之间的关系链,也就是A to BB to CB to D,以此描述出一套完整的树组织结构。

2.3 迭代器定义

public interface Iterator<E> {

    boolean hasNext();

    E next();
    
} 
  • 这里的这个类和javajdk中提供的是一样的,这样也方面后续读者可以对照listIterator进行源码学习。
  • 方法描述;hasNext,判断是否有下一个元素、next,获取下一个元素。这个在list的遍历中是经常用到的。

2.4 可迭代接口定义

public interface Iterable<E> {

    Iterator<E> iterator();

}
  • 这个接口中提供了上面迭代器的实现Iterator的获取,也就是后续在自己的数据结构中需要实现迭代器的功能并交给Iterable,由此让外部调用方进行获取使用。

2.5 集合功能接口定义

public interface Collection<E, L> extends Iterable<E> {

    boolean add(E e);

    boolean remove(E e);

    boolean addLink(String key, L l);

    boolean removeLink(String key);

    Iterator<E> iterator();

}
  • 这里我们定义集合操作接口;Collection,同时继承了另外一个接口Iterable的方法iterator()。这样后续谁来实现这个接口,就需要实现上述定义的一些基本功能;添加元素删除元素遍历
  • 同时你可能注意到这里定义了两个泛型<E, L>,因为我们的数据结构一个是用于添加元素,另外一个是用于添加树节点的链路关系。

2.6 (核心)迭代器功能实现

public class GroupStructure implements Collection<Employee, Link> {

    private String groupId;                                                 // 组织ID,也是一个组织链的头部ID
    private String groupName;                                               // 组织名称
    private Map<String, Employee> employeeMap = new ConcurrentHashMap<String, Employee>();  // 雇员列表
    private Map<String, List<Link>> linkMap = new ConcurrentHashMap<String, List<Link>>();  // 组织架构关系;id->list
    private Map<String, String> invertedMap = new ConcurrentHashMap<String, String>();       // 反向关系链

    public GroupStructure(String groupId, String groupName) {
        this.groupId = groupId;
        this.groupName = groupName;
    }

    public boolean add(Employee employee) {
        return null != employeeMap.put(employee.getuId(), employee);
    }

    public boolean remove(Employee o) {
        return null != employeeMap.remove(o.getuId());
    }

    public boolean addLink(String key, Link link) {
        invertedMap.put(link.getToId(), link.getFromId());
        if (linkMap.containsKey(key)) {
            return linkMap.get(key).add(link);
        } else {
            List<Link> links = new LinkedList<Link>();
            links.add(link);
            linkMap.put(key, links);
            return true;
        }
    }

    public boolean removeLink(String key) {
        return null != linkMap.remove(key);
    }

    public Iterator<Employee> iterator() {

        return new Iterator<Employee>() {

            HashMap<String, Integer> keyMap = new HashMap<String, Integer>();

            int totalIdx = 0;
            private String fromId = groupId;  // 雇员ID,From
            private String toId = groupId;   // 雇员ID,To

            public boolean hasNext() {
                return totalIdx < employeeMap.size();
            }

            public Employee next() {
                List<Link> links = linkMap.get(toId);
                int cursorIdx = getCursorIdx(toId);

                // 同级节点扫描
                if (null == links) {
                    cursorIdx = getCursorIdx(fromId);
                    links = linkMap.get(fromId);
                }

                // 上级节点扫描
                while (cursorIdx > links.size() - 1) {
                    fromId = invertedMap.get(fromId);
                    cursorIdx = getCursorIdx(fromId);
                    links = linkMap.get(fromId);
                }

                // 获取节点
                Link link = links.get(cursorIdx);
                toId = link.getToId();
                fromId = link.getFromId();
                totalIdx++;

                // 返回结果
                return employeeMap.get(link.getToId());
            }
             
            // 给每个层级定义宽度遍历进度
            public int getCursorIdx(String key) {
                int idx = 0;
                if (keyMap.containsKey(key)) {
                    idx = keyMap.get(key);
                    keyMap.put(key, ++idx);
                } else {
                    keyMap.put(key, idx);
                }
                return idx;
            }
        };
    }

}
  • 以上的这部分代码稍微有点长,主要包括了对元素的添加和删除。另外最重要的是对遍历的实现new Iterator<Employee>
  • 添加和删除元素相对来说比较简单,使用了两个map数组结构进行定义;雇员列表组织架构关系;id->list。当元素添加元素的时候,会分别在不同的方法中向map结构中进行填充指向关系(A->B),也就构建出了我们的树形组织关系。

迭代器实现思路

  1. 这里的树形结构我们需要做的是深度遍历,也就是左侧的一直遍历到最深节点。
  2. 当遍历到最深节点后,开始遍历最深节点的横向节点。
  3. 当横向节点遍历完成后则向上寻找横向节点,直至树结构全部遍历完成。

3. 测试验证

3.1 编写测试类

@Test
public void test_iterator() { 
    // 数据填充
    GroupStructure groupStructure = new GroupStructure("1", "小傅哥");  
    
    // 雇员信息
    groupStructure.add(new Employee("2", "花花", "二级部门"));
    groupStructure.add(new Employee("3", "豆包", "二级部门"));
    groupStructure.add(new Employee("4", "蹦蹦", "三级部门"));
    groupStructure.add(new Employee("5", "大烧", "三级部门"));
    groupStructure.add(new Employee("6", "虎哥", "四级部门"));
    groupStructure.add(new Employee("7", "玲姐", "四级部门"));
    groupStructure.add(new Employee("8", "秋雅", "四级部门"));   
    
    // 节点关系 1->(1,2) 2->(4,5)
    groupStructure.addLink("1", new Link("1", "2"));
    groupStructure.addLink("1", new Link("1", "3"));
    groupStructure.addLink("2", new Link("2", "4"));
    groupStructure.addLink("2", new Link("2", "5"));
    groupStructure.addLink("5", new Link("5", "6"));
    groupStructure.addLink("5", new Link("5", "7"));
    groupStructure.addLink("5", new Link("5", "8"));       

    Iterator<Employee> iterator = groupStructure.iterator();
    while (iterator.hasNext()) {
        Employee employee = iterator.next();
        logger.info("{},雇员 Id:{} Name:{}", employee.getDesc(), employee.getuId(), employee.getName());
    }
}

3.2 测试结果

22:23:37.166 [main] INFO  org.itstack.demo.design.test.ApiTest - 二级部门,雇员 Id2 Name:花花
22:23:37.168 [main] INFO  org.itstack.demo.design.test.ApiTest - 三级部门,雇员 Id4 Name:蹦蹦
22:23:37.169 [main] INFO  org.itstack.demo.design.test.ApiTest - 三级部门,雇员 Id5 Name:大烧
22:23:37.169 [main] INFO  org.itstack.demo.design.test.ApiTest - 四级部门,雇员 Id6 Name:虎哥
22:23:37.169 [main] INFO  org.itstack.demo.design.test.ApiTest - 四级部门,雇员 Id7 Name:玲姐
22:23:37.169 [main] INFO  org.itstack.demo.design.test.ApiTest - 四级部门,雇员 Id8 Name:秋雅
22:23:37.169 [main] INFO  org.itstack.demo.design.test.ApiTest - 二级部门,雇员 Id3 Name:豆包

Process finished with exit code 0
  • 从遍历的结果可以看到,我们是顺着树形结构的深度开始遍历,一直到右侧的节点3雇员 Id:2、雇员 Id:4...雇员 Id:3

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/435140.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

R -- 用psych包做主成分分析

主成分分析 主成分分析是一种数据降维方式&#xff0c;他将大量相关变量转化为一组很少的不相关的变量&#xff0c;这些不相关的变量称为主成分。 人话版&#xff1a;给你发一个由18位数字组成的身份证号码&#xff0c;第1、2位数字表示所在省份的代码&#xff1b;第3、4位数…

深度学习笔记之残差网络(ResNet)

深度学习笔记之残差网络[ResNet] 引言引子&#xff1a;深度神经网络的性能问题核心问题&#xff1a;深层神经网络训练难残差网络的执行过程残差网络结构为什么能够解决核心问题残差网络的其他优秀性质 引言 本节将介绍残差网络( Residual Network,ResNet \text{Residual Netwo…

C#中用程序代码修改了datagridview中的数据,保存时只对光标当前行有保存解决办法

C#中DataGridView绑定了DataTable后&#xff0c;通过代码修改DataGridView中的数据&#xff0c;总有一行&#xff08;被修改过并被用户选中的行集合中索引为0的行&#xff09;不能被UpDate回数据库的问题和解决办法 长江黄鹤 2017-06-26 | 300阅读 | 1转藏 转藏全屏朗读分…

【JavaScript】初入前端,记录JavaScript学习过程

文章目录 一、下面将是你在本教程中学到的主要内容1. JavaScript直接写入HTML输出流2. JavaScript对事件的反应3. JavaScript&#xff1a;改变 HTML 内容4. JavaScript 改变HTML图像5. 改变HTML样式6. JavaScript 验证输入 二、JavaScript 语法学习1. JavaScript的位置2. 浏览器…

如何在硬盘上恢复已经删除的照片?

可以从硬盘恢复删除的照片吗&#xff1f; 旅行后&#xff0c;许多人倾向于将照片保存到另一个储存设备作为副本或备份。例如&#xff0c;将它们存储在外部硬盘上或将图片传输到电脑。但是在整理照片的时候&#xff0c;很可能不小心把照片删掉了&#xff0c;尤其是使用外接硬…

成都爱尔樊映川:视网膜上视觉最敏锐部位,出问题怎么办

视网膜后极部有一直径约 2mm 的浅漏斗状小凹陷区&#xff0c;该区含有丰富的叶黄素呈现椭圆形黄色&#xff0c;称为“黄斑”&#xff0c;是视网膜上视觉最敏锐的部位。 它主要与精细视觉及色觉等视功能有关。正常情况下&#xff0c;外界物体光线进入眼内&#xff0c;投影在黄斑…

【项目开发】二开系统,费了好大劲,才整好,规划业务逻辑太重要了

作为程序员一天不写代码&#xff0c;不会咋样&#xff0c;第二天会比较生疏&#xff0c;所以小编也不能闲着&#xff0c; 3天的时间吧&#xff0c;搞了个羽毛球场地预约系统&#xff0c;看着场地预约页面比较简单&#xff0c; 小编下班回家&#xff0c;搞了2个晚上&#xff0c…

开发常用的 Linux 命令2(文件的查看、搜索和权限)

开发常用的 Linux 命令2&#xff08;文件的查看、搜索和权限&#xff09; 作为开发者&#xff0c;Linux是我们必须掌握的操作系统之一。因此&#xff0c;在编写代码和部署应用程序时&#xff0c;熟练使用Linux命令非常重要。这些常用命令不得不会&#xff0c;掌握这些命令&…

【MySQL】增删改查基本操作

目录 上篇在这里喔~ 增删改查1 1.条件查询WHERE 1.查询语文成绩大于60的同学 2.查询语文成绩等于NULL的同学 3.查询语文成绩在60-90之间的同学 4.查询英语成绩等于30&#xff0c;77&#xff0c;90的所有同学 5.查询小锦鲤的语文成绩是否为空 6.查询姓孙的同学的所有信息 …

shiro漏洞复现及其攻击流量分析

前言 最近面试时经常被问到&#xff0c;每次说的都不太完美&#xff0c;现在再来复现一边。 shiro介绍 Apache Shiro是一个开源安全框架&#xff0c;提供身份验证、授权、密码学和会话管理。 CVE-2016-4437 利用vulhub搭建的靶场。 在Apache Shiro < 1.2.4版本中存在反…

Servlet 之超详解【2023年最新版】

编译软件&#xff1a;IntelliJ IDEA 2019.2.4 x64 操作系统&#xff1a;win10 x64 位 家庭版 服务器软件&#xff1a;apache-tomcat-8.5.27 目录 一. 什么是Servlet&#xff1f;二. 如何编写第一个servlet程序&#xff1f;三. Servlet的生命周期四. Servlet的技术体系五. web项…

Flutter Animation 动画

前言 &#xff1a; 在Flutter 中&#xff0c;做动画离不开这么一个类&#xff0c;那就是 Animation 这个类如往常一样&#xff0c;也是一个抽象类。 abstract class Animation<T> extends Listenable implements ValueListenable<T> 整个animation.dart 文件只有…

Windows和IDEA安装Scala

一、Windows安装Scala 前提&#xff1a;Windows已经安装好JDK1.8 第一步&#xff0c;下载对应的 Scala 安装文件 scala-2.12.11.zip (尚硅谷资料里有。直接获取&#xff09; 第二步&#xff0c;解压scala-2.12.11.zip 注意自己解压的目录&#xff0c;我这里解压到D盘java文…

3、ThingsBoard使用jar包单机部署

1、概述 前面一节我讲了如何初始化数据库表结构以及默认的数据。这一节我将讲解如何使用jar包部署。 2、部署 2.1、修改thingsboard.yml配置 上一节我已经讲解了thingsboard.yml中的基础配置,基础的组件配置如何redis、kafka、Cassandra、pg等大家都知道,关键的地方是在于…

Zimbra 远程代码执行漏洞(CVE-2019-9670)漏洞分析

Zimbra 远程代码执行漏洞(CVE-2019-9670)漏洞分析 漏洞简介 Zimbra是著名的开源系统&#xff0c;提供了一套开源协同办公套件包括WebMail&#xff0c;日历&#xff0c;通信录&#xff0c;Web文档管理和创作。一体化地提供了邮件收发、文件共享、协同办公、即时聊天等一系列解决…

主 存储器

主存储器 概述 实际上在主存储器运作时&#xff0c;根据MAR中的地址访问某个存储单元时&#xff0c;还需经过地址译码、驱动等电路才能找到所需的访问单元。读出时需经过读出放大器&#xff0c;才能将被选中单元的存储字送到MDR。写入时&#xff0c;MDR中的数据也必须经过写入…

课程简介:.Net Core从零学习搭建权限管理系统

课程简介目录 &#x1f680;前言一、课程背景二、课程目的三、系统功能四、系统技术架构五、课程特点六、课程适合人员七、课程规划的章节八、最后 &#x1f680;前言 本文是《.Net Core从零学习搭建权限管理系统》教程专栏的导航站&#xff08;点击链接&#xff0c;跳转到专栏…

【运动规划算法】路径规划中常用的插值方法

文章目录 简介一、线性插值二、三次样条插值三、B样条插值四、贝塞尔曲线插值总结 简介 常见用于处理路径平滑的插值算法主要包括线性插值、三次样条插值、B样条插值和贝塞尔曲线插值等&#xff0c;下面分别介绍它们的优缺点和使用场景。 一、线性插值 线性插值是最简单的插值…

【主流Chat模型的申请入口和方法】

主流Chat模型的申请入口和方法 一、申请New Bing二、申请内测文心一言三、申请内测Claude四、谷歌家的Bard五、Adobe Firefly六、GitHub Copilot chat七、通义千问八、360智脑 一、申请New Bing 注册一个 outlook 邮箱&#xff0c;很简单&#xff0c;2分钟就可搞定&#xff5e…

操作系统(2.7)--进程

目录 一、进程的引入 1.进程的两个基本属性 2.程序并发执行所需付出的时空开销 3.线程---作为调度和分派的基本单位 二、线程(轻型进程)与进程(重型进程)的比较 1&#xff09;调度的基本单位 2&#xff09;并发性 3&#xff09;拥有资源 4&#xff09;独立性 5&#…