YAZONG 我的开源

设计模式(十七)行为模式(对象行为型模式)迭代器模式-ITERATOR

 
0 评论0 浏览

2、17迭代器模式

定义

1)迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。(可以编写多态的代码与这些聚合搭配)。把游走的任务放在迭代器上,而不是聚合上。这样简化了聚合的接口和实现,也让责任各得其所。

2)当你需要访问一个聚集对象,而且不管这些对象是什么都需要遍历的时候,就该考虑使用迭代器模式。

3)当需要对聚集有多种方式遍历时,就可以考虑使用迭代器模式。

4)为遍历不同的聚集结构提供如开始、下一个、是否结束、当前哪一项等统一的接口。

5)迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。

6)简单地说,迭代器就类似于一个数据库中的游标,可以在一个容器内上下翻滚,遍历所有它需要查看的元素。

通用类图

image.png

1)Iterator抽象迭代器

负责定义访问和遍历元素的接口,而且基本上是有固定的3个方法:first()获得第一个元素,next()访问下一个元素,isDone()是否已经访问到底部(对应java的hasNext()方法)。

2)ConcreteIterator具体迭代器

具体迭代器角色要实现迭代器接口,完成容器元素的遍历。

3)Aggregate抽象容器

容器角色负责提供创建具体迭代器角色的接口,必然提供一个类型concreteIterator()这样的方法,在java中一般是iterator()方法。

有这个共同的接口供所有的聚合使用,这对客户代码是很方便的,将客户代码从集合对象的实现解耦了。

4)Concrete Aggregate具体容器

具体容器实现容器接口定义的方法,创建出容纳迭代器的对象。

#在此”通用案例”中实现的是外部的迭代器,也就是说,客户通过调用next()取得下一个元素。而内部的迭代器则是由迭代器自己控制。在这种情况下,因为是由迭代器自行在元素之间游走,所以你必须告诉迭代器在游走的过程中,要做些什么事情,也就是说,你必须将操作传入给迭代器。因为客户无法控制遍历的过程,所以内部迭代器比外部迭代器更没有弹性。然而,某些人可能认为内部的迭代器比价容易使用,因为只需将操作告诉它,它就会帮你做完所有事情。

通用案例

import java.util.Vector;

/**
 * 情景类
 */
public class Client1 {
    public static void main(String[] args) {
        //声明容器
        Aggregate aggregate = new ConcreteAggregate();
        //产生对象数据放进去
        aggregate.add("abc");
        aggregate.add("123");
        //遍历一下
        Iterator iterator = aggregate.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}
/**
 * 抽象迭代器
 */
interface Iterator{
    //遍历到下一个元素
    Object next();
    //是否已经遍历到尾部
    boolean hasNext();
    //删除当前指向的元素
    boolean remove();
}

/**
 * 具体迭代器
 * TODO 开发系统时,迭代器的删除方法应该完成两个逻辑:一是删除当前元素,二是当前游标指向下一个元素.
 */
class ConcreteIterator implements Iterator{

    private Vector vector = new Vector();
    //定义当前游标
    private int cursor = 0;

    public ConcreteIterator(Vector vector) {
        this.vector = vector;
    }

    /**
     * 遍历到下一个元素
     * @return
     */
    @Override
    public Object next() {

        Object result = null;
        if(this.hasNext()){
            result = this.vector.get(this.cursor++);
        }

        return result;
    }

    /**
     * 是否已经遍历到尾部
     * @return
     */
    @Override
    public boolean hasNext() {

        if(this.cursor == this.vector.size()){
            return false;
        }else{
            return true;
        }

    }

    /**
     * 删除当前指向的元素
     * @return
     */
    @Override
    public boolean remove() {
        this.vector.remove(this.cursor);
        return true;
    }

}

/**
 * 抽象容器
 */
interface Aggregate{
    //增加元素
    void add(Object object);
    //减少元素
    void remove(Object object);
    //迭代器对象:遍历所有的元素
    Iterator iterator();
}

/**
 * 具体容器
 */
class ConcreteAggregate implements Aggregate{

    //容纳对象的容器
    private Vector vector = new Vector();

    /**
     * 增加元素
     * @param object
     */
    @Override
    public void add(Object object) {
        this.vector.add(object);
    }

    /**
     * 减少元素
     * @param object
     */
    @Override
    public void remove(Object object) {
        this.remove(object);
    }

    /**
     * 迭代器对象:遍历所有的元素
     * @return
     */
    @Override
    public Iterator iterator() {
        return new ConcreteIterator(this.vector);
    }

}

业务案例类图

image.png

业务案例代码

import java.util.ArrayList;
import java.util.Iterator;

/**
 * 场景类
 */
public class Client2 {
    public static void main(String[] args) {
        IProject project = new Project();
        project.add("project1",1,1000);
        project.add("project2",2,2000);
        project.add("project3",3,3000);
        IProjectIterator projectIterator = project.iterator();
        while(projectIterator.hasNext()){
            IProject pro = (IProject) projectIterator.next();
            System.out.println(pro.getProjectInfo());
        }
    }
}

/**
 * 项目信息接口
 */
interface IProject{
    //增加项目
    void add(String name,int num,int cost);
    //项目信息
    String getProjectInfo();
    //被遍历的对象
    IProjectIterator iterator();
}
/**
 * 项目信息
 */
class Project implements IProject{

    //定义一个项目列表
    private ArrayList<IProject> projectList = new ArrayList<>();
    //项目名称
    private String name = "";
    //项目成员数量
    private int num = 0;
    //项目费用
    private int cost = 0;

    public Project() {
    }

    private Project(String name, int num, int cost) {
        this.name = name;
        this.num = num;
        this.cost = cost;
    }

    @Override
    public void add(String name, int num, int cost) {
        this.projectList.add(new Project(name,num,cost));
    }

    @Override
    public String getProjectInfo() {
        String info = "";
        info = info + "项目名称是:" + this.name;
        info = info + "\t项目人数是:" + this.num;
        info = info + "\t项目费用是:" + this.cost;
        return info;
    }

    @Override
    public IProjectIterator iterator() {
        return new ProjectIterator(this.projectList);
    }
}

/**
 * 项目迭代器接口
 */
interface IProjectIterator extends Iterator{

}

/**
 * 项目迭代器(封装了遍历)
 */
class ProjectIterator implements IProjectIterator{

    //所有的项目都放在ArrayList
    private ArrayList<IProject> projectList = new ArrayList<>();
    private int currentItem = 0;

    public ProjectIterator(ArrayList<IProject> projectList) {
        this.projectList = projectList;
    }

    @Override
    public boolean hasNext() {
        //定义一个返回值
        boolean b = true;
        if(this.currentItem >= projectList.size() || this.projectList.get(this.currentItem) == null){
            b = false;
        }
        return b;
    }

    @Override
    public Object next() {
        return this.projectList.get(this.currentItem++);
    }

    @Override
    public void remove() {
        //暂时没有使用到
    }
}

项目名称是:project1	项目人数是:1	项目费用是:1000
项目名称是:project2	项目人数是:2	项目费用是:2000
项目名称是:project3	项目人数是:3	项目费用是:3000

将枚举适配到迭代器

实际问题

在遗留的代码中,这些代码暴露出枚举接口,但是我们又希望在新的代码中只使用迭代器。要解决这个问题,就需要构造一个适配器(参考此设计模式系列中的”适配器”章节)。

结构关系

image.png

设计适配器类图

image.png

设计适配器案例

import java.util.Enumeration;

import java.util.Iterator;

 

/**

 * 场景类

 */

public class Client3 {

}

/**

 * EnumerationIterator适配器

 * TODO 将枚举适配成迭代器,适配器需要实现迭代器接口,适配器必须看起来就像是一个迭代器.

 */

class EnumerationIterator implements Iterator{

 

    private Enumeration enumeration;

 

    /**

     * 利用组合的方式,将枚举结合进入适配器中,所以用一个实例变量记录枚举.

     * @param enumeration

     */

    public EnumerationIterator(Enumeration enumeration) {

        this.enumeration = enumeration;

    }

 

    /**

     * 迭代器的hasNext()方法其实是委托给枚举的hasMoreElements()方法.

     * @return

     */

    @Override

    public boolean hasNext() {

        return enumeration.hasMoreElements();

    }

 

    /**

     * 迭代器的next()方法其实是委托给枚举的nextElement()方法.

     * @return

     */

    @Override

    public Object next() {

        return enumeration.nextElement();

    }

 

    /**

     * 不能支持迭代器的remove()方法.在这里了是抛出一个异常.同java.util.Iterator中的处理.

     */

    @Override

    public void remove() {

        throw new UnsupportedOperationException();

    }

}

迭代器与集合

Collection类图

image.png

1)Collection和Iterator的好处在于,每个Collection都知道如何创建自己的Iterator。只要调用ArrayList(迭代器会暗中调用ArrayList的get方法。)上的iterator(),就可以返回一个具体的Iterator,而你根本不需要知道或关系到底使用了哪个具体类,你知道使用它的Iterator接口就可以了。

2)面向接口编程,接口是对一个事物的描述,也就是说通过接口就知道这个事物有哪些方法,哪些属性。

Hashtable的用法

import java.util.Hashtable;

import java.util.Iterator;

 

/**

 * 场景类

 * TODO HashTable对于迭代器的支持是"间接"的.

 */

public class Client4 {

    public static void main(String[] args) {

        CafeMenu menu = new CafeMenu();

        for (int i = 0; i < 5; i++) {

            menu.addMenu("nameval-" + i,"descriptionval-" + i);

        }

        Iterator iterator = menu.createIterator();

        while(iterator.hasNext()){

            MenuItem menuItem = (MenuItem)iterator.next();

            System.out.println(menuItem);

        }

    }

}

interface Menu{

    Iterator createIterator();

}

class CafeMenu implements Menu{

 

    private Hashtable<String,MenuItem> menuItems = new Hashtable();

 

    public void addMenu(String name,String description){

        MenuItem menuItem = new MenuItem(name,description);

        menuItems.put(name,menuItem);

    }

 

    /**

     * HashTable对于迭代器的支持是"间接"的.这个迭代器不是直接从HashTable取出,而是由HashTable的value取出.

     * 这种处理方法不错:因为HashTable内部存储了两组对象,key和value.

     * 如果想遍历value,当然是要先从HashTable取得value,然后再取得迭代器.

     * 这里不是取得整个HashTable的迭代器,而是取得值的部分的迭代器.

     * @return

     */

    @Override

    public Iterator createIterator() {

        return menuItems.values().iterator();

    }

}

class MenuItem{

 

    private String name;

    private String description;

 

    public MenuItem(String name, String description) {

        this.name = name;

        this.description = description;

    }

 

    public String getName() {

        return name;

    }

 

    public void setName(String name) {

        this.name = name;

    }

 

    public String getDescription() {

        return description;

    }

 

    public void setDescription(String description) {

        this.description = description;

    }

 

    @Override

    public String toString() {

        return "MenuItem{" +

                "name='" + name + '\'' +

                ", description='" + description + '\'' +

                '}';

    }

}

 

#可以发现这里的输出是无序的

MenuItem{name='nameval-2', description='descriptionval-2'}

MenuItem{name='nameval-1', description='descriptionval-1'}

MenuItem{name='nameval-0', description='descriptionval-0'}

MenuItem{name='nameval-4', description='descriptionval-4'}

MenuItem{name='nameval-3', description='descriptionval-3'}

Java5的迭代器和集合

import java.util.ArrayList;

import java.util.List;

 

/**

 * 场景类

 */

public class Client5 {

    public static void main(String[] args) {

        List<String> list = new ArrayList<>();

        for (int i = 0; i < 5; i++) {

            list.add("val-" + i);

        }

        /**

         * 这里使用的java5的泛型特性来确保for/in的类型安全.

         * 在集合中的每个对象之间反复遍历.

         * 每次到循环的最后,obj会被赋值为集合中的下一个元素.

         */

        for(String str : list){

            System.out.println(str);

        }

    }

}

单一职责与内聚

1)一个类应该只有一个引起变化的原因。当允许一个类不但要完成自己的事情(某种聚合),还同时要担负更多的责任(例如遍历)时,此时就给了这个类两个变化的原因。如果集合改变的话,那么这个类也需要跟着改变。如果遍历的方式改变的话,那么这个类也需要跟着改变。

2)类的每个责任都有改变的潜在区域。超过一个责任,意味着超过一个改变的区域。所以尽量要让每个类保持单一责任。

3)内聚,用来度量一个类或模块紧密地达到单一目的或责任。

4)当一个模块或一个类被设计成只支持一组相关的功能时,此时具有高内聚。反之,当设计成支持一组不相关的功能时,此时具有低内聚。

5)内聚是一个比单一责任原则更普遍的概念,但两者其实关系是很密切的。遵守这个原则的类很容易具有很高的凝聚力,而且比背负许多责任的低内聚类更容易维护。

其他

1)对于散列表这样的集合,元素之间没有明显的次序关系,怎么办?

迭代器意味着没有次序。只是取出所有的元素,并不表示取出元素的先后就代表元素的大小次序。对于迭代器来说,数据结构可以是有次序的,或是没有次序的,甚至数据可以是重复的。除非某个集合的文件有特殊说明,否则不可以对迭代器所取出的元素大小顺序做出假设。

2)迭代器写的”多态”代码

当写了一个需要以迭代器当做参数的方法时,其实就是在使用多态的迭代。也就是说,可以在不同的集合中游走,只要这个集合支持迭代器即可。不用在乎这个集合是如何被实现的,但依然可以编程在它内部的元素之间游走。

3)迭代器是为容器服务的。容器:能容纳对象的所有类型都可以称之为容器,例如Collection集合类型、Set类型等,迭代器模式就是为了解决遍历这些容器中的元素而诞生的。迭代器模式提供了遍历容器的方便性,容器只要管理增减元素就可以了,需要遍历时交由迭代器进行。

4)“集合”指的是”一群对象”。其存储方式可以是各式各样的数据结构,例如:列表、数组、散列表,无论用什么方式存储,一律可以视为是集合,有时候也被称为聚合。


标题:设计模式(十七)行为模式(对象行为型模式)迭代器模式-ITERATOR
作者:yazong
地址:https://blog.llyweb.com/articles/2020/08/09/1596975035882.html