木灵鱼儿

木灵鱼儿

阅读:89

最后更新:2022/06/21/ 23:58:28

行为模式:迭代器模式

简介

迭代器模式:提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。

简单来说就是再程序设计中,对于一些自定义的数据结构对象,比如链表啊,或者系统提供的数据结构对象,他们的遍历方法通常会写在数据结构的类中,也就是说数据和遍历的方法是写在同一个类里面。

这种方式不利于程序的扩展,因为如果要改动遍历的方法,就必须改动源数据,这违背了开闭原则。

既然遍历的方法不能写在数据类中,那么将遍历的方法让客户实现不就好了,但是同样也是不可取的,如果交由客户去写,第一是会暴露很多不必要的属性给客户,第二是增加了客户端的使用负担。

而迭代器模式很好的解决了这个问题,在迭代器模式中有以下几个角色:

  1. 抽象的数据结构:抽象出数据的操作方法以及对接迭代器对象的接口方法。
  2. 具体的数据结构:实现抽象的数据结构
  3. 抽象的迭代器:定义遍历时获取数据的接口,一般会包含:hasNext()、first()、next()等方法
  4. 具体的迭代器:实现抽象迭代器定义的接口

抽象的数据结构中依赖抽象的迭代器,依赖倒置原则。

在具体实现的数据结构中会存在一个用于对接迭代器的方法,假设为getIterator方法,当用户调用getIterator方法的时候,将我们预先写好的迭代器类实例化,并将数据结构中存储的数据传入迭代器,然后将迭代器抛出。

之后用户通过迭代器去获取数据。

为什么要自定义一个遍历用的迭代器呢?

这就要考虑到数据的遍历方式!

数据结构中的遍历分为树的遍历和图的遍历

1、树的遍历分为三种:

先序遍历:根左右,树非空,先访问根节点,在按照从左到右的顺序遍历根节点的每一颗子树。

中序遍历:左根右,在二叉树中,中序遍历首先遍历左子树,然后访问根结点,最后遍历右子树。

后序遍历:左右根,树非空,则按照从左到右的顺序遍历根节点的每一颗子树,之后在访问根节点。

2、图的遍历分为两种:

广度优先遍历:类似于树的层次遍历,从数组中选择一个没有被访问的顶点v,并标记为已访问,接着依次访问其所有未被访问的邻接顶点,标记为已访问,从这些邻接点出发进行广度优先遍历,直到图中所有和v有路径相通的顶点都被访问过,再重复上述步骤,直到所有点都被访问过。

深度优先遍历:类似于树的先序遍历。从数组中选择一个没有被访问的顶点v,并标记为已访问,接着从v的一个未被访问过的邻接点v1出发进行深度优先遍历,再从v1开始深度优先遍历,直到所有和v有路径顶点相通的顶点都被访问过,重复上诉所有步骤,直到所有丁点都被访问过。

所以针对一些情况,自定义更加合适的遍历方式和数据结构是很有必要的。那么遍历和数据结构的整合方式,优先采用迭代器模式。

代码实现

//抽象的数据结构
interface DataInterface {
  list: Array<string>;

  add(data: string): this;

  remove(data: string): boolean;

  //对接迭代器的接口
  getIterator(): CustomIterator;
}

//具体的数据结构
class MyDataList implements DataInterface {
  list: Array<string> = [];

  add(data: string): this {
    this.list.push(data);
    return this;
  }

  remove(data: string): boolean {
    const index = this.list.indexOf(data);
    if (index !== -1) {
      this.list.splice(index, 1);
      return true;
    }
    return false;
  }

  //对接迭代器的接口
  getIterator(): CustomIterator {
    return new MyIterator(this.list);
  }
}

//抽象的迭代器
interface CustomIterator {
  first(): string;

  next(): string | void;

  hasNext(): boolean;
}

//具体的迭代器
class MyIterator implements CustomIterator {
  private list: Array<string>;
  private index: number = 0;

  constructor(list: Array<string>) {
    this.list = list;
  }

  first(): string {
    this.index = 0;
    return this.list[this.index];
  }

  next(): string | void {
    if (this.hasNext()) {
      return this.list[this.index++];
    }
  }

  hasNext(): boolean {
    return this.index < this.list.length;
  }
}

//客户调用
class Client {
  constructor() {
    //数据
    const data = new MyDataList();
    data.add("我").add("是").add("hero");

    //使用迭代器
    let text = "";
    const iterator = data.getIterator();
    while (iterator.hasNext()) {
      text += iterator.next();
    }

    console.log(text);
  }
}

new Client();

应用场景

迭代器模式通常在以下几种情况使用。

  1. 当需要为聚合对象提供多种遍历方式时。
  2. 当需要为遍历不同的聚合结构提供一个统一的接口时。
  3. 当访问一个聚合对象的内容而无须暴露其内部细节的表示时。

由于数据结构与迭代器的关系非常密切,所以大多数语言在实现数据结构时都提供了迭代器类,因此大数情况下使用语言中已有的数据结构的迭代器就已经够了。

js中的迭代器

内容摘自文章:《 js中的迭代器(Iterator)》

在 JavaScript 中,迭代器是一个对象,它定义一个序列,并在终止时可能返回一个返回值。 更具体地说,迭代器是通过使用 next() 方法实现 Iterator protocol 的任何一个对象,该方法返回具有两个属性的对象: value,这是序列中的 next 值;和 done ,如果已经迭代到序列中的最后一个值,则它为 true 。如果 valuedone 一起存在,则它是迭代器的返回值。

不难看出,js中的迭代器也是迭代器模式的具体产出。

在js里原生具备 Iterator 接口的数据结构如下:

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的 arguments 对象
  • NodeList 对象

ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,Symbol.iterator属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。

iteration有三个作用:

  1. 为各种数据结构,提供一个统一的、简便的访问接口;
  2. 使得数据结构的成员能够按某种次序排列;
  3. 主要供for...of消费

iteration接口什么时候被使用

  • 解构赋值

对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator方法。

let set = new Set().add('a').add('b').add('c');
let [first, ...rest] = set;
// first='a'; rest=['b','c'];
  • 扩展运算符(...)
var str = 'hello';
[...str] //  ['h','e','l','l','o'],string上有iteration,因此字符串也可以用for...of来循环
let arr = ['b', 'c'];
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']
  • generator函数 调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是返回一个遍历器对象
let myIterable = {
  [Symbol.iterator]: function* () {
    yield 'a';
    yield 'b';
    yield 'c';
  }
};
console.log([...myIterable]) // [a, b, c]

for (let i of myIterable){
  console.log(i)  // [a, b, c]
}

还有其他方法背后也会用到iteration,比如Array.from()、Promise.all()等等,这里不再展开描述了。

版权申明

本文系作者 @木灵鱼儿 原创发布在木灵鱼儿 - 有梦就能远航站点。未经许可,禁止转载。

关于作者

站点职位 博主
获得点赞 0
文章被阅读 89

相关文章