特色技术
专业从事预应力结构体系的设计、施工一体化解决方案
设计模式 | Iterator设计模式
- 分类:程序技术
- 作者:
- 来源:
- 发布时间:2019-10-22 20:32
- 访问量:
【概要描述】此文是日本作者结城浩著作的《图解设计模式》的译作,原文用java程序编写,对熟练掌握C++、对java不熟悉的读者来说,程序读起来相当费劲。因此本文作者将书中的23个设计模式的程序全部用visual C++6.0和STL库进行了重新编写和编译。
设计模式 | Iterator设计模式
【概要描述】此文是日本作者结城浩著作的《图解设计模式》的译作,原文用java程序编写,对熟练掌握C++、对java不熟悉的读者来说,程序读起来相当费劲。因此本文作者将书中的23个设计模式的程序全部用visual C++6.0和STL库进行了重新编写和编译。
- 分类:程序技术
- 作者:
- 来源:
- 发布时间:2019-10-22 20:32
- 访问量:
1.0 前言
此文是日本作者结城浩著作的《图解设计模式》的译作,原文用java程序编写,对熟练掌握C++、对java不熟悉的读者来说,程序读起来相当费劲。因此本文作者将书中的23个设计模式的程序全部用visual C++6.0和STL库进行了重新编写和编译。
本文是研究C++程序和程序设计技术的绝佳文档,欢迎各位转载和阅读。
1.1 Iterator模式
使用C++语言显示数组arr中的元素时,我们可以使用下面这样的for循环语句遍历数组
for (int i=0;i<length;i++)
{
cout<<arr[i]<<endl;
}
请注意这段代码中的循变量i。该变量的初始值是0,然后会递增为1,2,3,...,程序则在每次i递增后都输出arr[ i ]。我们在程序中经常会看到这样的for循环语句。
数组中保存了很多元素,通过指定数组下标,我们可以从中选择任意一个元素。
arr[0] 最开始的元素(第0个元素)
arr[1] 下一个元素(第1个元素)
.
.
.
arr[i] (第i个元素)
.
.
.
arr[length-1] 最后一个元素
for语句中的i++的作用是让i的值在每次循环后自增1,这样就可以访问数组中的下一个元素,下下一个元素,再下下一个元素,也就实现了从头至尾逐一遍历数组元素的功能。
将这里的循环变量i的作用抽象化、通用化后形成的模式,在设计模式中称为Iterator模式。
Iterator模式用于在数据集合中按照顺序遍历集合。英语单词 Iterator有反复做某件事情的意思。汉语称为“迭代器”。
我们将在本章中学习Iterator模式
1.2 示例程序
首先,让我们来着一段实现了Iterator模式的示例程序,这示例程序的作用是将书(Book)放置到书架( Book Shelf)中,并将书的名字按顺序显示出来(图1-1)。
图1-1 示例程序的示意图
Aggregate接口
Aggregate接口(代码清单1-1)是所要遍历的集合的接口。实现了该接口的类将成为一个可
以保存多个元素的集合,就像数组一样。Aggregate有“使聚集”“集合”的意思。
代码清单1-1 Aggregate接口(Aggregate.h)
#include "Iterator.h"
#include "Book.h"
class Aggregate
{
public:
Aggregate();
virtual ~Aggregate();
virtual Iterator* CreateIterator()=0;
virtual void push(const Book&book)=0;
};
注:
1、在这里用CreateIterator纯虚函数代替原书中的Iterator(),笔者认为更符合接口函数的意图。
2、java用interface标识符来表示接口类,C++用纯虚函数来表示接口函数。
3、本文添加了一个额外的接口函数push,用来表示给集合添加元素。
图1-2 示例程序的类图
表1-1 类和接口的一览表
在Aggregate接口中声明的方法中只有一个-------------CreateIterator方法。该方法会生成一个用于遍历集合迭代器。
想要遍历集合中的元素时,可以调用CreateIterator方法来生成一个实现了Iterator接口的类的实例。
Iterator接口
接下来我们看看Iterator接口(代码清单1-2)。Iterator接口用于遍历集合中的元素,其作用相当于循环语句中的循环变量。那么,在 Iterator接口中需要有哪些方法呢? Iterator接口的定义方式有很多种,这里我们编写了最简单的 Iterator接口。
代码清单1-2 Iterator接口(Iterator.h)
#include "Book.h"
class Iterator
{
public:
Iterator();
virtual ~Iterator();
virtual bool hasnext()=0;
virtual Book next()=0;
};
这里我们声明了两个方法,即判断是否存在下一个元素的 hasnext方法,和获取下一个元素的next方法。
hasnext方法的返回值是boolean类型的,其原因很容易理解。当集合中存在下一个元素时,该方法返回true;当集合中不存在下一个元素,即已经遍历至集合末尾时,该方法返回false。has next方法主要用于循环终止条件。
这里有必要说明一下next方法。该方法的返回类型是 Book,这表明该方法返回的是集合中的一个元素(某本书)。但是,next方法的作用并非仅仅如此。为了能够在下次调用next方法时正确地返回下一个元素,该方法中还隐含着将选代器移动至下一个元素的处理。说“隐含”,是因为Iterator接口只知道方法名。想要知道next方法中到底进行了什么样的处理,还需要看一下实现了Iterator接口的类( BookShelfIterator),这样,我们才能看懂next方法的作用。
Book类
Book类是表示书的类(代码清单1-3)。但是这个类的作用有限,它可以做的事情只有一件-------------通过 getName方法获取书的名字。书的名字是在外部调用Book类的构造函数并初始化Book类时,作为参数传递给Book类的。
代码清单1-3 Book类(Book.h)
#include <string>
using namespace std;
class Book
{
public:
Book();
Book(const string&str);
virtual ~Book();
string& getName(){return name;}
private:
string name;
};
BookShe1f类
Bookshe1f类是表示书架的类(代码清单1-4)。由于需要将该类作为集合进行处理,因此它
实现了 Aggregate接口。代码中的 public Aggregate部分即表示这一点。此外,请注意在 Book Shelf类中还实现了 Aggregate接口的 CreateIterator和push方法。
代码清单1-4 BookShelf类(BookShelf.h)
#include "Aggregate.h"
#include "Book.h"
#include <iostream>
using namespace std;
const maxnum=4;
class BookShelf : public Aggregate
{
private:
Book* books;
int last;
public:
BookShelf();
virtual ~BookShelf();
Iterator* CreateIterator();
void push(const Book&book);
int getLength(){return last;}
Book& getBook(int index){return books[index];}
};
这个书架中定义了 books字段,它是Book类型的数组。该数组的大小( max size)在生成
Book Shelf的实例时就被指定了。之所以将 books字段的可见性设置为 private,是为了防止
外部不小心改变了该字段的值。
接下来我们看看 CreateIterator方法。该方法会生成并返回 BookShelfIterator类的实例作为
Book Shelf类对应的 Iterator。当外部想要遍历书架时,就会调用这个方法。
BookShelfIterator类
接下来让我们看看用于遍历书架的 BookShelfIterator类(代码清单1-5)
代码清单1-5 BookShelfIterator类(BookShelfIterator.h)
#include "Iterator.h"
#include "BookShelf.h"
class BookShelfIterator:public Iterator
{
public:
BookShelfIterator();
virtual ~BookShelfIterator();
BookShelfIterator(const BookShelf&bs);
bool hasnext();
Book next();
private:
BookShelf bookshelf;
int index;
};
因为 BookShelfIterator类需要发挥 Iterator的作用,所以它实现了 Iterator接口。
bookshe1f属性表示 BookShelfIterator所要遍历的书架。Index属性表示迭代器当前所指向的书的下标。
构造函数会将接收到的BookShelf的实例保存在 bookshelf字段中,并将 index初始化为0。
hasnext方法是 Iterator接口中所声明的方法。该方法将会判断书架中还有没有下一本书,如果有就返回true,如果没有就返回fa1se。而要知道书架中有没有下一本书,可以通过比较index和书架中书的总册数( BookShelf. getlength()的返回值)来判断。
next方法会返回迭代器当前所指向的书(Book的实例),并让迭代器指向下一本书。它也是
Iterator接口中所声明的方法。next方法稍微有些复杂,它首先取出book变量作为返回值,
然后让 index指向后面一本书。
如果与本章开头的for语句来对比,这里的“让 index指向后面一本书”的处理相当于其中
的i++,它让循环变量指向下一个元素。
main函数
至此,遍历书架的准备工作就完成了。接下来我们使用main类(代码清单1-6)来制作一个小书架。
#include "stdafx.h"
#include "BookShelf.h"
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
BookShelf* bs=new BookShelf;
Book bk1("Around the world in 80 Days");
Book bk2("Bible");
Book bk3("Cinderella");
Book bk4("Daddy Long legs");
bs->push(bk1);
bs->push(bk2);
bs->push(bk3);
bs->push(bk4);
Iterator* it=bs->CreateIterator();
while (it->hasnext())
{
Book book=(Book)it->next();
cout<<book.getName()<<endl;
}
printf("Hello World!\n");
return 0;
}
这段程序首先设计了一个能容纳4本书的书架,然后按书名的英文字母顺序依次向书架中放入了下面这4本书。
Around the world in 80 Days (《环游世界80天》)
Bible(《圣经》)
Cinderella(《灰姑娘》)
Daddy Long legs(《长腿爸爸》)
为了便于理解,笔者特意选了这4本首字母分别为A、B、C、D的书。
通过Book shelf , iterator()得到的it是用于遍历书架的 Iterator实例。whi1e部分的条件当然就是it. Has Next()了。只要书架上有书,whi1e循环就不会停止。然后,程序会通过it.next()一本一本地遍历书架中的书。
图1-3展示了上面这段代码的运行结果。
图1-3运行结果
1.3 Iterator模式中的登场角色
读完示例程序,让我们来看看 Iterator模式中的登场角色。
◆ Iterator(迭代器)
该角色负责定义按顺序逐个遍历元素的接口(API)。在示例程序中,由 Iterator接口扮演这个角色,它定义了 hasnext和next两个方法。其中, hasnext方法用于判断是否存在下一个元素,next方法则用于获取该元素。
◆Concrete Iterator(具体的迭代器)
该角色负责实现 Iterator角色所定义的接口(API)。在示例程序中,由BookShelfIterator类扮演这个角色。该角色中包含了遍历集合所必需的信息。在示例程序中, BookShelf类的实例存在 bookshelf字段中,被指向的书的下标保存在 index字段中。
◆Aggregate(集合)
该角色负责定义创建 Iterator角色的接口(API)。这个接口(API)是一个方法,会创建出“按顺序访问保存在我内部元素的人”。在示例程序中,由 Aggregate接口扮演这个角色,它里面定义了 CreateIterator方法。
◆ Concrete Aggregate(具体的集合)
该角色负责实现 Aggregate角色所定义的接口(API)。它会创建出具体的 Iterator角色,即Concrete Iterator角色。在示例程序中,由 BookShelf类扮演这个角色,它实现了 CreateIterator方法。
图1-4是展示了 Iterator模式的类图。
图1-4 Iterator模式的类图
1.4 拓展思路的要点
不管实现如何变化,都可以使用 Iterator。
为什么一定要考虑引人 Iterator这种复杂的设计模式呢?如果是数组,直接使用for循环语句进行遍历处理不就可以了吗?为什么要在集合之外引入Iterator这个角色呢?
一个重要的理由是,引人Iterator后可以将遍历与实现分离开来。请看下面的代码
while (it->hasnext())
{
Book book=(Book)it->next();
cout<<book.getName()<<endl;
}
这里只使用了 Iterator的 hasnext方法和next方法,并没有调用 BookShelf的方法。
也就是说,这里的whi1e循环并不依赖于 BookShe1f的实现。
如果编写BookShelf的开发人员决定放弃用数组来管理书本,而是用STL Vector取而代之,会怎样呢?不管 BookShelf如何变化,只要 BookShelf的 CreateIterator方法能正确地返回 Iterator的实例(也就是说,返回的 Iterator类的实例没有问题, hasnext和next方法都可以正常工作),即使不对上面的whi1e循环做任何修改,代码都可以正常工作。
这对于BookShelf的调用者来说真是太方便了。设计模式的作用就是帮助我们编写可复用的类。所谓“可复用”,就是指将类实现为“组件”,当一个组件发生改变时,不需要对其他的组件进行修改或是只需要很小的修改即可应对。
这样也就能理解为什么在示例程序中 CreateIterator方法的返回值不是 BookShelfIterator类型而是Iterator类型了(代码清单1-6).这表明,这段程序就是要使用CreateIterator的方法进行编程,而不是BookShel Iterator的方法。
难以理解抽象类和接口
难以理解抽象类和接口的人常常使用 Concrete Aggregate角色和 Concrete Iterator角色编程,而不使用 Aggregate接口和 Iterator接口,他们总想用具体的类来解决所有的问题。
但是如果只使用具体的类来解决问题,很容易导致类之间的强耦合,这些类也难以作为组件被再次利用。为了弱化类之间的耦合,进而使得类更加容易作为组件被再次利用,我们需要引入抽象类和接口。
这也是贯穿本书的思想。即使大家现在无法完全理解,相信随着深入阅读本书,也一定能够逐渐理解。请大家将“不要只使用具体类来编程,要优先使用抽象类和接口来编程”印在脑海中。
Aggregate和 Iterator的对应
请大家仔细回忆一下我们是如何把 BookShelfIterator类定义为 BookShelf类的Concrete Iterator 角色的。 BookShelfIterator类知道 BookShelf是如何实现的。也正是因为如此,我们才能调用用来获取下一本书的 getBook方法。
也就是说,如果 BookShelf的实现发生了改变,即 getBook方法这个接口(AP1)发生变化时,我们必须修改 BookShelfIterator类。
正如 Aggregate和 Iterator这两个接口是对应的一样, Concrete Aggregate和Concrete Iterator 这两个类也是对应的.
容易弄错“下一个”
在Iterator模式的实现中,很容易在next方法上出错。该方法的返回值到底是应该指向当前元素还是当前元素的下一个元素呢?更详细地讲,next方法的名字应该是下面这样的。
return current Element And Advance To Next Position
也就是说,next方法是“返回当前的元素,并指向下一个元素”。
还容易弄错“最后一个”
在 Iterator模式中,不仅容易弄错“下一个”,还容易弄错“最后一个”。hasnext方法在返回
最后一个元素前会返回true,当返回了最后一个元素后则返回fa1se。稍不注意,就会无法正确
地返回“最后一个”元素。
请大家将 hasnext方法理解成“确认接下来是否可以调用next方法”的方法就可以了。
多个Iterator
“将遍历功能置于 Aggregate角色之外”是 Iterator模式的一个特征。根据这个特征,可以针对一个 Concrete Aggregate角色编写多个 Concrete Iterator角色。
迭代器的种类多种多样
在示例程序中展示的 Iterator类只是很简单地从前向后遍历集合。其实,遍历的方法是多种多样的。
·从最后开始向前遍历
·既可以从前向后遍历,也可以从后向前遍历(既有next方法也有 previous方法)
·指定下标进行“跳跃式”遍历
学到这里,相信大家应该可以根据需求编写出各种各样的 Iterator类了。
不需要 delete Iterator
在Java中,没有被使用的对象实例将会自动被删除(垃圾回收,GC)。因此,在 iterator中
不需要与其对应的 delete Iterator方法。
注:但是在C++中需要手工删除对象实例,这已经超出了本书的范畴。
1.5 相关的设计模式
◆ Visitor模式
Iterator模式是从集合中一个一个取出元素进行遍历,但是并没有在 Iterator接口中声明对取出的元素进行何种处理。
Visitor模式则是在遍历元素集合的过程中,对元素进行相同的处理。
在遍历集合的过程中对元素进行固定的处理是常有的需求。 Visitor模式正是为了应对这种需求而出现的。在访问元素集合的过程中对元素进行相同的处理,这种模式就是 Visitor模式。
◆ Composite模式
Composite模式是具有递归结构的模式,在其中使用 Iterator模式比较困难。
◆ Factory Method模式
在 iterator方法中生成 Iterator的实例时可能会使用 Factory Method模式。
扫二维码用手机看