Iterator模式
简介
面向对象编程思想以来,程序员们将常见的面向对象编程范式总结起来,就形成了23种设计模式,因为是适用于面向对象编程思想的,所以设计模式种最本质的核心就是继承、接口、封装、高内聚和低耦合的思想。所以在了解设计模式的过程中,多多思考这几个方面,可以帮助大家更好的理解它们。废话不多说,下面就开始介绍迭代器模式
声明: 本次的介绍,借鉴了图灵书籍《图解设计模式》(结城浩·著),示例基本一致;如果大家有兴趣,可以直接阅读该书
迭代器模式最主要的用途是方便我们进行遍历访问;其本质作用类似于下面的代码:
arr = ["三国演义", "红楼梦", "水浒传", "西游记"]
for name in arr:
print(name)
输出结果:
三国演义
红楼梦
水浒传
西游记
Process finished with exit code 0
迭代器模式就是将某个对象聚合起来,封装成一个聚合类中,该聚合类暴露出来一个能生成可迭代对象的方法;通过访问生成的可迭代对象进行遍历操作。简单来说,就是将上面代码片段中的name封装成类,arr封装成聚合类(内部存放name类实例),以方便我们将遍历操作同聚合方式解耦合。
下面我们来看一下具体实现例子。
接口和具体类
模块 | 类 | 说明 |
---|---|---|
aggregate | Aggregate. | 聚合类的接口 |
iterator. | Iterator. | 表示遍历的类的接口 |
book | Book | 表示书的类 |
bookshelf. | BookShelf. | 表示书架的类 |
bookshelfiterator | BookShelfIterator | 遍历书架的迭代器类 |
接口设计说明
本次示例用一个遍历书架上的书,并打印出书的名字来展示;
书是一个独立的对象,有很多属性,比如名称、页数、目录等,而书架则可以聚合很多书,也可以没有书,书架销毁后,并不影响书本身的存在,因为书还可以放在箱子里、柜子里等,所以书架同书之间属于聚合关系。
想象一个场景:现在我们有一整个书架的书,我们想知道其中每一本书的名字,访问顺序应该是这样的:先访问书架-再访问书-再访问书属性name;重复以上操作直到书架上的最后一本书。
这里可以用简单的for循环,但是今天我们用迭代器模式来实现。
UML图示
aggregate模块
提供了一个Aggregate的聚合抽象类,将聚合类的公共方法抽取出来,形成统一的接口。
iterator方法返回一个可迭代对象,访问者可用于遍历操作
iterator模块
提供Iterator迭代器抽象类,定义可迭代对象的接口,当然python中有特殊的魔术方法能够用于实现这个功能,我们为了展示设计模式的思想,暂时用普通方法来实现
Iterator类有两个公有方法has_next()和next(),其中has_next()返回一个bool值,表示迭代器是否存在下一个元素,用来判断是否遍历到末尾;next()方法用来取出下一个对象,实现方式为先取出对象,然后将索引值前移。
bookshelf模块
实现BookShelf类,继承自Aggregate,并实现iterator()方法。返回Iterator对象。
这里只是Aggregate的具体类的其中之一,实际应用中,你可以实现任何你想聚合的对象聚合类
bookshelfiterator模块
实现BookShelfIterator类,继承自Iterator,并实现has_next()和next()方法。
这里一般是同BookShelf一一对应,每实现一个BookShelf类就实现一个对应的BookShelfIterator类。确保对象的聚合和迭代操作的一致性。
book模块
实现了Book类,表示被聚合的对象。
具体实现
Aggregate抽象类
import abc
from abc import ABCMeta
class Aggregate(metaclass=ABCMeta):
""" 表示某个对象集合的接口类 """
@abc.abstractmethod
def iterator(self):
pass
Iterator抽象类
import abc
from abc import ABCMeta
class Iterator(metaclass=ABCMeta):
""" 表示遍历Aggregate的接口类 """
@abc.abstractmethod
def has_next(self) -> bool:
pass
@abc.abstractmethod
def next(self):
pass
Book类
class Book:
""" 表示书的类 """
def __init__(self, book_name):
self.name = book_name
def get_name(self):
return self.name
BookShelf类
from DesignMode.IteratorMode.aggregate import Aggregate
from DesignMode.IteratorMode.book import Book
from DesignMode.IteratorMode.bookshelfiterator import BookShelfIterator
class BookShelf(Aggregate):
""" 表示书架的类 """
def __init__(self):
self.__length = 0
self.__books: [Book] = list()
def get_book_at(self, index: int) -> Book:
return self.__books[index]
def append_book(self, book: Book) -> bool:
self.__books.append(book)
self.__length = len(self.__books)
return True
def get_length(self):
return self.__length
def iterator(self) -> BookShelfIterator:
return BookShelfIterator(self)
BookShelfIterator类
from DesignMode.IteratorMode.iterator import Iterator
class BookShelfIterator(Iterator):
""" 表示遍历书架的类 """
def __init__(self, book_shelf):
self.__book_shelf = book_shelf
self.__current_index = 0
def has_next(self) -> bool:
if self.__current_index < self.__book_shelf.get_length():
return True
return False
def next(self):
book = self.__book_shelf.get_book_at(self.__current_index)
self.__current_index += 1
return book
Client类
from DesignMode.IteratorMode.book import Book
from DesignMode.IteratorMode.bookshelf import BookShelf
class Client:
""" 表示测试设计模式的客户端类;可以理解为一个用户角色 """
@staticmethod
def run(*args, **kwargs):
book_shelf = BookShelf()
book_shelf.append_book(Book("三国演义"))
book_shelf.append_book(Book("红楼梦"))
book_shelf.append_book(Book("水浒传"))
book_shelf.append_book(Book("西游记"))
book_shelf_iterator = book_shelf.iterator()
while book_shelf_iterator.has_next():
book = book_shelf_iterator.next()
print(book.get_name())
if __name__ == '__main__':
Client.run()
运行结果
三国演义
红楼梦
水浒传
西游记
Process finished with exit code 0
重新出发
上面是迭代器模式的简单示例,看到这里有些小伙伴可能觉得别扭,上面的编程风格非常的不python,python里面提供了很多魔术方法,能够更加方便的实现迭代器模式。的确,我们来重新实现一版更python化的代码
Aggregate类
class Aggregate(metaclass=ABCMeta):
@abc.abstractmethod
def __getitem__(self, item):
pass
@abc.abstractmethod
def __len__(self):
pass
@abc.abstractmethod
def append(self, item):
pass
@abc.abstractmethod
def iterator(self):
pass
Iterator类
class Iterator(metaclass=ABCMeta):
@abc.abstractmethod
def __next__(self):
pass
@abc.abstractmethod
def __iter__(self):
pass
BookShelf类
class BookShelf(Aggregate):
def __init__(self):
self.__books = list()
def __getitem__(self, item):
return self.__books[item]
def __len__(self):
return len(self.__books)
def append(self, book):
self.__books.append(book)
def iterator(self):
return BookShelfIterator(self)
BookShelfIterator类
class BookShelfIterator(Iterator):
def __init__(self, book_shelf):
self.__book_shelf = book_shelf
self.__current_index = 0
def __iter__(self):
return self
def __next__(self):
if self.__current_index < len(self.__book_shelf):
book = self.__book_shelf[self.__current_index]
self.__current_index += 1
return book
return None
Book类
class Book:
def __init__(self, book_name):
self.__name: str = book_name
@property
def name(self):
return self.__name
def __str__(self):
return self.__name
class Client:
""" 表示测试设计模式的客户端类;可以理解为一个用户角色 """
@staticmethod
def run(*args, **kwargs):
book_shelf = BookShelf()
book_shelf.append(Book("三国演义"))
book_shelf.append(Book("红楼梦"))
book_shelf.append(Book("水浒传"))
book_shelf.append(Book("西游记"))
book_shelf_iterator = book_shelf.iterator()
book = next(book_shelf_iterator)
while book:
print(book)
book = next(book_shelf_iterator)
在第二种方式中我们使用了很多python的魔术方法,代码量变多了,看起来似乎还是第一种更好,不过如果是在实际项目中,不可能会像我们所举的例子这么简单,运用魔术方法不仅可以让我们的代码看起来简洁,而且不需要定义很多额外的方法,诸如get_book_at、get_length、get_name等,相比python常用的调用方式和调用方法,这些额外方法显得冗余而又格格不入。使用魔术方法能够保证调用方式的一致性。
第二种方式实现的BookShelf实际上更像是一个列表了,除了我们没有实现的方法之外,它的使用同列表基本没差,你也可以在BookShelf内部实现__iter__、__next__方法,将Iterator和BookShelf合二为一,不过这样一来,就等同于实现一个书架列表。
iterator是可以用来直接for循环的
class BookShelfIterator:
# ...
def __next__(self):
if self.__current_index < len(self.__book_shelf):
book = self.__book_shelf[self.__current_index]
self.__current_index += 1
return book
return StopIteration # 使用for循环一定要抛出这个异常,才能停止
for book in book_shelf_iterator:
print(book)
上面代码的输出结果是一样的
总结
其实说白了,迭代器模式就是包装一个类似容器的类,然后通过一个迭代器类来遍历它。就好比文章开头的for循环。既然一个for循环就可以搞定的逻辑,何必要大费周章的搞一个设计模式出来呢?
迭代器模式最大的优点是将 遍历和实现分开!这也就是为什么第二种方式中没有将BookShelf和BookShelfIterator合二为一的原因。
遍历和实现分开后,无论通过哪种循环遍历,都不依赖聚合类,只依赖Iterator的实现。
比较常见的场景是
1.我们可能会更改book的存储方式,比如通过队列、栈、字典等方式,此时只需要修改BookShelf类即可,其他代码可复用
2.相比常规的遍历方式,还可以从后往前遍历、支持双向遍历、支持跳跃式遍历
文章评论