模式动机(Iterator Pattern):对于聚合类的对象进行访问时,比如list,queue等,我们总希望有一个统一的访问接口,或者实现对多个聚合类对象的统一访问,或者实现对一个聚合类的多种不同访问方式,此时迭代器模式可以满足我们的这种需求。
迭代器就是在不暴露聚合类对象内部结构的前提下,对外提供了一种访问该对象的方法。
一般而言,一个迭代器是和一个聚合类紧密相连的。如何实现只允许迭代器访问聚合类的内部数据而避免其他类访问呢?这里需要借助于C++中的友元类,令迭代器类为聚合类 的友元类即可实现这个目标。
具体迭代器实现时有如下问题需要考虑:
1》 迭代遍历过程由迭代器控制还是由用户控制
若由用户控制时,这种迭代器称为外部迭代器。此时,客户必须负责向前推进遍历过程,显式地向迭代器请求下一个元素;若由迭代器控制,则称为内部迭代器。此时,迭代器会自动对聚合对象中的每个元素执行客户指定的操作。
大多数情况下,外部迭代器比内部迭代器更加灵活,可适应多个不同类型的聚合类对象,且扩展更容易。
2》 遍历算法是由迭代器定义还是由聚合类本身定义
若迭代器负责遍历算法,那么很容易在相同的聚合类上使用不同的迭代算法,同时也易于在不同的聚合类上使用同一迭代算法;若聚合类本身定义迭代算法,那么迭代器只负责存储遍历的当前状态,相当于一个指向当前状态的指针,因此我们称这种迭代器为游标(cursor)。
为了扩展迭代器的通用性,我们经常使用多态迭代器类型,即同时拥有一个抽象迭代器接口和具体迭代器实现类。对于不同类型的聚合对象,可以使用不同类型的迭代器实现类,这样就需要用工厂模式来分配迭代器对象。此时又有一个问题,分配的迭代器是谁来负责删除。如果由用户负责,可能造成内存泄漏,因为程序可能很复杂而忘记delete,或者在delete之前程序发生了异常。那么如何解决呢?
方法就是使用代理模式在栈上分配一个迭代器代理,由该代理负责删除堆上分配的空间。这样当程序结束时,所有分配的空间都会得到释放。要定义这种迭代器代理,必须实现如下操作:
1> 支持解引用操作 *
2> 赋值操作 =
3> ->操作
4> 比较操作,如!= / ==
5> 自增操作 ++
为了代码的简洁性,我们只重载了 -> 操作,另外需要将复制构造函数和赋值操作运算符声明为private,这样既可以防止多次delete同一对象,又可以防止编译器提供的默认实现。
模式结构图:
模式代码:
bt_迭代模式.h:
1 #ifndef IP_H 2 #define IP_H 3 #include <iostream> 4 #include <string> 5 using namespace std; 6 7 /* 8 抽象迭代器 9 */ 10 class Iterator 11 { 12 public: 13 virtual ~Iterator(){ } 14 virtual void First() = 0; 15 virtual void Next() = 0; 16 virtual bool IsDone() = 0; 17 virtual char CurrentItem() = 0; 18 }; 19 20 /* 21 抽象聚合类 22 */ 23 class Aggregate 24 { 25 public: 26 virtual ~Aggregate(){ } 27 virtual Iterator* CreateIterator() = 0; 28 virtual int Count() const = 0; 29 virtual char Get(int index) const = 0; 30 }; 31 32 33 /* 34 具体迭代器 35 */ 36 class ConcreteIterator : public Iterator 37 { 38 public: 39 ConcreteIterator(const Aggregate* a) : aggregate(a), currentIndex(0){ } 40 virtual void First(){ currentIndex = 0; } 41 virtual void Next(){ currentIndex++; } 42 virtual bool IsDone(){ return currentIndex >= aggregate->Count(); } 43 virtual char CurrentItem() 44 { 45 if(IsDone()) 46 { 47 cout << "已遍历完毕" << endl; 48 return 0; 49 } 50 else 51 return aggregate->Get(currentIndex); 52 } 53 54 private: 55 const Aggregate* aggregate; 56 int currentIndex; 57 }; 58 59 /* 60 具体聚合类 61 */ 62 class ConcreteAggregate : public Aggregate 63 { 64 public: 65 ConcreteAggregate(string str){ name = str; } 66 virtual Iterator* CreateIterator() 67 { 68 return new ConcreteIterator(this); 69 } 70 virtual int Count() const{ return name.size(); } 71 virtual char Get(int index) const{ return name.at(index); }; 72 73 private: 74 string name; 75 }; 76 77 #endif // IP_H
测试用例.cpp:
1 #include "bt_迭代器模式.h" 2 int main() 3 { 4 cout << "***** 迭代器模式测试 *****" << endl; 5 string name("benxintuzi"); 6 Aggregate* aggregate = new ConcreteAggregate(name); 7 Iterator* iter = new ConcreteIterator(aggregate); 8 for(iter->First(); !iter->IsDone(); iter->Next()) 9 cout << iter->CurrentItem() << endl; 10 11 delete iter; 12 delete aggregate; 13 14 return 0; 15 }
模式扩展:
将迭代器模式进行扩展,使得可以通过迭代器代理自己释放分配的内存,而不是依赖客户的手动操作。定义代理类如下:
// ... class IteratorPtr /* 迭代器的代理类 */ { public: IteratorPtr(Iterator* it) : iter(it){ } ~IteratorPtr(){ delete iter; } // 删除堆上的迭代器 Iterator* operator->(){ return iter; } private: IteratorPtr(const IteratorPtr&); IteratorPtr& operator=(const IteratorPtr&); private: Iterator* iter; }; // ... int main() { cout << "***** 迭代器代理模式测试 *****" << endl; string name("benxintuzi"); Aggregate* aggregate = new ConcreteAggregate(name); IteratorPtr iterPtr(aggregate->CreateIterator()); // 对迭代器进行包装,并且代理类指定在栈上,程序结束后代理类对象自动回收 for(iterPtr->First(); !iterPtr->IsDone(); iterPtr->Next()) cout << (iterPtr->CurrentItem()) << endl; return 0; }
模式总结:
:: 支持以不同的方式遍历一个聚合类,只需定义抽象迭代器的具体实现类即可更改遍历方法。
:: 迭代器模式简化了聚合类的接口,使得聚合类本身不必再维护如何遍历的代价了。
:: 在同一个聚合类上,通过不同的迭代器实例可以同时执行多个遍历操作,互不影响。