⑨集合管理-迭代器和组合模式

统一的遍历方法

像是为对象提供 Iterator 接口一样,有时需要遍历一个复杂对象的内部属性,所以需要一个统一的接口,这也是迭代器模式需求的由来。

迭代器模式:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。

现在有两个对象保存着一些数据,但是数据使用不同的数据结构保存,数组,对象,链表,或是一些特殊的封装结构。

1
2
3
4
5
6
7
8
9
10
11
class Test1 {
data = ["a1", "b1", "c1"];
}

class Test2 {
data = {
a2: "a2",
b2: "b2",
c2: "c2",
};
}

如果想要便利这两个对象中的所有数据,最容易想到的办法就是分别使用数据和对象的遍历方法,通过两次循环依次返回。

把这个遍历的实现定义为类 MapObject,但这并不是一个合理的办法:

  • 遍历的前提是必须要知道对象的实现细节,违背了针对接口编程,而不是针对实现,也可以说是违背了封装。
  • 如果需要更换其中的一个类遍历,那么必须修改 MapObject,违背了对扩展开放,对修改关闭。

现在已经清楚了变化之处在于遍历,想办法把遍历封装起来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
interface IteratorClass<T> {
new (): T;
}
interface IIterator {
iterator(): IterableIterator<string>;
}

// 遵循面向接口编程的原则,两个类都实现了相同的接口

class Test1 implements IIterator {
data = ["a1", "b1", "c1"];
iterator() {
return this.data[Symbol.iterator]();
}
}

class Test2 implements IIterator {
data = {
a2: "a2",
b2: "b2",
c2: "c2",
};
iterator() {
const iterator = (): IterableIterator<string> => {
let data = this.data;
let keys = Object.keys(data);
let index = 0;
return {
next() {
return index < keys.length
? {
value: data[keys[index++] as keyof typeof data],
done: false,
}
: {
value: undefined,
done: true,
};
},
} as IterableIterator<string>;
};

return iterator();
}
}

class MapObject {
test1: Test1;
test2: Test2;
constructor(test1: IteratorClass<Test1>, test2: IteratorClass<Test2>) {
this.test1 = new test1();
this.test2 = new test2();
}
printItem() {
let stack = [this.test1, this.test2];

stack.forEach((instance) => {
let iterator = instance.iterator();
let done: any, value: any;
do {
({ done, value } = iterator.next());
if (value) {
console.log(value);
}
} while (!done);
});
}
}

单一职责

一个类应该只有一个引起变化的原因。, 当一个类有多个变化的可能时,会增加维护的成本,或导致其他的功能出现错误。

内聚这个术语也可以看作是衡量单一指责的一个表述。

组合模式

组合模式相对于之前提到的代码组合更加具体,可以理解为代码组合包括组合模式。

现在有这样的几个对象,每个对象用不同的数据结构保存自己的数据,而且数据还可能分级,下一级是另一个对象,形成一个树的结构。需要实现一个迭代器,能在树的不同节点中以及下一层节点中移动。

实现这个功能可以使用组合模式,组合模式允许你将对象组合成树形结构来表现 ‘整体/部分’ 层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。

组合的目的是忽略整体和个体的差别,应用在整体上的操作同样可以应用在个体上。

遵循面向接口的变成方式,首先实现抽象类,所有的对象以及叶子节点的对象都需要实现抽象类,并且从冲向类中继承 print 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
abstract class AbstractDataCollection {
abstract createIterator(): any;
print(): void {
let done: any, value: any;
const iterator = this.createIterator();
do {
({ done, value } = iterator.next());
if (!done) {
value.print();
}
} while (!done);
}
}

不同的对象集合可以组合但是由于实现方式不同,需要各自实现抽象相类中的 createIterator 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
class DataCollection1 extends AbstractDataCollection {
data: any[] = [];
add(dataItem: any) {
this.data.push(dataItem);
return this;
}
iterator() {
return this.data[Symbol.iterator]();
}
createIterator() {
return new CompositeIterator(this.iterator());
}
}
class DataCollection2 extends AbstractDataCollection {
data: { [key: string]: any } = {};
add(dataItem: any, key: string) {
this.data[key] = dataItem;
return this;
}
iterator() {
let data = this.data;
let keys = Object.keys(data);
let index = 0;
return {
next() {
return index < keys.length
? {
value: data[keys[index++] as keyof typeof data],
done: false,
}
: {
value: undefined,
done: true,
};
},
};
}
createIterator() {
return new CompositeIterator(this.iterator());
}
}

type LinkData = {
next: LinkData | null;
value?: any;
};

class DataCollection3 extends AbstractDataCollection {
data: LinkData = { next: null };
last: LinkData = this.data;
add(dataItem: any) {
this.last.next = {
value: dataItem,
next: null,
};
this.last = this.last.next;
return this;
}
iterator() {
let data: LinkData | null = this.data;
return {
next() {
data = data!.next;

if (data === null) {
return {
done: true,
value: undefined,
};
} else {
return {
done: false,
value: data.value,
};
}
},
};
}
createIterator() {
return new CompositeIterator(this.iterator());
}
}

class DataItem extends AbstractDataCollection {
data: any;
constructor(data: any) {
super();
this.data = data;
}
createIterator() {
return new NullIterator();
}
print(): void {
console.log(this.data);
}
}

叶子节点的 createIterator 方法返回一个空的迭代器,真实环境中经常会使用这个方式添加占位,让所有对象的行为保持一致。

对象集合的 createIterator 方法,实现了一个 Iterator 类,把没有迭代完成的迭代器重新放回到队列中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class CompositeIterator<T> implements Iterator<T> {
stack: Iterator<T>[] = [];
constructor(iterator: Iterator<T>) {
this.stack.push(iterator);
}
next(...args: [] | [undefined]): IteratorResult<T, any> {
if (this.stack.length) {
const iterator = this.stack.pop();

let component = iterator!.next();
if (!component.done) {
this.stack.push(iterator!);
}
return component;
} else {
return {
value: undefined,
done: true,
};
}
}
}

class NullIterator<T> implements Iterator<T> {
next(...args: [] | [undefined]): IteratorResult<T, any> {
return {
done: true,
value: undefined,
};
}
}

最后实现遍历的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
const t = new DataCollection1();
t.add(new DataItem(1));
t.add(new DataItem(2));

const p = new DataCollection2();

p.add(new DataItem("a"), "a");
p.add(new DataItem("b"), "b");

const q = new DataCollection3();
q.add(new DataItem("p"));

const w = new DataCollection2();
w.add(new DataItem("w1"), "w1");
w.add(new DataItem("w2"), "w2");

q.add(w);

q.add(new DataItem("q"));

class MapObject extends AbstractDataCollection {
stack: any[] = [];
add(instance: AbstractDataCollection) {
this.stack.push(instance);
return this;
}
iterator() {
return this.stack[Symbol.iterator]();
}
createIterator() {
return new CompositeIterator(this.iterator());
}
}
const o = new MapObject();

o.add(t);
o.add(p);
o.add(q);
o.print();

真实世界中的组合模式没有这么刻板,例如 React 组件的组合,通过单向数据流或状态管理工具,只要调用外层组件的方法即可,无需关心子组件的实现。

有时不同的节点之间需要双向连接用于回退,或反查父节点。

如果某个节点作为计算功能,并且频繁调用,可以考虑使用缓存。

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2015-2025 SunZhiqi

此时无声胜有声!

支付宝
微信