定义
wiki: 在 物件导向程式设计里,迭代器模式是一种设计模式,是一种最简单也最常见的设计模式。它可以让使用者透过特定的介面巡访容器中的每一个元素而不用了解底层的实作。
简单点说,为一个容器设置一个迭代函数,可以使用这个迭代函数来顺序访问其中的每一个元素,而外部无需知道底层实现。
如果再结合 访问者模式,向其中传入自定义的访问者,那么就可以让访问者访问容器中的每个元素了。
类图
(图源网络)角色
-
抽象聚合类: 定义一个抽象的容器
-
具体聚合类: 实现上面的抽象类,作为一个容器,用来存放元素,等待迭代
-
抽象迭代器: 迭代器接口,每个容器下都有一个该迭代器接口的具体实现
-
具体迭代器: 根据不同的容器,需要定义不同的具体迭代器,定义了游标移动的具体实现
举个栗子
- 创建抽象容器结构体
// 容器接口
type IAggregate interface {
Iterator() IIterator
}
复制代码
- 创建抽象迭代器
// 迭代器接口
type IIterator interface {
HasNext() bool
Current() int
Next() bool
}
复制代码
HasNext()Current()Next()
- 实现容器
// 具体容器
type Aggregate struct {
container []int // 容器中装载 int 型容器
}
// 创建一个迭代器,并让迭代器中的容器指针指向当前对象
func (a *Aggregate) Iterator() IIterator {
i := new(Iterator)
i.aggregate = a
return i
}
复制代码
int
- 实现迭代器
type Iterator struct {
cursor int // 当前游标
aggregate *Aggregate // 对应的容器指针
}
// 判断是否迭代到最后,如果没有,则返回true
func (i *Iterator) HasNext() bool {
if i.cursor+1 < len(i.aggregate.container) {
return true
}
return false
}
// 获取当前迭代元素(从容器中取出当前游标对应的元素)
func (i *Iterator) Current() int {
return i.aggregate.container[i.cursor]
}
// 将游标指向下一个元素
func (i *Iterator) Next() bool {
if i.cursor < len(i.aggregate.container) {
i.cursor++
return true
}
return false
}
复制代码
- 使用迭代器
func main() {
// 创建容器,并放入初始化数据
c := &Aggregate{container: []int{1, 2, 3, 4}}
// 获取迭代器
iterator := c.Iterator()
for {
// 打印当前数据
fmt.Println(iterator.Current())
// 如果有下一个元素,则将游标移动到下一个元素
// 否则跳出循环,迭代结束
if iterator.HasNext() {
iterator.Next()
} else {
break
}
}
}
复制代码
container
实际栗子
我可能又双叕要写自己造的轮子了...
不过这里我只说说我对于迭代器模式的一个实际应用, 这个轮子没有开发完,只是个半半半成品
template
可能要等 Bingo 的所有模块都写完之后再去实现了吧...
废话有点多... 入正题...
模板引擎需要将模板中的字符,按照模板标记的左右定界符分割成词法链,例如下面的模板:
{{ for item in navigation }}
<li>tag</li>
{{ endfor }}
复制代码
navigationli
将会生成如下的词法链
[for]-> [item]-> [in]-> [navigation]-> [<li>tag</li>]-> [endfor]
复制代码
每个方括号代表一个词法链上的节点,每个节点都会区分出是文本节点还是语法节点。
具体的实现方法这里不说了,涉及到了词法分析器的状态转换,有兴趣的自己搜一搜就好,下面要实现的就是调试时打印词法链的过程,用到了迭代器模式。
词法链的结构体如下:
// 词法分析链包含大量节点
type LexicalChain struct {
Nodes []*LexicalNode
current int // 当前指针
Params map[string]interface{} // 变量名->变量值
TokenStream *TokenStream // token流,这是通过节点解析出来的
}
复制代码
对应的词法节点的结构体如下:
type LexicalNode struct {
T int // 类型(词法节点还是文本节点)
Content []byte // 内容,生成模版的时候要使用内容进行输出
tokens []*Token // token流
root *Token // 抽象语法树跟节点
lineNum int // 行数
stack []int // 符栈,用来记录成对的操作符
}
复制代码
Print()
// 打印节点值
func (n *LexicalNode) Print() {
switch n.T {
case textNode:
fmt.Println("[node type]: TEXT") // 文本节点
case lexicalNode:
fmt.Println("[node type]: LEXICAL") // 词法节点
default:
fmt.Println("[node type]: UNKNOWN TYPE") // 未知类型
break
}
fmt.Println("[line number]: " + strconv.Itoa(n.lineNum))
fmt.Println("[content]: " + string(n.Content))
}
复制代码
Print()
func (l *LexicalChain) Print() {
// 打印当前节点
l.Iterator(func(node *LexicalNode) {
fmt.Println("====================")
fmt.Println("[index]: " + strconv.Itoa(l.current))
node.Print()
})
}
复制代码
Iterator()
Print()
Iterator()
func (l *LexicalChain) Iterator(call func(node *LexicalNode)) {
// 对于链中的每个节点,执行传入的方法
call(l.Current()) // 调用传入的方法,将当前节点作为参数传入
for {
if l.Next() != -1 { // 这里和第一个栗子一样,将游标指向下一个元素,并继续调用 传入的回调
call(l.Current())
} else {
break // 如果迭代到了最后,则直接跳出循环,结束迭代
}
}
}
复制代码
Next()
这样就可以对生成的词法链进行打印了,方便了后续调试开发...
总结
JAVAphperlaravelfiltermap
优点:
- 这个不用说了,设计模式的最大优点——解耦,增加容器或者迭代器都无需修改源代码,并且简化了容器类,将迭代逻辑抽出来,放在了迭代器中
- 可以用不同的方式来遍历同一个对象(这就是上面说的,通过传入不同的回调来进行不同的迭代) 缺点:
- 迭代器模式算是将一个对象(容器)的存储职责和遍历职责分离了,就像第一个栗子中说的,新增一个容器类就要新增一个迭代器类,增加了系统的代码量和复杂性。
上述代码均放在 golang-design-patterns 这个仓库中
打个广告,推荐一下自己写的 go web框架 bingo,求star,求PR ~