Go设计模式-迭代器到底是不是一个多此一举的模式?原创
大家好,这里是陪你一起进步的网管~!上周收到了各位对设计模式的催更,这里我就直接开始啦,今天要一起学习的是迭代器模式。
迭代器模式(Iterator Design Pattern),也叫作游标模式(Cursor Design Pattern)。提供了一种方法顺序地访问一个聚合对象中的元素,而不是暴露该对象的内部表示。这里说的聚合对象也常被称作集合,是编程中最常使用的数据类型之一,有些编程序言还内置提供了整个集合框架,比如Java内置提供的Collection类族、Map类族。
如果集合基于列表, 那么遍历集合这项工作会很简单。 但如果集合基于栈、 树、 图和其他复杂的数据结构实现,那么该如何遍历其中的元素呢?
无论集合采用什么数据结构实现, 它都必须提供某种访问元素的方式, 便于其他代码访问其中的元素,怎么才能能让客户端用统一的方式访问不同结构的集合呢?这就需要用到迭代器模式了,迭代器模式的主要思想是将集合的遍历行为抽取为单独的迭代器对象。
迭代器模式用于遍历集合中的对象,很多语言里都内置了这种设计模式,迭代器模式的思想是将集合对象的遍历操作从集合类中拆分出来,放到迭代器实现类中,让两者的职责更单一。
我们可以先看下 Java 语言里内置给我们提供的迭代器,Java中把迭代器定义在java.util.Iterator
接口中,接口中的 hasNext
和 next
方法是最主要的两个方法,一个用来判断结合中是否还有下个元素,一个用于从集合中取出游标指向的元素,同时把游标指向下个元素。
public interface Iterator<E> {
boolean hasNext();
E next();
// ... 其他方法省略了
}
不管用哪种语言实现迭代器模式,这两个方法都是必不可少的。
有了迭代器接口 Iterator 后,每种集合都会提供Iterator的具体实现,用来让客户端通过迭代器遍历它们
List<String> list = new ArrayList<>();
list.add("one");
list.add("two");
list.add("three");
Iterator<String> iterator = list.iterator();
Set<String> set = new HashSet<>();
set.add("one");
set.add("two");
set.add("three");
Iterator<String> iterator2 = set.iterator();
while(iterator.hasNext()) {
Object nextObject = iterator.next();
}
下面我们看一下迭代器模式的构成,在 Java 语言里通过自身的集合框架和Iterator接口实现了迭代器模式的接口抽象和实现,所以写代码时可能不会太关注,待我们了解清楚迭代器模式的结构构成后再用代码自己实现起来也就不那么难了。
迭代器模式的结构
迭代器模式的结构,可以用下面这个 UML 类图表示:
-
Iterator 接口: 这个接口会定义一些基础的操作函数,如 hasNext()
或getNext()
等。通过名称就可以看出,这些方法可以帮助我们执行遍历集合、重启迭代等操作。 -
Collection 接口: 这个接口代表了要被遍历的集合。在这个接口里定义了一个 createIterator
方法,该方法会返回一个Iterator
的实例。 -
Concrete Iterator: Iterator
接口的具体实现类。 -
Concrete Collection: Collection
接口的具体实现类。 -
客户端 Client:通过集合和迭代器的接口与两者进行交互。 这样一来客户端无需与具体类进行耦合, 允许同一客户端代码使用各种不同的集合和迭代器。
Go源码实现
迭代器模式的编码实现环节,我们选择用Go语言给底层数据结构为列表的集合实现一个迭代器模式。例子为了演示方便易懂,选择了列表这个看似实现迭代器完全多此一举的集合给大家演示,大家不要忘了咱们上边强调过的,迭代模式的最大收益是让使用者即不必关心集合的底层结构,还能用统一的方式访问这些不同结构的集合。
首先按照迭代器模式的结构我们来声明迭代器和集合的接口。迭代器接口中定义两个核心方法hasNext
和getNext
type iterator interface {
hasNext() bool
getNext() *User
}
集合要能创建出遍历自身元素的迭代器,所以接口中必须要包含createIterator
方法。
type collection interface {
createIterator() iterator
}
定义好接口后接下来我们要的是编写集合的具体实现,实现中将集合对象的遍历操作从集合类中拆分出来,放到迭代器实现类中。
"本文使用的完整可运行源码
去公众号「网管叨bi叨」发送【设计模式】即可领取"
type User struct {
name string
age int
}
type userCollection struct {
users []*User
}
func (u *userCollection) createIterator() iterator {
return &userIterator{
users: u.users,
}
}
type userIterator struct {
index int
users []*User
}
func (ui *userIterator) hasNext() bool {
return ui.index < len(ui.users)
}
func (ui *userIterator) getNext() *User {
if ui.hasNext() {
user := ui.users[ui.index]
ui.index++
return user
}
return nil
}
有了迭代器后,客户端就能用迭代器完成集合对象的元素遍历了。
"本文使用的完整可运行源码
去公众号「网管叨bi叨」发送【设计模式】即可领取"
func main() {
userK := &User{
name: "Kevin",
age: 30,
}
userD := &User{
name: "Diamond",
age: 25,
}
userCollection := &userCollection{
users: []*User{userK, userD},
}
iterator := userCollection.createIterator()
for iterator.hasNext() {
user := iterator.getNext()
fmt.Printf("User is %v\n", user)
}
}
当然上面实现的迭代器遍历代码中没有加锁,所以不是线程安全的,大家可以自己完善下。
本文的完整源码,已经同步收录到我整理的电子教程里啦,可向我的公众号「网管叨bi叨」发送关键字【设计模式】领取。
总结
迭代器模式在平时编程的时候使用的并不多,像Java、C#编程时都自带了迭代器模式的实现,也支持实现语言内置的Iterator
接口来给自定义集合创建迭代器。
关于迭代器的使用场景,我们只要记住它的主要思想是将集合对象的遍历操作从集合类中拆分出来,放到迭代器实现类中,让两者的职责更单一的同时也让客户端不必关系该怎么去实现集合的迭代算法。
个人认为Java语言的Collection、Map类族中提供的各种迭代器是对该模式的典型应用,代码实现写的很优秀,值得借鉴学习。