行为型软件设计模式
行为型模式(Behavioral Pattern)是对在不同的对象之间划分责任和算法的抽象化。
行为型模式不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用。
通过行为型模式,可以更加清晰地划分类与对象的职责,并研究系统在运行时实例对象 之间的交互。在系统运行时,对象并不是孤立的,它们可以通过相互通信与协作完成某些复杂功能,一个对象在运行时也将影响到其他对象的运行。
行为型模式分为类行为型模式和对象行为型模式两种:
- 类行为型模式:类的行为型模式使用继承关系在几个类之间分配行为,类行为型模式主要通过多态等方式来分配父类与子类的职责。
- 对象行为型模式:对象的行为型模式则使用对象的聚合关联关系来分配行为,对象行为型模式主要是通过对象关联等方式来分配两个或多个类的职责。根据“合成复用原则”,系统中要尽量使用关联关系来取代继承关系,因此大部分行为型设计模式都属于对象行为型设计模式。
迭代器模式
迭代器模式是一种行为设计模式, 让你能在不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素。
模式动机
集合是编程中最常使用的数据类型之一,通常可以被认为是一组对象的容器。
大部分集合使用简单列表存储元素。 但有些集合还会使用栈、 树、 图和其他复杂的数据结构,如下图所示。
集合这种数据类型虽然是一种用于存储的数据结构,但同时需要提供访问其存储数据的方法,特别是一种可以遍历其存储所有数据的方法。
也就是说,无论集合的构成方式如何, 它都必须提供某种访问元素的方式, 便于其他代码使用其中的元素。 集合应提供一种能够遍历元素的方式, 且保证它不会周而复始地访问同一个元素。
对于基于数组或者列表的集合而言, 访问或者遍历其元素可以直接通过下标完成,这种对象遍历的方法是简单且唯一的。 但对于树和图这种复杂数据结构,应该如何遍历其中的元素呢? 而且复杂数据结构的遍历方法是多样的。比如对于树形数据结构,就可以适用深度优先算法、广度优先或者随机存取等算法来遍历树结构。
一种简单的做法是,将这些遍历算法作为这些集合数据结构类的公共成员函数。但不断向集合类中添加遍历算法,首先会导致单个类的功能过于臃肿,集合数据结构类承担太多的功能,一方面提供添加和删除等功能,还要提供遍历访问功能。其次,集合数据结构类的首要功能是 “高效存储数据”,在遍历过程中,需要保存遍历状态,其和元素的添加和删除混杂在一起,容易引起混乱;添加过多的遍历算法会导致类的职责模糊问题,违反单一职责的设计原则。最后,不论是列表,还是树或者图,一般都会继承自共有的父类——泛型类,有些算法可能是根据特定应用订制的, 将其加入泛型集合类中会显得非常奇怪。
另一方面,从客户端来看,使用多种集合的客户端代码可能并不关心存储数据的方式(如同SQL数据库的使用者并不会关心底层数据库是MySql、SQL Server,还是Oracle一样),其更关心的是用这些数据结构存储数据后,如何访问(遍历)其中的所有数据。 不过由于不同的集合数据结构类提供不同的元素访问方式, 客户端代码将不得不与特定集合类进行耦合。
在这样的场景下,动态变化的是遍历数据对象的方法。一个自然而然的想法是,将遍历数据功能抽象形成单独的类,其专门负责遍历集合数据结构类中数据——这就是迭代器设计模式。
总结
- 一个聚合对象,如一个列表(List)或者一个集合(Set),应该提供一种方法来让别人可以访问它的元素,而又不需要暴露它的内部结构。
- 针对不同的需要,可能还要以不同的方式遍历整个聚合对象,但是我们并不希望在聚合对象的抽象层接口中充斥着各种不同遍历的操作。
- 在迭代器模式中,提供一个外部的迭代器来对聚合对象进行访问和遍历,迭代器定义了一个访问该聚合元素的接口,并且可以跟踪当前遍历的元素,了解哪些元素已经遍历过而哪些没有。
模式定义
迭代器模式(Iterator Pattern) :提供一种方法来访问聚合对象,而不用暴露这个对象的内部表示,其别名为游标(Cursor)。
迭代器模式是一种对象行为型模式。
迭代器模式就是顺序访问聚集中的对象,这是集合中非常常见的一种操作。其包含两层意思:
- 需要遍历的对象,即聚集对象,
- 迭代器对象,用于对聚集对象进行遍历访问。
模式结构
模式时序图
模式抽象代码分析
- 迭代器接口
- 具体迭代器
- 集合接口声明
- 具体集合类
模式分析
- 聚合是一个管理和组织数据对象的数据结构。聚合对象主要拥有两个职责:一是存储内部数据;二是遍历内部数据。
- 将遍历聚合对象中数据的行为提取出来,封装到一个迭代器中,通过专门的迭代器来遍历聚合对象的内部数据,这就是迭代器模式的本质。迭代器模式是“单一职责原则”的完美体现。
- 在迭代器模式中应用了工厂方法模式,聚合类充当工厂类,而迭代器充当产品类,由于定义了抽象层,系统的扩展性很好,在客户端可以针对抽象聚合类和抽象迭代器进行编程。
- java编程语言的类库都已经实现了迭代器模式,因此在实际使用中很少自定义迭代器,只需要直接使用Java语言中已定义好的迭代器即可。
模式实例
在本例中, 迭代器模式用于遍历一个封装了访问微信好友关系功能的特殊集合。 该集合提供使用不同方式遍历档案资料的多个迭代器。
“好友 (friends)” 迭代器可用于遍历指定档案的好友。 “同事 (colleagues)” 迭代器也提供同样的功能, 但仅包括与目标用户在同一家公司工作的好友。 这两个迭代器都实现了同一个通用接口, 客户端能在不了解认证和发送 REST 请求等实现细节的情况下获取档案。
客户端仅通过接口与集合和迭代器交互, 也就不会同具体类耦合。 如果你决定将应用连接到全新的社交网络, 只需提供新的集合和迭代器类即可, 无需修改现有代码。
SocialNetwork.java
Webchat.java
LinkedIn.java
ProfileIterator.java
WebchatIterator.java
LinkedInIterator.java
Profile.java
SocialSpammer.java
Client.java
模式优点
- 单一职责原则。 通过将体积庞大的遍历算法代码抽取为独立的类, 可对客户端代码和集合进行整理。
- 开闭原则。 可实现新型的集合和迭代器并将其传递给现有代码, 无需修改现有代码。
- 可以并行遍历同一集合, 因为每个迭代器对象都包含其自身的遍历状态。
- 相似的, 可以暂停遍历并在需要时继续。
模式缺点
- 如果你的程序只与简单的集合进行交互, 应用该模式可能会矫枉过正。
- 对于某些特殊集合, 使用迭代器可能比直接遍历的效率低。
模式适用环境
- 访问一个聚合对象的内容而无须暴露它的内部表示。
- 需要为聚合对象提供多种遍历方式。
- 为遍历不同的聚合结构提供一个统一的接口。
模式应用
该模式在 Java 代码中很常见。 许多框架和程序库都会使用它来提供遍历其集合的标准方式。
\1. 下面是该模式在核心 Java 程序库中的一些示例:
java.util.Iterator的所有实现 (还有 java.util.Scanner)。
\2. JDK1.2 引入了新的Java聚合框架Collections
- Collection是所有Java聚合类的根接口。
- 在JDK类库中,Collection的iterator()方法返回一个java.util.Iterator类型的对象,而其子接口java.util.List的listIterator()方法返回一个java.util.ListIterator类型的对象,ListIterator是Iterator的子类。它们构成了Java语言对迭代器模式的支持,Java语言的java.util.Iterator接口就是迭代器模式的应用。
识别方法: 迭代器可以通过导航方法 (例如 next和 previous等) 来轻松识别。 使用迭代器的客户端代码可能没有其所遍历的集合的直接访问权限。
模式扩展
Java迭代器,•在JDK中,Iterator接口具有如下3个基本方法:
- Object next():通过反复调用next()方法可以逐个访问聚合中的元素。
- boolean hasNext():hasNext()方法用于判断聚合对象中是否还存在下一个元素,为了不抛出异常,必须在调用next()之前先调用hasNext()。如果迭代对象仍然拥有可供访问的元素,那么hasNext()返回true。
- void remove():用于删除上次调用next()时所返回的元素
总结
- 声明迭代器接口。 该接口必须提供至少一个方法来获取集合中的下个元素。 但为了使用方便, 你还可以添加一些其他方法, 例如获取前一个元素、 记录当前位置和判断迭代是否已结束。
- 声明集合接口并描述一个获取迭代器的方法。 其返回值必须是迭代器接口。 如果你计划拥有多组不同的迭代器, 则可以声明多个类似的方法。
- 为希望使用迭代器进行遍历的集合实现具体迭代器类。 迭代器对象必须与单个集合实体链接。 链接关系通常通过迭代器的构造函数建立。
- 在你的集合类中实现集合接口。 其主要思想是针对特定集合为客户端代码提供创建迭代器的快捷方式。 集合对象必须将自身传递给迭代器的构造函数来创建两者之间的链接。
- 检查客户端代码, 使用迭代器替代所有集合遍历代码。 每当客户端需要遍历集合元素时都会获取一个新的迭代器。