文档章节

HeadFirst设计模式(十) - 迭代器模式

XuePeng77
 XuePeng77
发布于 2016/08/07 19:22
字数 2582
阅读 41
收藏 0
点赞 0
评论 0

良好的管理集合

有许多种方法可以把对象堆起来成为一个集合。你可以把它们放进数据、堆栈、列表或者是散列表中,每一种都有它自己的优点和适合的使用时机。

举个例子

加入现在有两个餐厅,披萨餐厅和煎饼餐厅,它们合并了,他们的菜单就需要合并在一起。

披萨餐厅是用ArrayList对象去存放餐单信息,而煎饼餐厅使用数组。他们两家餐厅都同意让菜单对象实现MenuItem。让我们看看具体的实现:

package cn.net.bysoft.iterator;

// 菜单对象,保存了菜单的信息
public class MenuItem {

	public MenuItem(String name, String description, boolean vegetarian, double price) {
		super();
		this.name = name;
		this.description = description;
		this.vegetarian = vegetarian;
		this.price = price;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public boolean isVegetarian() {
		return vegetarian;
	}

	public void setVegetarian(boolean vegetarian) {
		this.vegetarian = vegetarian;
	}

	public double getPrice() {
		return price;
	}

	public void setPrice(double price) {
		this.price = price;
	}

	private String name;
	private String description;
	private boolean vegetarian;
	private double price;
}
import java.util.ArrayList;

// 煎饼餐厅对象,用ArrayList保存了菜单。
public class PancakeHouseMenu {
	ArrayList<MenuItem> menuItems;
	
	public PancakeHouseMenu() {
		menuItems = new ArrayList<MenuItem>();
		addItem("煎饼1号", "牛肉煎饼", false, 2.99);
		addItem("煎饼2号", "素食煎饼", true, 1.49);
	}
	
	public void addItem(String name, String description, boolean vegetarian, double price) {
		MenuItem menu = new MenuItem(name, description, vegetarian, price);
		menuItems.add(menu);
	}

	public ArrayList<MenuItem> getMenuItems() {
		return menuItems;
	}
}
// 披萨餐厅对象,用数据保存了菜单信息。
public class PizzaHouseMenu {
	static final int MAX_ITEMS = 2;
	int numberOfItems = 0;
	MenuItem[] menuItems;

	public PizzaHouseMenu() {
		menuItems = new MenuItem[MAX_ITEMS];
	}

	public void addItem(String name, String description, boolean vegetarian, double price) {
		MenuItem menu = new MenuItem(name, description, vegetarian, price);
		if (numberOfItems >= MAX_ITEMS)
			System.out.println("对不起,菜单数量已满");
		else
			menuItems[numberOfItems++] = menu;
	}

	public MenuItem[] getMenuItems() {
		return menuItems;
	}
}

    想了解为什么有两种不同的菜单表现方式会让事情变得复杂化,让我们试着实现一个同事使用者两个菜单的客户代码。创建一个服务员对象。

  • printMenu(); 打印出菜单上的每一项
  • printBreakfastMenu(); 只打印早餐
  • printLunchMenu(); 只打印午餐
  • printVegetarianMenu(); 打印所有的素食菜单
  • isItemVegetarian(name); 查询指定的菜品是否是素食

    先从实现printMenu()方法开始:

import java.util.ArrayList;

public class Client {
	public static void main(String[] args) {
		// 先获得煎饼餐厅的菜单集合
		PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
		ArrayList<MenuItem> menusOfPancake = pancakeHouseMenu.getMenuItems();

		// 在获得披萨餐厅的菜单数组
		PizzaHouseMenu pizzaHouseMenu = new PizzaHouseMenu();
		MenuItem[] menusOfPizza = pizzaHouseMenu.getMenuItems();

		for (int i = 0; i < menusOfPancake.size(); i++) {
			MenuItem menu = menusOfPancake.get(i);
			System.out.print(menu.getName() + ",价格:");
			System.out.print(menu.getPrice() + ",");
			System.out.print(menu.getDescription() + "\n");
		}

		System.out.println();

		for (int i = 0; i < menusOfPizza.length; i++) {
			MenuItem menu = menusOfPizza[i];
			System.out.print(menu.getName() + ",价格:");
			System.out.print(menu.getPrice() + ",");
			System.out.print(menu.getDescription() + "\n");
		}
	}
}

    打印每份菜单上的所有项,必须调用getMenuItem()方法,来取得他们各自的菜单。上面的例子中两者的返回类型是不同的。

    接着,想打印出菜单需要将集合和数组循环并一一列出菜单。

    最后,我们总是需要处理这两个菜单的遍历,如果还有第三家餐厅以不同的方式实现菜单集合,我们就需要有第三个循环。

可以封装遍历吗?

    可以封装变化的部分。很明显,这里发生的变化是:由不同的集合类型所造成的遍历。但是,这能够被封装吗?让我们来看看这个想法……

  1. 要便利煎饼餐厅,我们需要使用ArrayList的size()和get()方法;
  2. 要便利披萨餐厅,我们需要使用数组的length字段和在中括号中输入索引;
  3. 现在我们创建一个对象,将它称为迭代器(Iterator),利用它来封装“遍历集合内的每个对象的过程”;

     想要在餐厅菜单中加入一个迭代器,我们需要先定义迭代器接口,然后为披萨餐厅创建一个迭代器类:

public interface Iterator {
	boolean hasNext();
	Object next();
}
public class PizzaIterator implements Iterator {

	MenuItem[] items;
	int position = 0;
	
	public PizzaIterator(MenuItem[] items) {
		this.items = items;
	}
	
	// 判断数组下一个索引是否还有元素
	public boolean hasNext() {
		if(position >= items.length || items[position] == null)
			return false;
		else return true;
	}
	
	// 获得当前索引位置的元素
	public Object next() {
		MenuItem item = items[position++];
		return item;
	}

}

import java.util.ArrayList;

public class PancakeIterator implements Iterator {

	ArrayList<MenuItem> items;
	int position = 0;
	
	public PancakeIterator(ArrayList<MenuItem> items) {
		this.items = items;
	}
	
	// 判断数组下一个索引是否还有元素
	public boolean hasNext() {
		if(position >= items.size() || items.get(position) == null)
			return false;
		else return true;
	}
	
	// 获得当前索引位置的元素
	public Object next() {
		MenuItem item = items.get(position);
		return item;
	}

}

    创建好迭代器后,改写披萨餐厅的代码,创建一个PizzaMenuIterator,并返回给客户:

public class PizzaHouseMenu {
	static final int MAX_ITEMS = 2;
	int numberOfItems = 0;
	MenuItem[] menuItems;

	public PizzaHouseMenu() {
		menuItems = new MenuItem[MAX_ITEMS];
		addItem("披萨1号", "素食披萨", true, 4.99);
		addItem("披萨2号", "海鲜蛤蜊披萨", true, 5.99);
	}

	public void addItem(String name, String description, boolean vegetarian, double price) {
		MenuItem menu = new MenuItem(name, description, vegetarian, price);
		if (numberOfItems >= MAX_ITEMS)
			System.out.println("对不起,菜单数量已满");
		else
			menuItems[numberOfItems++] = menu;
	}
	
	public Iterator createIterator() {
		return new PizzaIterator(menuItems);
	}
}

import java.util.ArrayList;

public class PancakeHouseMenu {
	ArrayList<MenuItem> menuItems;
	
	public PancakeHouseMenu() {
		menuItems = new ArrayList<MenuItem>();
		addItem("煎饼1号", "牛肉煎饼", false, 2.99);
		addItem("煎饼2号", "素食煎饼", true, 1.49);
	}
	
	public void addItem(String name, String description, boolean vegetarian, double price) {
		MenuItem menu = new MenuItem(name, description, vegetarian, price);
		menuItems.add(menu);
	}

	public Iterator createIterator() {
		return new PancakeIterator(menuItems);
	}
}

    我们不再需要getMenuItems()方法,而是用createIterator()方法代替,用来从菜单项数组创建一个迭代器,并把他返回给客户,返回迭代器接口。客户不需要知道餐厅菜单使如何实现维护的,也不需要知道迭代器是如何实现的。客户只需直接使用这个迭代器遍历菜单即可。下面修改一下客户类的调用:

public class Waitress {
	PancakeHouseMenu pancake;
	PizzaHouseMenu pizza;
	
	public Waitress(PancakeHouseMenu pancake, PizzaHouseMenu pizza) {
		this.pancake = pancake;
		this.pizza = pizza;
	}
	
	public void printMenu() {
		Iterator pizzaIterator = pizza.createIterator();
		printMenu(pizzaIterator);
		
		Iterator pancakeIterator = pancake.createIterator();
		printMenu(pancakeIterator);
	}
	
	private void printMenu(Iterator iterator) {
		while(iterator.hasNext()) {
			MenuItem menu = (MenuItem)iterator.next();
			System.out.print(menu.getName() + ",价格:");
			System.out.print(menu.getPrice() + ",");
			System.out.print(menu.getDescription() + "\n");
		}
	}
}
public class Client {
	public static void main(String[] args) {
		PancakeHouseMenu pancake = new PancakeHouseMenu();
		PizzaHouseMenu pizza = new PizzaHouseMenu();
		
		Waitress waitress = new Waitress(pancake, pizza);
		waitress.printMenu();
	}
}

    到目前为止,我们将客户调用与餐厅的菜单数据接口解耦了,客户调用再也不用为每一个不同数据结构的菜单编写一套遍历的代码了。

我们有些什么……

    我们现在使用一个共同的迭代器接口(Iteraotr)实现了两个具体类(PizzaIterator和PancakeIterator)。这两个具体类都实现了各自的hasNext()方法和next()方法。

    然后再PancakeHouseMenu和PizzaHouseMenu两个类中,创建一个createIterator()方法返回各自的迭代器,在Waitress类中,使用这两个餐厅对象返回的迭代器打印菜单。这时Waitress类和Client类再也不需要关心存放菜单的数据结构,之关心能从迭代器中获得菜单就好。

    迭代器模式给你提供了一种方法,可以顺序访问一个聚集对象的元素,而又不用知道内部是如何表示的。你已经在前面的两个菜单实现中看到了这一点。在设计中使用迭代器的影响是明显的:如果你有一个统一的方法访问聚合中的每一个对象,你就可以编写多态的代码和这些聚合搭配,使用如同前面的printMenu()方法一样,只要有了迭代器这个方法根本不用管菜单究竟是由数组还是集合或者其他的数据结构来保存的。

    另外一个对你的设计造成重要影响的,是迭代器模式把在元素之间游走的责任交给迭代器,而不是聚合对象。这不仅让聚合的接口和实现变得更简洁,也可以让聚合更专注它所应该专注的事情上面,而不必去理会遍历的事情。

    让我们检查类图,将来龙去脉拼凑出来……

    先看看Aggregate接口,有一个共同的接口提供所有的聚合使用,这对客户代码是很方便的,将客户代码从集合对象的实现解耦。

    接下来看看ConcreteAggregate类,这个具体聚合持有一个对象的集合,并实现一个方法,利用此方法返回集合的迭代器。每一个具体聚合都要负责实例化一个具体的迭代器,次迭代器能够便利对象集合。

    接下来是Iterator接口,这是所有迭代器都必须实现的接口,它包含一些方法,利用这些方法可以在集合元素之间游走。你可以自己设计或者使用java.util.Iterator接口。

    最后是具体的迭代器,负责遍历集合。

单一职责

    如果我们允许我们的聚合实现他们内部的集合,以及相关的操作和遍历的方法,又会如何?我们已经知道这回增加聚合中的方法个数,但又怎么样呢?为什么这么做不好?

    想知道为什么,首选需要认清楚,当我们允许一个类不但要完成自己的事情,还同时要负担更多的责任时,我们就给这个类两个变化的原因。如果这个集合变化的话,这个类也必须要改变,如果我们遍历的方式改变的话,这个类也必须跟着改变。所以,引出了设计原则的中心:

单一职责

一个类应该只有一个引起变化的原因。

    类的每个责任都有改变的潜在区域。超过一个责任,意味着超过一个改变区域。这个原则告诉我们,尽量让每一个类保持单一责任。

    内聚(cohesion)这个术语你应该听过,它用来度量一个类或者模块紧密地达到单一目的或责任。

    当一个模块或一个类被设计成只支持一组相关的功能时,我们说它具有高内聚;反之,当被设计成支持一组不相关的功能时,我们说它具有低内聚。

    内聚是一个比单一职责更普遍的概念,但两者其实关系是很密切的。遵守这个原则的类更容易有很高的凝聚力,而且比背负许多职责的低内聚类更容易维护。

    以上就是迭代器模式的一些内容。

© 著作权归作者所有

共有 人打赏支持
XuePeng77
粉丝 39
博文 132
码字总数 175794
作品 0
丰台
JavaScript 中常见设计模式整理

开发中,我们或多或少地接触了设计模式,但是很多时候不知道自己使用了哪种设计模式或者说该使用何种设计模式。本文意在梳理常见设计模式的特点,从而对它们有比较清晰的认知。 JavaScript 中...

牧云云
05/18
0
0
【设计模式笔记】(十六)- 代理模式

一、简述 代理模式(Proxy Pattern),为其他对象提供一个代理,并由代理对象控制原有对象的引用;也称为委托模式。 其实代理模式无论是在日常开发还是设计模式中,基本随处可见,中介者模式中...

MrTrying
06/24
0
0
java设计模式-- 单例模式

在很久之前,也就是在大二暑假的时候,那时候看马士兵的视频教程中有提到很多的设计模式。 java的设计模式大致可以分为3大类,23种设计模式。 其中,创建型模式有5种:单例模式、建造者模式、...

爱学习的逃课君
2014/11/27
0
0
设计模式 2014-12-19

book: 阎宏《JAVA与模式》 架构设计栏目 http://blog.csdn.net/enterprise/column.html 概要: http://bbs.csdn.net/forums/Embeddeddriver 23种设计模式分别是: 1.单例模式 2.工厂方法模式...

jayronwang
2014/12/19
0
0
(目录)设计模式(可复用面向对象软件的基础)

本系列“设计模式”博客使用Golang语言实现算法。所谓算法是指解决一个问题的步骤,个人觉得不在于语言。小弟只是最近学习Golang,所以顺带熟练一下语法知识,别无它意。 本系列博客主要介绍...

chapin
2015/01/13
0
0
[设计模式]简单工厂模式

简介 简单工厂模式 (Simple Factory) 又叫静态工厂方法(Static Factory Method)模式。 简单工厂模式通常是定义一个工厂类,这个类可以根据不同变量返回不同类的产品实例。 简单工厂模式是一...

静默虚空
2015/06/03
0
0
你需要了解的23种JavaScript设计模式

为什么要学习设计模式? 在许多访谈中,你可能会遇到很多面向对象编程中的接口,抽象类,代理和以及其他与设计模式相关的问题。 一旦了解了设计模式,它会让你轻松应对任何访谈,并可以在你的...

java高级架构牛人
06/02
0
0
设计模式笔录(二),设计模式有哪些

本人出道5年,学习、编程、再学习、再编程一路走过,只是在笔和纸留下些脚印,实感惭愧。现开始把自己学习到的心得,实践中的体会,一一贴在互联网上,大家互相学习、探讨,寻找一些技术朋友...

方旭
2011/03/31
0
0
设计模式——Java中常用的9种设计模式

Factory Pattern(工厂模式)   将程序中创建对象的操作,单独出来处理,大大提高了系统扩展的柔性,接口的抽象化处理给相互依赖的对象创建提供了最好的抽象模式。 2. Facade Pattern (门面...

utopia1985
2013/06/19
0
0
Java 设计模式(14) —— 复合模式

一、复合模式 模式常一起使用,组合在一个设计解决方案中 复合模式在一个解决方案中结合两个或多个模式,能解决一般性或一系列的问题 二、示例 本次设计模式讲解中无代码示例,由于复合模式是...

磊_lei
05/26
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Weblogic问题解决记录

问题:点击登录,页面刷新但是不进去管理界面。解决:删除cookies再登录。

wffger
26分钟前
0
0
RxJava2的错误处理方案

最近使用retrofit2 + rxKotlin2写接口访问,想尽量平铺代码,于是就想到当借口返回的状态码为「不成功」时(比如:code != 200),就连同网络错误一起,统一在onError方法中处理。想法总是好的...

猴亮屏
34分钟前
0
0
程序的调试信息

调试二进制程序时,经常要借助GDB工具,跟踪程序的执行流程,获取程序执行时变量的值,以发现问题所在。GDB能得到这些信息,是因为编译程序时,编译器保存了相应的信息。Linux下的可执行程序...

qlee
56分钟前
0
0
应用级缓存

缓存命中率 从缓存中读取数据的次数与总读取次数的比例,命中率越高越好 java缓存类型 堆缓存 guavaCache Ehcache3.x 没有序列化和反序列化 堆外缓存ehcache3.x 磁盘缓存 存储在磁盘上 分布式...

writeademo
今天
0
0
python爬虫日志(3)find(),find_all()函数

1.一般来说,为了找到BeautifulSoup对象内任何第一个标签入口,使用find()方法。 以上代码是一个生态金字塔的简单展示,为了找到第一生产者,第一消费者或第二消费者,可以使用Beautiful Sou...

茫羽行
今天
0
0
java:thread:顺序执行多条线程

实现方案: 1.调用线程的join方法:阻塞主线程 2.线程池 package com.java.thread.test;public class MyThread01 implements Runnable {@Overridepublic void run() {Syste...

人觉非常君
今天
0
0
ElasticSearch 重写IK分词器源码设置mysql热词更新词库

常用热词词库的配置方式 1.采用IK 内置词库 优点:部署方便,不用额外指定其他词库位置 缺点:分词单一化,不能指定想分词的词条 2.IK 外置静态词库 优点:部署相对方便,可以通过编辑指定文...

键走偏锋
今天
19
0
Git 2.18版本发布:支持Git协议v2,提升性能

Git 2.18版本发布:支持Git协议v2,提升性能Git 2.18版本发布:支持Git协议v2,提升性能 新版本协议的主要驱动力是使 Git 服务端能够对各种 ref(分支与 tag)进行过滤操作。 这就意味着,G...

linux-tao
今天
0
0
python浏览器自动化测试库【2018/7/22-更新】

64位py2.7版本 更新 document_GetResources 枚举页面资源 document_GetresourceText 获取指定url的内容 包括页面图片 下载地址下载地址 密码:upr47x...

开飞色
今天
44
0
关于DCL双重锁失效及解决方案

关于DCL双重锁失效及解决方案 Double Check Lock (DCL)实现单例 DCL 方式实现单例的优点是既能够在需要时才初始化单例,又能够保证线程安全,且单例对象初始化后调用getInstance方法不进行...

DannyCoder
今天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部