木灵鱼儿
阅读:89
行为模式:迭代器模式
简介
迭代器模式:提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
简单来说就是再程序设计中,对于一些自定义的数据结构对象,比如链表啊,或者系统提供的数据结构对象,他们的遍历方法通常会写在数据结构的类中,也就是说数据和遍历的方法是写在同一个类里面。
这种方式不利于程序的扩展,因为如果要改动遍历的方法,就必须改动源数据,这违背了开闭原则。
既然遍历的方法不能写在数据类中,那么将遍历的方法让客户实现不就好了,但是同样也是不可取的,如果交由客户去写,第一是会暴露很多不必要的属性给客户,第二是增加了客户端的使用负担。
而迭代器模式很好的解决了这个问题,在迭代器模式中有以下几个角色:
- 抽象的数据结构:抽象出数据的操作方法以及对接迭代器对象的接口方法。
- 具体的数据结构:实现抽象的数据结构
- 抽象的迭代器:定义遍历时获取数据的接口,一般会包含:hasNext()、first()、next()等方法
- 具体的迭代器:实现抽象迭代器定义的接口
抽象的数据结构中依赖抽象的迭代器,依赖倒置原则。
在具体实现的数据结构中会存在一个用于对接迭代器的方法,假设为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();
应用场景
迭代器模式通常在以下几种情况使用。
- 当需要为聚合对象提供多种遍历方式时。
- 当需要为遍历不同的聚合结构提供一个统一的接口时。
- 当访问一个聚合对象的内容而无须暴露其内部细节的表示时。
由于数据结构与迭代器的关系非常密切,所以大多数语言在实现数据结构时都提供了迭代器类,因此大数情况下使用语言中已有的数据结构的迭代器就已经够了。
js中的迭代器
内容摘自文章:《 js中的迭代器(Iterator)》
在 JavaScript 中,迭代器是一个对象,它定义一个序列,并在终止时可能返回一个返回值。 更具体地说,迭代器是通过使用
next()
方法实现 Iterator protocol 的任何一个对象,该方法返回具有两个属性的对象:value
,这是序列中的 next 值;和done
,如果已经迭代到序列中的最后一个值,则它为true
。如果value
和done
一起存在,则它是迭代器的返回值。
不难看出,js中的迭代器也是迭代器模式的具体产出。
在js里原生具备 Iterator 接口的数据结构如下:
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator
属性,Symbol.iterator属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。
iteration有三个作用:
- 为各种数据结构,提供一个统一的、简便的访问接口;
- 使得数据结构的成员能够按某种次序排列;
- 主要供
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()等等,这里不再展开描述了。
版权申明
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿 - 有梦就能远航站点。未经许可,禁止转载。
相关推荐
行为模式:观察者模式
简介观察者模式:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式。而我们前端vue的响应式原理其实就是观察者模式的实现,只不过他是完全解耦的发布订阅者模式;和基础的观察者模式实现上会有不同,原理都是一样的。观察者模式是为了将重要的部分与辅助部分进行解耦,我们举个例子:在前端中,我们的滚动条距离就可以理解为一个重要部分,我们监听了scroll事件,并在回调函数中处理了一条特殊操作,比如根据滚动的距离操作header元素显隐。此时这个代码量很少,我们知道scroll事件是不建议监听太多的,...
行为模式:访问者模式
简介访问者模式:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。有一点点抽象,前一句话的意思是指在写好一个类之后,这个类就基本上不需要改动了(只要需求不改),这其实就是为了解决类结构不变但操作处理逻辑易变的问题,把对数据的操作都封装到访问者类中,我们只需要调用不同的访问者,而无需改变改变结构类,实现了。简单来说就是将一些操作作为扩展分离出去, 这些扩展的功能可能现在没有,以后会有,我们无法知道以后的情况,于是...
行为模式:命令模式
简介命令模式是将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。举个简单的例子:我有一个饭店,共有五个厨师:A,B,C,D,E;每个厨师都会做不同的菜,但是我没有菜单,于是客人就必须知道我有几个厨师,他们分别会做什么菜,于是就会产生下面这些情况:客人1想吃青椒炒蛋,于是问了好几个厨师才知道C厨师会做,于是它通知C厨师做青椒炒蛋客人2想吃冒菜,于是他也问了好几个厨师,找到E厨师制作...更多你会发现客人与厨师耦合度非常之高,这显然不利于饭店的发展,然后我们想一下生活中的真实情况:每个饭店都...
行为模式:模板方法模式
简介模板方法模式:定义一个操作中的算法骨骼,而将算法的一些步骤延迟到子类中去,使得子类可以不改变整体算法结构的情况下重新定义该算法的某些特定步骤。简单点来说就是将重要的步骤顺序的处理交由父类来做,然后父类是一个抽象的类,它申明了每个步骤的抽象,由子类去进行每个步骤的具体实现。这样父类定好了一个整体的骨骼,具体步骤实现交给子类,每当需要改动时只需要更换不同的子类就可以了,符合开闭原则。代码实现//抽象父类 abstract class A { //抽象方法 public abstract step1(): void; //抽象方法 public abstract step...
结构型模式:代理模式
简介代理模式指:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。更直白的说,代理模式更注重的是对源对象的控制访问(中介和保护),虽然我们可以在代理对象上扩展源对象没有的属性,但是如果要增强原有的功能其实还需要和其他模式进行组合使用,比如装饰器模式。角色:抽象角色:用于源对象与代理对象的依赖抽象源对象:真实的业务对象,是代理类所要代表的真实对象代理类:通过抽象角色规范,提供与源对象相同的接口,内部含有对源对象的引用,通过它可以进行扩展和控制代码实现//抽象 interface AInterfa...
结构型模式:享元模式
简介享元模式是运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。举个例子:我们有一个服装工厂,它有男装和女装衣服各100套,为了销售,需要模特来帮忙拍照做成广告照片,正常情况下我们可能会这么写。class Model { private gender: string; //性别 private clothes: number; //衣服 constructor(gender: string, clothes: number) { this.gender = gende...
结构型模式:外观模式
简介外观模式:是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。其实就是一种封装,把一系列的操作封装成简单的接口供客户端去调用。但是不能滥用,因为本身就是为了减少客户端与各个依赖的耦合性,将一堆依赖改为依赖一个外观类。外观模式遵循的是迪米特法则,也就是最少知道原则,但是如果新增新的功能,可能会改动到外观类,所以它违背了开闭原则。它常常应用于SKD,开源类库这些,用于给外部提供精简的接口。而我们js,可能并不需要去创建一个类,直接整个函...

结构型模式:组合模式
简介组合模式它本身是一种树形结构模式,可以将其理解为一棵树,其中树为根节点,然后有树枝和树叶,树枝不断的分叉,树枝上也会有树叶。我们看个图:它是一种将对象组合成树状的层次结构的模式,用来表示“整体-部分”的关系,使用户对单个对象和组合对象具有一致的访问性。其实说人话就是,我定义了一个接口,它有一个show方法,然后所有的对象都得实现,然后对象之间可以嵌套,因为嵌套了,所以当我调用show方法的时候,这个对象必须去遍历自己的子级嵌套对象的show方法,不断的重复,最终得到结果之和。类似于一个递归函数,只不过具体的处理都在每个对象自身show方法中去实现。组合模式常常用在:购物车、节点统计购...

结构型模式:装饰器模式
简介在软件开发过程中,有时想用一些现存的组件。这些组件可能只是完成了一些核心功能。但在不改变其结构的情况下,可以动态地扩展其功能。所有这些都可以釆用装饰器模式来实现。装饰器模式指:在不改变现有对象结构的情况下,动态的给对象增加一些职责(功能)的一种模式。这种模式也是防止滥用继承,相对于桥接模式只能桥接两个维度,装饰器模式是可以对接多个维度的,因为他可以嵌套使用。我们举个例子:我有一个食物类:class 煎饼我单点煎饼这个食物是可以的,我也可以加不同的配料:鸡蛋、火腿、豆皮于是乎我们通过继承得到:class 鸡蛋煎饼 extends 煎饼class 火腿煎饼 extends 煎饼class...
结构型模式:桥接模式
简介桥接模式是指:将抽象与实现分离,使它们可以独立变化。它使用组合关系来代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。简单点来说,桥接模式用就是用于连接两个独立变化维度的一种方式(组合),而为什么要连接,我们需要先了解到类继承带来的复杂性。先看一张图:我们可以看到,从最顶层的电脑类延伸了三个子类,子类又延伸出不同的品牌类,如果我们又新增了一个新的电脑子类,比如二合一电脑,又不得不重复实现一下对应的品牌子类;如果是新增新的品牌,也是同理,也得产生很多新的子类。这就导致类的不断产生,从而有了类爆炸这一说。而桥接模式,就是用于解决这个问题的,我们将他们进行抽象,你会发现有两个...
