迭代器模式

很多人认为今天看迭代器模式使用价值远不如学习价值了,Martin Flower 甚至在在自己的网页上提出撤销此模式。当然这种说法也不无道理,因为现在像高级语言如C# 、java 等已经把这个模式做在语言中了。但是这个模式就真的如网上所言没有实用价值了吗?我并不认为是这样的。

    本文将介绍这种模式与其它模式搭配使用 适配器-迭代器模式。至于有没有用评判在你惊讶 希望于你有益~

模式背景

    有两家餐厅A和B,餐厅A是一家早餐餐厅店,而餐厅B是一家午餐餐厅店。

    现在两个餐厅的大Boss决定将两家餐厅进行合并。在餐厅A中,菜单的逻辑实现是基于ArrayList的,而餐厅B的菜单逻辑实现则是基于数组的。如果去修改任何一家餐厅的菜单实现,都可能会引发一些不必要的修改,且这种修改可能还会导致不必要的Bug,所以A和B都不愿意去修改菜单的原始实现。

    现在的需求是,在不改变两家餐厅对菜单的实现上,再方便地对菜单进行遍历。

                                                                      -- 摘自《Head Frist设计模式》


模式定义

何谓迭代器模式?所谓迭代器模式就是提供一种方法顺序访问一个聚合对象中的各个元素,而不是暴露其内部的表示。在实际的开发过程中,我们可能需要针对不同的需求,可能需要以不同的方式来遍历整个整合对象,但是我们不希望在聚合对象的抽象接口层中充斥着各种不同的便利操作这个时候我们就需要这样一种东西,它应该具备如下三个功能:

      1、能够便利一个聚合对象。

      2、我们不需要了解聚合对象的内部结构。

      3、能够提供多种不同的遍历方式。

      这三个功能就是迭代器模式需要解决的问题。作为一个功能强大的模式,迭代器模式把在元素之间游走的责任交给迭代器,而不是聚合对象这样做就简化了聚合的接口和实现,也可以让聚合更专注在它所应该专注的事情上,这样做就更加符合单一责任原则


科普

    聚合对象:

聚合(Cohesion)是一个模块内部各成分之间相关联程度的度量。

这里有多个含义值得考虑。首先,聚合是对一个模块内部的度量,这也是许多情况下我们把聚合称之为内聚的原因。第二,这里出现的模块是广义的模块,它可能是子系统,可能是功能模块,也可能是功能模块中的某一个类。从不同的层次看,聚合的程度也会有所不同。至于为什么不同,后面会有解释。第三,模块的成分包括模块的行为和状态。要做到高聚合,那么模块内部的行为必须要与模块的内部状态紧密关联。通俗来讲,一个模块仅完成一个独立的功能,模块内部不存在与该功能无关的操作或状态。

举一个生活中的例子。

有两座城市Sidtown和Fredborough,连接两座城市的公路一天到晚总是拥堵不堪。经过"有关部门"调查之后发现,这两座城市中有两家公司Better Mousetrap和 Zokko Soda,Better Mousetrap的工厂建造在Sidtown,而该工厂的员工都居住在Fredborough,所以每天早上大批员工从Fredborough出发前往Sidtown,并在傍晚返回;类似的,Zokko Soda公司的运输车在每天的工作时间都需要在制瓶工厂和灌装工厂穿梭来往。


很明显,如果Better Mousetrap的工厂和员工居住地都在同一城市,而Zokko Soda的两座工厂都建造在另一座城市,那么城市之间的交通状况将会明显改善。


对比两图,上面两座城市间之所以出现交通的问题,是因为每座城市的"聚合性"都比较低:不相关的两个公司出现在了同一座城市,使得城市内部交通的利用率比较低,而城市之间的交通出现了超负荷。

模式结构


模式实现

公共接口

package com.nchu.iterator;

import java.util.Iterator;

public interface Menu {  
    public Iterator<MenuItem> createIterator();  
}  

迭代器的创建

在上面的类图中我们可以找到BreakfastMenu和LunchMenu两个菜单类,它们都是实现了Menu接口的。可是,因为它们包含了不同的容器对象(BreakfastMenu包含了ArrayList,LunchMenu包含了数组),所以在创建迭代器的时候就会有所不同。

BreakfastMenu创建迭代器

    

package com.nchu.iterator;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class BreakfastMenu implements Menu {

	private List<MenuItem> menuItems = new ArrayList<>();
	
	public void addMenuItem(MenuItem menuItem){
		menuItems.add(menuItem);
	}
	
	public void remove(MenuItem menuItem){
		menuItems.remove(menuItem);
	}
	
	public Iterator<MenuItem> createIterator() {  
        return menuItems.iterator();  
    }  
}

因为ArrayList自身就包含了迭代器的实现,所以这里就可以直接返回ArrayList的迭代器。

LuchMenu创建迭代器

package com.nchu.iterator;

import java.util.Iterator;

public class LuchMenu implements Menu {

	private MenuItem[] menuItems = new MenuItem[30];
	private int position = 0;
	
	public void addMenuItem(MenuItem menuItem){
		menuItems[position++] = menuItem;
	}
	
	public void remove(MenuItem menuItem){
		int i = 0;
		while(menuItems[i] != menuItem) ++i;
	}
	
	public Iterator<MenuItem> createIterator() {  
                return  new LunchIterator(menuItems, position);  
    }  
}

数组没有迭代器的实现部分,所以与上面的创建方式会有所不同,此时正是迭代器模式大显身手的时候。

自定义迭代器

由于数组本身不具备迭代的功能,所以我们就需要对其进行扩展。可是,如果想要“迭代”数组,其根本实现还是要依赖于数组的循环遍历。因为数组只有这一种方式可以依次提取元素。在迭代器中有两个核心方法:hasNext()和next()。所以,我们就利用这两个方法变相实现对数组的迭代。

package com.nchu.iterator;

import java.util.Iterator;



public class LunchIterator implements Iterator<MenuItem>{

	private MenuItem[] menuItems = null;  
    private int position = 0;
    private int index;
    
	public LunchIterator(MenuItem[] menuItems, int index) {
		this.menuItems = menuItems;
		this.index = index;
	}

	@Override
	public boolean hasNext() {
		if(menuItems == null)
			return false;
		return position < this.index;
	}

	@Override
	public MenuItem next() {
		MenuItem munuItem = menuItems[position++];
		return munuItem;
	}

	@Override
	public void remove() {
	}
}


迭代器组合

这里说的迭代组合是针对迭代器之外,客户端的实现逻辑。假定我们有一个女服务员,她可以打印出客户所需要的菜单,而不用关心此菜单的实现方式。

package com.nchu.iterator;

import java.util.Iterator;

public class Waitress {  
  
    private Iterator<MenuItem> iterator = null;  
    private Menu menu = null;  
      
    public Waitress(Menu menu) {  
        this.menu = menu;  
    }  
      
    public void printMenu() {  
        System.out.println("\n菜单:");  
        iterator = menu.createIterator();  
        while(iterator.hasNext()) {  
            System.out.println(iterator.next());  
        }  
    }  
} 


菜品类

package com.nchu.iterator;

public class MenuItem {

	private String foodName;
	
	public MenuItem(String foodName) {
		this.foodName = foodName;
	}

	@Override
	public String toString() {
		return this.foodName;
	}
	
	
}

测试类

package com.nchu.iterator;

public class Client {

	public static void main(String[] args) {
		BreakfastMenu bm = new BreakfastMenu();
		bm.addMenuItem(new MenuItem("a"));
		bm.addMenuItem(new MenuItem("b"));
		bm.addMenuItem(new MenuItem("c"));
		
		LuchMenu lm = new LuchMenu();
		lm.addMenuItem(new MenuItem("d"));
		lm.addMenuItem(new MenuItem("e"));
		lm.addMenuItem(new MenuItem("f"));
		
//		Waitress wa = new Waitress(bm);
		Waitress wa = new Waitress(lm);
		
		wa.printMenu();
	}
}

使用场景

     1、访问一个聚合对象的内容而无须暴露它的内部表示。

      2、需要为聚合对象提供多种遍历方式。

      3、为遍历不同的聚合结构提供一个统一的接口


模式优缺点

    优点
         1、它支持以不同的方式遍历一个聚合对象。

       2、迭代器简化了聚合类。

       3、在同一个聚合上可以有多个遍历。

       4、在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。

        

    缺点

        由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。


模式总结

        迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明的访问集合内部数据。

    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值