设计模式 with Python 9:迭代器模式

我们在初学编程语言的时候,除了在诸如变量、控制流程、面向对象这些编程基础内容上会话费大量精力以外,编程语言的内建集合(Collection)或者说内建容器,也同样是重中之重。无论是常用的数组、链表、映射或者是不常用的队列、堆栈等,都需要我们话费大量时间去学习,因为你工作后会发现编写程序的工作中有很大一部分时间是在与这些集合组件打交道。

而我们今天要讨论的迭代器模式,就是为了对于不同内部实现的集合提供一种统一的迭代方式,从而对于客户端来说屏蔽不需要了解的内部实现,可以简单地进行迭代、遍历工作,这是非常棒的一个想法。

如《Head First 设计模式》中的示例那般,在这里我围绕餐馆菜单来进行举例说明。

注意:因为Python和Java在迭代器实现上有较大不同,所以这里使用Java进行举例说明,在文末会集中说明Python方式的实现应该是怎样的。

外卖服务

假如我们想创建一个外卖服务,为附近的几家餐馆提供统一的线上外卖服务,联系了几家餐馆后,它们给我们提供了用于接入的SDK,简单整合这些SDK后整个系统大概长这样:

虽然表示菜品的菜单项MenuItem已经统一为一个类型,但烧烤店的菜单BarbecueHouseMenu采用数组进行存储,而汉堡店的菜单BurgerJointMenu采用数组列表ArrayList进行存储。很自然,对于这两种不同的集合我们需要使用不同的方式进行遍历输出。

代码相对简单,这里只展示最后的外卖服务TakeOutService类与测试输出,完整代码见take_out_v1/src

package pattern9.take_out_v1.src;import java.util.ArrayList;/*** @author 70748* @version 1.0* @created 05-7��-2021 14:44:27*/
public class TakeOutService {private BarbecueHouseMenu barbecueHouseMenu;private BurgerJointMenu burgerJointMenu;public TakeOutService() {this.barbecueHouseMenu = new BarbecueHouseMenu();this.burgerJointMenu = new BurgerJointMenu();}public void finalize() throws Throwable {}public void printMenu() {System.out.println("This is a Barbecue House menu:");MenuItem[] menuItems = this.barbecueHouseMenu.getMenuItems();for (int i = 0; i < menuItems.length; i++) {if (menuItems[i] != null) {System.out.println(menuItems[i]);}}System.out.println("This is a Burger Joint menu:");ArrayList<MenuItem> items = this.burgerJointMenu.getMenuItems();for (int i=0;i<items.size();i++){MenuItem menuItem = items.get(i);System.out.println(menuItem);}}public static void main(String[] args) {TakeOutService takeOutService = new TakeOutService();takeOutService.printMenu();}
}// end TakeOutService// This is a Barbecue House menu:
// 菜品名称:烤羊肉串      价格:3.0       是否素菜:否
// 菜品名称:烤羊排        价格:10.0      是否素菜:否
// 菜品名称:烤韭菜        价格:2.0       是否素菜:是
// 菜品名称:烤茄子        价格:5.0       是否素菜:是
// 菜品名称:烤土豆        价格:1.0       是否素菜:是
// This is a Burger Joint menu:
// 菜品名称:田园鸡腿堡    价格:15.0      是否素菜:否
// 菜品名称:蔬菜沙拉      价格:10.0      是否素菜:是
// 菜品名称:巨无霸        价格:20.0      是否素菜:否
// 菜品名称:薯条  价格:5.0       是否素菜:是
// 菜品名称:可乐  价格:10.0      是否素菜:是

在这里,我们使用了两种不同的方式遍历菜单项,这是因为两个菜单返回的包含菜单项MenuItem的集合是不同的类型,所以需要使用不同的方式进行迭代。

正如本文一开始所说的,这正是迭代器模式的“用武之地”。

迭代器模式

如果你接触过Python,那肯定听说过迭代器,迭代器可以说深入了Python本身,但这里说的是一般性的迭代器模式,所以可能会和Python的迭代器实现有所差别。

我们看标准的迭代器UML:

这个设计本身很简单,Iterator是一个标识迭代器的接口,有两个方法:hasNext表示能否从当前的迭代器中获取到下一个元素,next是直接从迭代器中获取到下一个元素。Iterable接口表示一个可迭代的类型,具有一个方法iterator,这个方法会返回一个迭代器,即实现了Iterator接口的类型。而具体的迭代器ConcreteIterator和具体的可迭代类型会分别实现IteratorIterable接口。

这里的关键在于,通过实现IteratorIterable接口我们可以创建具有统一接口的迭代器和可迭代对象,通过这些多出来的抽象层次,我们可以让外部的客户端程序在不依赖具体集合的实现的情况下单独使用迭代器进行对集合中的元素的遍历。所以通常ConcreteIterator会包含一个集合的句柄用于实现hasNextnext的具体逻辑。

下面我们通过对外卖服务使用迭代器模式重构,来说明迭代器模式具体是如何实现的。

使用迭代器模式

先创建迭代器和可迭代对象接口:

package pattern9.take_out_v2.src;public interface Iterator {public boolean hasNext();public Object next();
}
package pattern9.take_out_v2.src;public interface Iterable {public Iterator iterator();
}

分别针对两种不同的集合创建对应的具体迭代器:

package pattern9.take_out_v2.src;public class ArrayIterator implements Iterator {private Object[] array;private int currentIndex = -1;public ArrayIterator(Object[] array) {this.array = array;}@Overridepublic boolean hasNext() {if (currentIndex >= array.length - 1 || array[currentIndex+1] == null) {return false;}return true;}@Overridepublic Object next() {Object next = array[currentIndex + 1];currentIndex++;return next;}}
package pattern9.take_out_v2.src;import java.util.ArrayList;public class ArrayListIterator implements Iterator {private ArrayList<Object> arrayList;private int nextIndex = 0;public ArrayListIterator(ArrayList arrayList) {this.arrayList = arrayList;}@Overridepublic boolean hasNext() {if (nextIndex >= arrayList.size()) {return false;}return true;}@Overridepublic Object next() {return arrayList.get(nextIndex++);}}

菜单类BarbecueHouseMenuBurgerJointMenu实现Iterable接口,改动的代码几乎一致,这里仅展示其中之一:

package pattern9.take_out_v2.src;import java.util.ArrayList;/*** @author 70748* @version 1.0* @created 05-7��-2021 14:44:27*/
public class BurgerJointMenu implements Iterable {private ArrayList<MenuItem> menuItems = new ArrayList<MenuItem>();public BurgerJointMenu() {this.addMenuItem(new MenuItem("田园鸡腿堡", 15, false));this.addMenuItem(new MenuItem("蔬菜沙拉", 10, true));this.addMenuItem(new MenuItem("巨无霸", 20, false));this.addMenuItem(new MenuItem("薯条", 5, true));this.addMenuItem(new MenuItem("可乐", 10, true));}public void finalize() throws Throwable {}private void addMenuItem(MenuItem menuItem) {this.menuItems.add(menuItem);}@Overridepublic Iterator iterator() {return new ArrayListIterator(this.menuItems);}
}// end BurgerJointMenu

修改测试代码:

package pattern9.take_out_v2.src;/*** @author 70748* @version 1.0* @created 05-7��-2021 14:44:27*/
public class TakeOutService {private BarbecueHouseMenu barbecueHouseMenu;private BurgerJointMenu burgerJointMenu;public TakeOutService() {this.barbecueHouseMenu = new BarbecueHouseMenu();this.burgerJointMenu = new BurgerJointMenu();}public void finalize() throws Throwable {}public void printMenu() {System.out.println("This is a Barbecue House menu:");this.printMenu(this.barbecueHouseMenu);System.out.println("This is a Burger Joint menu:");this.printMenu(this.burgerJointMenu);}private void printMenu(Iterable iterable) {Iterator iterator = iterable.iterator();while (iterator.hasNext()) {MenuItem menuItem = (MenuItem) iterator.next();System.out.println(menuItem);}}public static void main(String[] args) {TakeOutService takeOutService = new TakeOutService();takeOutService.printMenu();}
}// end TakeOutService// This is a Barbecue House menu:
// 菜品名称:烤羊肉串 价格:3.0 是否素菜:否
// 菜品名称:烤羊排 价格:10.0 是否素菜:否
// 菜品名称:烤韭菜 价格:2.0 是否素菜:是
// 菜品名称:烤茄子 价格:5.0 是否素菜:是
// 菜品名称:烤土豆 价格:1.0 是否素菜:是
// This is a Burger Joint menu:
// 菜品名称:田园鸡腿堡 价格:15.0 是否素菜:否
// 菜品名称:蔬菜沙拉 价格:10.0 是否素菜:是
// 菜品名称:巨无霸 价格:20.0 是否素菜:否
// 菜品名称:薯条 价格:5.0 是否素菜:是
// 菜品名称:可乐 价格:10.0 是否素菜:是

这里的要点在于,我们只关心是否可以获取到两个菜单相关的迭代器,并不关心这两个迭代器如何实现。所以在实现中其实迭代器也可以通过持有两种菜单的引用,而非直接持有数组或者数组列表的引用,同样可以达到目的,但从通用性的角度讲,显然创建数组和数组列表的迭代器比创建两个菜单的迭代器更具有通用性,如果你的其它地方需要数组迭代器,直接拿过去用就是了。

上面我们展示了如何从头到尾在外卖服务中使用一套自己创建的迭代器模式,但其实Java本身是内建了迭代器的相关接口和组件的,我们并不需要从头到尾自己创建,只需要直接使用即可。

Java内建的迭代器接口

Java的内建可迭代接口是java.lang.Iterable,迭代器接口为java.util.Iterator。只要用这两个内建接口替换我们自己创建的相应接口即可。

比如这样:

package pattern9.take_out_v3.src;import java.util.Iterator;public class ArrayIterator<T> implements Iterator<T> {private T[] array;private int currentIndex = -1;

这里使用泛型改写ArrayIterator,以避免可能的warning。关于Java的泛型,可以阅读Java 泛型

至于ArrayListIterator,可以完全删除,因为Java所有的内置集合都已经实现了Iterable接口,我们直接调用iterator获取迭代器即可。

比如这样:

 @Overridepublic Iterator<MenuItem> iterator() {return this.menuItems.iterator();}

剩余改动的代码这里不一一展示,可以在take_out_v3/src获取完整代码。

单一责任原则

迭代器模式体现了一个设计模式原则:单一责任原则。

这个原则指的是,对于我们创建的一个具体的类,应当让其承担相对单一的责任或任务

比如集合相关的类,其核心任务就是管理内部的元素,比如添加、删除元素。而迭代任务并非其核心任务,好的做法是从其中分离出来,让迭代器去“代劳”,而集合本身只需要关心元素管理即可。

这就是单一责任原则。看起来似乎很容易,但是如果你接触编程久了就会知道,对类的抽象层次,或者说类定义的粒度的把握相当困难,我们都清楚越是将抽象层次细分,越是将类的粒度缩小,越可以提高设计的灵活性,但与此同时,会产生庞大的类数目,会让管理和维护以及工作量随之上升,所以这之间要有所取舍,仔细衡量以制定一个合适的程度。

当然,这种把握属于经验的一部分,并没有捷径或者准则可以提供,唯一获取的方式是持之以恒。

组合模式

我们现在的外卖服务已经运行的很完美了,如果没有新需求的话。

假设我们有一个新的需求:汉堡店想提供一个“活动菜单”,比如和某某知名游戏联名,当然我们并不需要用户说出某种羞耻的台词才能获取到这个菜单

设计模式 with Python 9:迭代器模式相关推荐

  1. 《Head first设计模式》学习笔记 – 迭代器模式

    迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示. 爆炸性新闻:对象村餐厅和对象村煎饼屋合并了! 真是个好消息!现在我们可以在同一个地方,享用煎饼屋美味的煎饼早餐,和好吃 ...

  2. C#设计模式之十五迭代器模式(Iterator Pattern)【行为型】

    一.引言 今天我们开始讲"行为型"设计模式的第三个模式,该模式是[迭代器模式],英文名称是:Iterator Pattern.还是老套路,先从名字上来看看."迭代器模式& ...

  3. 大战设计模式【12】—— 迭代器模式

    迭代器模式(Iterator) 设计模式使用的例子https://github.com/LinkinStars/DesignPatternsAllExample 一.定义 提供一种方法顺序访问一个聚合 ...

  4. 设计模式笔记十七:迭代器模式

    原文:http://www.runoob.com/design-pattern/ 少许个人理解,如有错误请指出.欢迎一起讨论. 迭代器模式(Iterator Pattern) 是 Java 和 .Ne ...

  5. 设计模式之禅【迭代器模式】

    真刀实枪之迭代器模式 从整理项目说起 老板要看项目,但是有点乱,让我整理下,简单,说干就干 类图先上 代码跟上 IProject package com.peng.ddq;/*** @author k ...

  6. 结合项目实例 回顾传统设计模式(九)迭代器模式

    关于迭代器模式,我们所需要知道的第一件事情就是它依赖于一个名为迭代器的接口.一旦我们有了这个接口,就可以为各种那个对象集合实现迭代器:数组.列表.散列表. 项目实例我们就拿用户角色模块来举例 背景 1 ...

  7. 设计模式的理解:迭代器模式(Iterator)

    迭代器模式,又称游标模式,提供一种方法顺序访问一个集合对象中的各个元素,而又不需要暴露该对象的内部表示. template<typename T> class Iterator{publi ...

  8. PHP设计模式(6)迭代器模式

    迭代器(Iterator)模式,在一个很常见的过程上提供了一个抽象:位于对象图不明部分的一组对象(或标量)集合上的迭代. 迭代有几种不同的具体执行方法:在数组属性,集合对象,数组,甚至一个查询结果集之 ...

  9. 《Head First 设计模式》读书笔记——迭代器模式

    让客户遍历我们的数组.堆栈.列表或者散列表时,无法知道我们存储对象的方式,就靠今天的迭代器模式了. 案例 我们有两个餐饮店要合并经营,他们的菜品有共同的地方,所以我们这两个商店类需要一个共同的菜单属性 ...

  10. 软件设计模式及体系结构之迭代器模式

    前言 1.电视机<→存储电视频道的集合<→聚合类 2.电视机遥控器<→操作电视频道<>迭代器 (Iterator) 3.访问一个聚合对象中的元素但又不需要暴露它的内部 分 ...

最新文章

  1. 简谈造成循环引用的原因以及处理办法
  2. PHPCMS商城:模块_购物车+订单模块(资源合集)
  3. 用 Visual Studio 自动生成C/C++注释(Doxygen、XML)
  4. java author 认证_详解Java后端优雅验证参数合法性
  5. 12 File and Device I/O using System Calls
  6. c语言控制台不退出程序,怎样可以屏蔽控制台程序的关闭按钮
  7. 网络安全DOS攻击✍
  8. win10文件夹加密_如何使用电脑(win10)局域网共享文件给nPlayer(SMB)
  9. UART协议快速扫盲(图文并茂+超详细)
  10. 用户态创建socket来控制arp报文的收发,含编码
  11. 计算机毕业设计springboot+vue+elementUI高校志愿者管理系统(源码+系统+mysql数据库+Lw文档)
  12. 让你的专属博客更加漂亮
  13. cmd.exe显示窗口大小和字体大小调整
  14. 高等数学--微分定理及其应用(四)
  15. 文末福利|使用Python转换PDF,Word/Excel/PPT/md/HTML都能转!
  16. 中国制造构建全球产业链,是关于价值链的创新
  17. android10手机运行内存怎么查看,安卓手机怎么查看手机内存
  18. Python对图像进行白色区域转化为黑色
  19. Sping-AOP切面相关操作
  20. FastDFS分布式文件服务器部署与运用

热门文章

  1. 使用js对表格数据排序
  2. 用Photoshop处理和生成pdf文件
  3. linux 下URL中 UTF-8编码、GB2312编码与汉字之间的转换
  4. python打擂台法_御灵手游怎么打擂台 擂台战打法技巧
  5. 135那些我们在「疫」的事” 主题征文大赛圆满落幕!
  6. matlab 绘制星形球面,一种星形套球面相对内孔的跳动检具
  7. 使用Git搭建个人博客
  8. Matlab实现图像识别(二)
  9. 优信二手车:赛道虽好,生意难做
  10. jquery ajax传递数组