golang设计模式之迭代器模式

栏目: Go · 发布时间: 5年前

内容简介:wiki: 在 物件导向程式设计里,迭代器模式是一种设计模式,是一种最简单也最常见的设计模式。它可以让使用者透过特定的介面巡访容器中的每一个元素而不用了解底层的实作。简单点说,为一个容器设置一个迭代函数,可以使用这个迭代函数来顺序访问其中的每一个元素,而外部无需知道底层实现。如果再结合访问者模式,向其中传入自定义的访问者,那么就可以让访问者访问容器中的每个元素了。

wiki: 在 物件导向程式设计里,迭代器模式是一种设计模式,是一种最简单也最常见的设计模式。它可以让使用者透过特定的介面巡访容器中的每一个元素而不用了解底层的实作。

简单点说,为一个容器设置一个迭代函数,可以使用这个迭代函数来顺序访问其中的每一个元素,而外部无需知道底层实现。

如果再结合访问者模式,向其中传入自定义的访问者,那么就可以让访问者访问容器中的每个元素了。

类图

golang设计模式之迭代器模式

(图源网络)

角色

  • 抽象聚合类: 定义一个抽象的容器

  • 具体聚合类: 实现上面的抽象类,作为一个容器,用来存放元素,等待迭代

  • 抽象迭代器: 迭代器接口,每个容器下都有一个该迭代器接口的具体实现

  • 具体迭代器: 根据不同的容器,需要定义不同的具体迭代器,定义了游标移动的具体实现

举个栗子

  1. 创建抽象容器结构体
// 容器接口
 type IAggregate interface {
 	Iterator() IIterator
 }
复制代码
  1. 创建抽象迭代器
// 迭代器接口
 type IIterator interface {
     HasNext() bool
     Current() int
     Next() bool
 }
复制代码

迭代器的基本需求,需要有判定是否迭代到最后的方法 HasNext() ,需要有获得当前元素的方法 Current() ,需要有将游标移动到下一个元素的方法 Next()

  1. 实现容器
// 具体容器
  type Aggregate struct {
    container []int // 容器中装载 int 型容器
  }
  // 创建一个迭代器,并让迭代器中的容器指针指向当前对象
  func (a *Aggregate) Iterator() IIterator {
       i := new(Iterator)
       i.aggregate = a
       return i
   }
复制代码

为了简便,这里我们仅仅让容器中存放 int 类型的数据

  1. 实现迭代器
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
 }
复制代码
  1. 使用迭代器
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 不是一个切片或数组,而是一个结构体数组,我们却只需要遍历每个结构体中的某个字段呢?结构体的某个字段是数组,又或者,这个结构体的需要迭代的字段是私有的,在包外无法访问,所以这时是用迭代器,就可以自定义一个迭代函数,对外仅仅暴露几个方法,来获取数据,而外部不会在意容器内究竟是结构体还是数组。

实际栗子

我可能又双叕要写自己造的轮子了...

不过这里我只说说我对于迭代器模式的一个实际应用, 这个轮子没有开发完,只是个半半半成品

github.com/silsuer/bin…

这是我计划写的一个不借助 标准库中的 template 实现的模板引擎,后来工作太忙,就搁浅了,涉及到了编译原理的一些知识,目前只写到了词法分析...

可能要等 Bingo 的所有模块都写完之后再去实现了吧...

废话有点多... 入正题...

模板引擎需要将模板中的字符,按照模板标记的左右定界符分割成词法链,例如下面的模板:

{{ for item in navigation }}
     <li>tag</li>
 {{ endfor }}
复制代码

这个模板的意思是遍历 navigation ,打印出对应数量的 li 标签

将会生成如下的词法链

[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() 函数的实现和第一个栗子差不多,就不贴了,代码在 这里

这样就可以对生成的词法链进行打印了,方便了后续调试开发...


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Google 广告优化与工具

Google 广告优化与工具

宫鑫 / 电子工业出版社 / 2010-7 / 60.00元

《Google 广告优化与工具》全面地阐述了Google AdWords这个高效广告投放平台的各方面内容,包括Google广告的渠道组成、质量得分、关键词和广告语策略、后期跟踪机制以及各种辅助工具的使用等。引导读者一步步建立一个强大的Google搜索引擎营销投放策略。本书案例丰富、知识面广且层次清晰,适用于各个层次的搜索引擎营销优化人员,不论是电商企业的领导人、工作在账户优化第一线的具体操作者、营......一起来看看 《Google 广告优化与工具》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

URL 编码/解码
URL 编码/解码

URL 编码/解码

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具