首页 > 其他分享 >设计模式:迭代器模式

设计模式:迭代器模式

时间:2023-09-02 23:55:24浏览次数:55  
标签:遍历 return 迭代 iterator 对象 模式 console 设计模式

设计模式

wiki中将设计模式分为四类,分别是:

  • 创建模式(creational patterns)
  • 结构模式(structural patterns)
  • 行为模式(behavioral patterns)
  • 并发模式(concurrency patterns)

迭代器模式属于其中的行为模式。

迭代器做的只有一件事,就是解决集合的遍历问题。

以下是wiki对迭代器模式的概括性描述:

Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.

提供一种在不暴露底层表示的情况下按顺序访问聚合对象元素的方式。

这个描述乍一看有点抽象,但其实我们常用的for循环就可以看作是迭代器模式的应用,在遍历时并不知道数组元素的具体表示:

let array = [1, 2, 3, 4, 5, 6];
for (let i = 0; i < array.length; i ++) {
  console.log(array[i]);
}

假如不应用迭代器,我们想要遍历array数组就得像下面这么做:

console.log(array[0]);
console.log(array[1]);
console.log(array[2]);
// ...

但使用for循环的前提是array是数组,这样我们才能使用索引去遍历这个集合,所以上面这种for循环就是针对数组类型集合的一种迭代器实现。

定义

下面再进一步了解迭代器模式是什么:

In object-oriented programming, the iterator pattern is a design pattern in which an iterator is used to traverse a container and access the container's elements. The iterator pattern decouples algorithms from containers; in some cases, algorithms are necessarily container-specific and thus cannot be decoupled.

For example, the hypothetical algorithm SearchForElement can be implemented generally using a specified type of iterator rather than implementing it as a container-specific algorithm. This allows SearchForElement to be used on any container that supports the required type of iterator.

在面向对象编程中,迭代器模式是一种设计模式,使用迭代器来遍历容器并访问容器中的元素。迭代器模式使算法与容器分离;在某些情况下,算法是特定于容器的,因此无法分离。

(可以看出,上面的for循环就是特定于容器,也就是数组类型的容器,同理,数组的原型方法forEach也是如此)

例如,假设算法'SearchForElement'可以使用特定类型的迭代器实现,而不是作为特定于容器的算法来实现。这样就可以在任何支持所需迭代器类型的容器上使用'SearchForElement'。

解决的问题

从上述定义中我们就可以知道,迭代器模式就是用于解决如何去遍历容器中的元素。

下面是wiki给出的描述:

  • The elements of an aggregate object should be accessed and traversed without exposing its representation (data structures).
  • New traversal operations should be defined for an aggregate object without changing its interface.

Defining access and traversal operations in the aggregate interface is inflexible because it commits the aggregate to particular access and traversal operations and makes it impossible to add new operations later without having to change the aggregate interface.

翻译过来即:

  • 访问和遍历聚合对象中的元素时,不应暴露其表示形式(数据结构)
  • 应在不改变聚合对象接口的情况下,为聚合对象定义新的遍历操作

在聚合接口中定义访问和遍历操作是不灵活的,因为这样做会使聚合对象服从于特定的访问和遍历操作,以后就不可能在不改变聚合接口的情况下添加新的操作。

(这段话的意思应该是指,访问和遍历操作应该由聚合对象自行实现)

如何做

那具体应该如何操作呢?

  • Define a separate (iterator) object that encapsulates accessing and traversing an aggregate object.
  • Clients use an iterator to access and traverse an aggregate without knowing its representation (data structures).

Different iterators can be used to access and traverse an aggregate in different ways.
New access and traversal operations can be defined independently by defining new iterators.

翻译过来即:

  • 定义一个单独的(迭代器)对象,封装对聚合对象的访问和遍历
  • 客户端使用迭代器来访问和遍历聚合对象,而无需了解其表示形式(数据结构)

不同的迭代器可以不同的方式访问和遍历聚合对象。

通过定义新的迭代器,可以独立定义新的访问和遍历操作。

ES6实现

在ES6之前,可以被遍历的对象严格来说只有数组,所以遍历方法都是针对数组这个集合类型;甚至类数组都不能直接调用forEach方法。

但现在E6中新增了Map和Set等集合类型,原来的遍历方法就更大地暴露出了它们的缺陷:

  • 遍历的对象必须是数组
  • 使用的是递增索引的方式

[Symbol.iterator]()

为此ES6定义了新的可迭代协议,即任何对象都可以通过定义一个迭代器接口,来生成迭代器,这个接口被称为“迭代器工厂”,即[Symbol.iterator](),来定义其被迭代的方式;此时这个类型便被称为“可迭代对象”(Iterable)。

迭代器可以提供给各种遍历方法调用,比如:for-of、数组解构、扩展操作符等操作,都会调用可迭代对象的迭代器接口得到其对应的迭代器,然后调用迭代器的next()方法来完成对可迭代对象的遍历,如果有提前结束遍历的需求,还可以给迭代器定义return()方法。

next()return()方法返回的对象被称为“IteratorResult对象”,包含有value和done两个属性,value表示迭代值,done表示遍历是否结束,当done的值为true时,代表遍历结束。

以下是一个简单的例子:

class Counter {
    constructor(limit) {
        this.limit = limit;
    }

   // 迭代器接口
    [Symbol.iterator]() {
        let count = 1,
          o = this;

        return { // 迭代器本体
          next() {
              if( count <= o.limit) {
                return { // IteratorResult对象
                   done: false,
                   value: count++
                };
              } else {
                return {
                   done: true
                 };
               }
           },
           return() {
               console.log( 'Exiting early' );
               return {
                  done: true
                };
            }
        }
   }
}

let counter1 = new Counter(5);
for (let i of counter1) {
    if(i > 2) {
        break;
    }
    console.log( i );
}

// 1
// 2
// Exiting early

在上述代码中,for-of在遍历过程中相当于调用迭代器的next()方法,当执行到break;语句时,就相当于调用了迭代器的return()方法;上述代码等价于:

// 通过调用迭代器工厂,获取迭代器对象
const iterator = counter1[Symbol.iterator]();
let iterator_result = { done: false };
while(!iterator_result.done) {
   iterator_result = iterator.next();
   if (iterator_result.value > 2) {
     iterator_result = iterator.return();
   } else {
     console.log(iterator_result.value);
   }
}

在ES6中,Array、Map和Set等集合类型都内置了单独的迭代器。

每个内置可迭代对象的默认迭代器接口所产生的迭代器,也有自己的默认迭代器函数,返回指向自身(并未严格实现Iterable接口,默认迭代器函数不能创建新的迭代器),即如下所示:

[Symbol.iterator]() {
    // ...

    return {
        next() {
           // ...
         },
        return() {
            // ...
        },
       [Symbol.iterator]() {
         return this;
       }
    }
}

Generator

另外,ES6还提供了生成迭代器的函数,简称“生成器(Generator)”,相当于迭代器工厂[Symbol.iterator]()这个接口,可以通过*来声明生成器函数,并配合yield来实现更灵活的遍历。

来看下面的例子:

function* generatorFn3() {
  // ...一些操作
  yield 'foo';
  // ...一些操作
  yield 'bar';
  // ...一些操作
  return 'baz';
}
let g3 = generatorFn3(); // 生成一个迭代器

此时通过迭代器来遍历,当执行第n次g3.next(),就会执行第n个yield(没有yield就是return)之前的代码,返回对象中的value即对应yield或return跟着的值,当执行到yield时,done为false,当执行到return,done就为true,如下所示:

console.log( g3.next() ); // { value: 'foo', done: false }
console.log( g3.next() ); // { value: 'bar', done: false }
console.log( g3.next() ); // { value: 'baz', done: true }

以上代码相当于:

g3 = generatorFn3();
for(const x of g3) {
    console.log( x );
}

但只会输出foo和bar,return后面跟着的值不属于迭代值。

总结

一般情况下,我们不需要自己实现迭代器,ES6内置的迭代器就足够日常的开发使用了,但了解迭代器模式,以及ES6提供的迭代器工厂和生成器函数,可以帮助我们解决特殊的开发需求,设计更灵活的代码方案(利用generator和yield)。

标签:遍历,return,迭代,iterator,对象,模式,console,设计模式
From: https://www.cnblogs.com/beckyyyy/p/17674426.html

相关文章

  • 网页版B站暗黑模式:Chrome Dark Reader 插件
    https://chrome.google.com/webstore/detail/dark-reader/eimadpbcbfnmbkopoojfekhnkhdbieeh?utm_source=ext_app_menu使用说明https://darkreader.org/help/zh-CN/效果还不错......
  • 设计模式学习1 设计原则
    设计原则1.开闭原则对扩展开放,修改关闭。在程序需要扩展的时候,不能去修改原有代码,实现一个热插拔的效果。为了使程序的扩展性好,易于维护和升级为了达到这样的效果,我们需要使用接口和抽象类2.里氏代换原则任何基类可以出现的地方,子类一定可以出现。也就是子类继承父类时,除了添......
  • wangEditor增加源码模式,添加查看源码功能
    wangEditor是一款轻量级的富文本编辑器。使用还比较方便,但是缺少查看源码模式,需要我们自定义一个menu给增加查看源码模式下面是wangEditor增加源码模式的代码:<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="......
  • 多线程|饿汉模式和懒汉模式
    单例模式是只有单个实例的模式,应用在只能有一个实例的场景中。单例模式有很多种,这里介绍饿汉模式和懒汉模式两个单例。一、饿汉模式“饿汉”是一种形象的描述,“饿汉”看到吃的就非常急切,把这种急切的形象类比到Java中就是在类加载阶段就把实例创建出来了。什么是类加载?Java代码......
  • 【从互联网商业思维的角度分析商业模式在国内各大互联网产品的运用】
    随着互联网技术的不断进步,互联网商业模式也在不断变化,各个互联网企业都在不断尝试各种商业模式,以满足不同消费者群体的需求,提高企业营销效益,下面我们将从互联网商业思维的角度,分析一下商业模式在国内各大互联网产品的运用。一、电商模式电商模式是指在互联网上建立电子商务平台,通......
  • mongodb副本集(非仲裁模式)修改各节点ip(update方式)
    环境:OS:Centos7mongodb:5.0当前的ip变更后的ip192.168.1.108192.168.1.105   PRIMARY192.168.1.109192.168.1.106   SECONDARY192.168.1.110192.168.1.107   SECONDARY 1.查看当前的集群登录一个节点上查......
  • 【23种设计模式】单例模式(一)
    前言:单例模式是创建型模式5种中的第1种,关注对象的创建,保证一个类仅有一个实例,并且提供一个全局访问点。在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性、以及良好的效率。如何绕过常规的构造器,提供一种机制来保证一个类只创建一......
  • Flink 1.17教程:Standalone会话模式运行时架构及并行度
    运行时架构——Standalone会话模式为例并行度并行度是指在计算过程中同时执行多个任务或操作的能力。在ApacheFlink中,并行度是指同时执行作业中的多个任务或算子的能力。并行度的引入是为了解决以下问题:提高计算速度:通过将任务拆分成多个子任务,并行执行它们,可以大大提高计算速度......
  • Flink 1.17教程:Hadoop yarn会话运行模式
    YARN运行模式_环境准备YARN上部署的过程是:客户端把Flink应用提交给Yarn的ResourceManager,Yarn的ResourceManager会向Yarn的NodeManager申请容器。在这些容器上,Flink会部署JobManager和TaskManager的实例,从而启动集群。Flink会根据运行在JobManger上的作业所需要的Slot数量动态分配T......
  • Flink 1.17教程:部署模式介绍及Standalone运行模式
    部署模式介绍在一些应用场景中,对于集群资源分配和占用的方式,可能会有特定的需求。Flink为各种场景提供了不同的部署模式,主要有以下三种:会话模式(SessionMode)、单作业模式(Per-JobMode)、应用模式(ApplicationMode)。它们的区别主要在于:集群的生命周期以及资源的分配方式;以及应用的mai......