Go设计模式(23)-迭代器模式

本文介绍了迭代器模式在集合操作中的应用,通过实例展示了如何在二维数组上实现广度优先遍历。强调了模式在复杂结构中的优点,如职责单一、统一接口和灵活扩展。

迭代器模式从来没有写过,第一次接触迭代器,还是好多年前学C++的STL的时候。当时觉得用迭代器太麻烦了,后来用习惯了觉得真香。

UML类图位置:https://www.processon.com/view/link/60d29bf3e401fd49502afd25

本文代码链接为:https://github.com/shidawuhen/asap/blob/master/controller/design/23iterator.go

1.定义

1.1 迭代器模式

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

UML:

图片

1.2分析

通过UML可以看出,对于集合Aggregate,其遍历能力被拆了出来,由Iterator负责遍历。

大家可能有疑问,可以用for循环解决的问题,为啥要搞得这么复杂呢?

其实主要看集合结构的复杂性,如果是普通的数组,可以不需要Iterator,直接使用for循环即可。如果是复杂的集合呢?对于这个集合需要有多种遍历方案呢?

如对于图结构,有广度优先、深度优先两种遍历方式,都在图集合里实现,是否感觉违背了职责单一原则。

所以对于复杂结构,迭代器有如下优势:

  1. 这种拆分,使集合和迭代器职责更加单一,符合单一职责原则

  2. 迭代器结构统一,方便使用,使用者无需知道遍历细节便可遍历集合

  3. 符合开闭原则,可以按照需求自己开发迭代器,无需改动集合类

  4. 符合里氏替换原则,可以方便的进行迭代方案的更换

通过UML可发现设计思路:迭代器中需要定义first()、isDone()、currentItem()、next() 四个最基本的方法。待遍历的集合对象通过依赖注入传递到迭代器类中。集合可通过CreateIterator() 方法来创建迭代器。

2.应用场景

迭代器模式一般在library中使用的比较多,毕竟library提供的大多是基础结构。实际业务场景中,很少需要自己编写迭代器。但代码还是要写的,这次写图集合与深度优先遍历迭代器,大家如果对其它类型的图迭代器感兴趣的话,可自行编写。

3.代码实现

对于二维数组,从[0,0]开始,以广度优先顺序遍历。如对于

1 2

3 4

广度优先遍历顺序为1 2 3 4。

package main

import (
	"fmt"
	"math/rand"
)

/**
 * @Author: Jason Pang
 * @Description: 二维数组,当做图集合,写个真正的图集合太麻烦了
 */
type TwoDimensionalArray struct {
	row   int64
	col   int64
	array [][]int64
}

/**
 * @Author: Jason Pang
 * @Description: 设置尺寸
 * @receiver t
 * @param row
 * @param col
 */
func (t *TwoDimensionalArray) SetSize(row int64, col int64) {
	t.row = row
	t.col = col
	t.array = make([][]int64, row)
	var i int64 = 0
	for i = 0; i < row; i++ {
		t.array[i] = make([]int64, col)
	}
}

/**
 * @Author: Jason Pang
 * @Description: 初始化数据
 * @receiver t
 */
func (t *TwoDimensionalArray) InitDefault() {
	if t.row <= 0 || t.col <= 0 {
		return
	}
	var i, j int64 = 0, 0
	for i = 0; i < t.row; i++ {
		for j = 0; j < t.col; j++ {
			t.array[i][j] = rand.Int63n(200)
		}
	}
}

/**
 * @Author: Jason Pang
 * @Description: 格式化输出。不能为指针。
 * @receiver t
 * @return string
 */
func (t TwoDimensionalArray) String() string {
	s := ""
	var i int64 = 0
	for i = 0; i < t.row; i++ {
		s += fmt.Sprintf("%v \n", t.array[i])
	}
	return s
}

/**
 * @Author: Jason Pang
 * @Description: 迭代器接口
 */
type Iterator interface {
	First()
	IsDone() bool
	CurrentItem()
	Next()
}

type BFSPos struct {
	x, y int64
}

/**
 * @Author: Jason Pang
 * @Description: 广度优先遍历
 */
type BFSIterator struct {
	data     *TwoDimensionalArray
	used     [][]bool
	queue    []BFSPos
	index    int64 //queue遍历的位置
	bfsIndex int64 //记录做广度优先遍历的位置
}

/**
 * @Author: Jason Pang
 * @Description: 赋值
 * @receiver d
 * @param data
 */
func (d *BFSIterator) Create(data *TwoDimensionalArray) {
	d.data = data
	var i int64 = 0
	d.used = make([][]bool, data.row)
	for i = 0; i < data.row; i++ {
		d.used[i] = make([]bool, data.col)
	}
	d.used[0][0] = true
	d.queue = make([]BFSPos, 1)
	d.queue[0] = BFSPos{0, 0}
	d.index = 0
	d.bfsIndex = 0
}

/**
 * @Author: Jason Pang
 * @Description: 初始数据
 * @receiver d
 */
func (d *BFSIterator) First() {
	fmt.Println(d.data.array[0][0])
}

/**
 * @Author: Jason Pang
 * @Description: 是否遍历结束
 * @receiver d
 * @return bool
 */
func (d *BFSIterator) IsDone() bool {
	if d.index == d.data.col*d.data.row {
		return true
	}
	return false
}

/**
 * @Author: Jason Pang
 * @Description: 当前数值
 * @receiver d
 */
func (d *BFSIterator) CurrentItem() {
	pos := d.queue[d.index]
	fmt.Println(d.index, ":", d.data.array[pos.x][pos.y])
}

/**
 * @Author: Jason Pang
 * @Description: 移动
 * @receiver d
 */
func (d *BFSIterator) Next() {
	if d.index >= d.data.row*d.data.col {
		fmt.Println("已到最后")
		return
	}
	//说明已经没有了,需要再加几个
	if d.index >= int64(len(d.queue))-1 {
		for d.bfsIndex < int64(len(d.queue)) && d.index < int64(len(d.queue)) {
			curI, curJ := d.queue[d.bfsIndex].x, d.queue[d.bfsIndex].y
			if curJ+1 < d.data.col && d.used[curI][curJ+1] == false {
				d.queue = append(d.queue, BFSPos{curI, curJ + 1})
				d.used[curI][curJ+1] = true
			}
			if curI+1 < d.data.row && curJ+1 < d.data.col && d.used[curI+1][curJ+1] == false {
				d.queue = append(d.queue, BFSPos{curI + 1, curJ + 1})
				d.used[curI+1][curJ+1] = true
			}
			if curI+1 < d.data.row && d.used[curI+1][curJ] == false {
				d.queue = append(d.queue, BFSPos{curI + 1, curJ})
				d.used[curI+1][curJ] = true
			}
			d.bfsIndex++
		}

	}
	d.index++
}

func main() {
	t := TwoDimensionalArray{}
	t.SetSize(3, 3)
	t.InitDefault()
	fmt.Printf("%s", t)

	iterator := BFSIterator{}
	iterator.Create(&t)
	for iterator.IsDone() != true {
		iterator.CurrentItem()
		iterator.Next()
	}
}

输出:

➜ myproject go run main.go

[10 151 21]

[51 137 120]

[158 148 16]

0 : 10

1 : 151

2 : 137

3 : 51

4 : 21

5 : 120

6 : 16

7 : 148

8 : 158

代码写的比较简单,但是仍然能看到迭代器模式的优点。

  1. 可以轻易的变更遍历算法

  2. TwoDimensionalArray可继承于接口,面向接口编程,扩展性会进一步增强

总结

迭代器模式可能对于大部分研发同学来说是不需要的,但对于搞基础框架、搞语言的同学来说应该经常会用。对于迭代器模式,并不是说学习怎么使用,更重要的一点是需要感知设计模式的思想内核。

最后

大家如果喜欢我的文章,可以关注我的公众号(程序员麻辣烫)

我的个人博客为:https://shidawuhen.github.io/

图片

往期文章回顾:

  1. 设计模式

  2. 招聘

  3. 思考

  4. 存储

  5. 算法系列

  6. 读书笔记

  7. 小工具

  8. 架构

  9. 网络

  10. Go语言

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员麻辣烫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值