迭代器模式和组合模式(管理良好的集合)

关于迭代器模式比较简单,

先看定义,提供了一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的的表示。

两个意义,1,迭代器让我们能游走于聚合对象内的每一个元素,而又不暴露其内部的表示(数据结构)。

2,把游走的任务放在迭代器上,而不是聚合上。这样简化了聚合的接口和实现,也让责任各得其所。

找了张比较近似的类图,



上面的类图中可以看出,我们定义了两个抽象类,一个是聚集(也就是集合)类 AbstractAggregate ,

还有一个就是迭代器类 AbstractIterator 类,

对于聚集类来说,它是用来存放数据的,也就是说是一个简单的只用了存放数据的地方,

而至于访问这个集合中的数据的话,就是通过迭代器 AbstractIterator 来完成了,

换句话说就是分工明确,这样上面的两个类也就都符合单一责任原则了。

在 AbstractAggregate 中定义了一个用了创建迭代器的方法 CreateIterator ,

在 AbstractIterator 中定义了得到第一个元素,下一个元素,是否结束,当前的元素等等方法。



适用性:

  • 访问一个聚合对象的内容而无需暴露它的内部表示。
  • 支持对聚合对象的多种遍历。
  • 为遍历不同的聚合结构提供一个统一的接口(即, 支持多态迭代)。

设计原则:一个类应该只有一个引用变量的原因。

内聚,这个术语,它用来度量一个类或者模块紧密地达到单一目的或责任。

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


关于Java.util.Iterator,说一点小问题,其中的一个问答项是如果不希望客户具有删除的能力,可以抛出一个Java.lang.UnsupportedOperationException运行时异常。Iterator的API文件提到可以让remove抛出这样的异常,而任何良好的客户程序只要调用了remove()方法,就应该检查是否发生了这个异常。


组合模式,结构性模式

定义为 允许你讲对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以抑制的方式处理个别对象以及对象组合。

组合模式让我们能用树形方式创建对象的结构,树里面包含了组合以及个别的对象。

使用组合结构,我们能把相同的操作应用在组合和个别对象上。换句话说,在大多数情况下,我们可以“忽略”对象组合和个别对象之间的差别。

抛却各种复杂的术语,组合模式的特点是:

对象通过实现(继承)统一的接口(抽象类),调用者对单一对象和组合对象的操作具有一致性。

组合模式很常见,Java的容器类(比如ArrayList)都实现了组合模式,更多的例子,比如文件和文件夹,headfirst中的菜单和菜单项,android中的view和viewgroup。



1,客户使用Component接口操作组合中的对象。

2,Component为组合中的所有对象定义一个接口,不管是组合还是叶节点。

3,Component可以为add(),remove(),getChild()和它的操作实现一些默认的行为。

4,这个Composite也实现了叶节点相关的操作。请注意,其中有一些操作可能对于Composite意义不大,因此在这个情况下可能产生异常

5,Composite的角色是要定义组件的行为,而这样的组件具有子节点。

6,叶节点通过实现Composite支持的操作,定义了组合内元素的行为。

7,叶节点没有孩子。


下面的代码是菜单的代码,其中在打印的时候使用了迭代器,遍历所有菜单组件,如何从面向过程的角度思考,组合模式通过递归原理实现了树结构(组合对象)的深度优先遍历。

public class Menu extends MenuComponent { 
    ArrayList menuComponents = new ArrayList();//这个是来维护一个子菜单的具体数据成员。 
    String name; 
    String description; 
    public Menu(String name, String description) { 
        this.name = name; 
        this.description = description; 
    } 
    public void add(MenuComponent menuComponent) { 
        menuComponents.add(menuComponent); 
    } 
    public void remove(MenuComponent menuComponent) { 
        menuComponents.remove(menuComponent); 
    } 
    public MenuComponent getChild(int i) { 
        return (MenuComponent)menuComponents.get(i); 
    } 
    public String getName() { 
        return name; 
    } 
    public String getDescription() { 
        return description; 
    } 
    public void print() {//由于这是一个组合的类,所以它还有成员,那么打印的时候我们就要使用迭代器, 
        System.out.print("/n" + getName()); 
        System.out.println(", " + getDescription()); 
        System.out.println("---------------------"); 
        Iterator iterator = menuComponents.iterator(); 
        while (iterator.hasNext()) { 
            MenuComponent menuComponent = //这里就体现了为什么菜单和菜单项都要继承自这一个统一的抽象类了。 
                (MenuComponent)iterator.next(); 
            menuComponent.print();//因为都实现了print方法,所以可以统一操作。 
        } 
    } 
}

上面的代码只是 MenuComponent在“内部”自定处理遍历。

下面我们实现组合迭代器,可以看做是一个“外部”的迭代器,所以有许多需要追踪的事情。外部迭代器必须维护它在遍历中的位置,以便外部客户可以通过调用hasNext()和next()来驱动遍历。想要实现一个组合迭代器,让我们的为每个组件都加上createIterator()方法。

MenuComponent:
public abstract class MenuComponent { 
    。。。。
    public abstract Iterator createIterator(); 
    public void print() { 
        throw new UnsupportedOperationException(); 
    } 
}

下面是在菜单和菜单项中实现这个方法:

Menu:
    public class Menu extends MenuComponent {
    。。。。。
    
    @Override
    public Iterator<MenuComponent> createIterator() {
        return new CompositeIterator(menuComponents.iterator());
       //若使用后边简化版迭代器则直接返回持有引用:return new CompositeIterator(menuComponents);
    }
    
    @Override
    public void print() {
        System.out.print("/n" + getName());
        System.out.println(", " + getDescription());
        System.out.println("---------------------");
        
        Iterator<MenuComponent> iter = menuComponents.iterator();
        while(iter.hasNext()){
               MenuComponent menuComponent = iter.next();
               menuComponent.print();
       }
    }
}



Menu:
    public class MenuItem extends MenuComponent {
    。。。。
    
    @Override
    public Iterator<MenuComponent> createIterator() {
	//空迭代器
	return new NullIterator();
    }
    
}


我们实现组合模式的一个外部迭代器:这是一个强大的迭代器,它遍历了组件内所有的菜单项,确保所有的子菜单都包括进来。
public class CompositeIterator implements Iterator { 
    Stack stack = new Stack(); 
    public CompositeIterator(Iterator iterator) { 
        stack.push(iterator);//我们把要遍历的的顶层组合的迭代器放入一个堆栈。外部迭代器必须维护其在遍历中的位置。
    } 
    public Object next() { 
        if (hasNext()) {//当调用者要取得下一个元素的时候,我们要确定是不是还有下一个 
            Iterator iterator = (Iterator) stack.peek();//要是有的话就从栈顶取出这个迭代器 
            MenuComponent component = (MenuComponent) iterator.next();//取出一个元素 
            if (component instanceof Menu) {//这个元素若是Menu则需要进一步使用迭代器,所以就放入堆栈中。 
                stack.push(component.createIterator()); 
            } 
            return component; 
        } else { 
            return null; 
        } 
    } 
    public boolean hasNext() { 
        if (stack.empty()) {//首先判断堆栈是否清空 
            return false; 
        } else {//若没有则反复弹出,这里递归的调用了hasNext 
            Iterator iterator = (Iterator) stack.peek(); 
            if (!iterator.hasNext()) { 
                stack.pop(); 
                return hasNext(); //递归调用
            } else { 
                return true; 
            } 
        } 
    } 
    public void remove() {//暂不支持 
        throw new UnsupportedOperationException(); 
    } 
}

最后,组合模式的要点:

1,组合模式以不遵守单一责任原则换取透明性,让Client将组合和叶节点一视同仁。

2,在实现组合模式时,有很多设计上的折中。要根据需求平衡透明性和安全性。

3,有时候系统需要遍历一个树枝构件的子构件很多次,这时候可以把遍历的缓存起来(TODO,具体的方案?)。

4,组合模式的实现中,可以让子对象持有父对象的引用进行反向追溯。


适用性:

以下情况使用Composite模式:

1,你想表示对象的部分-整体层次结构。

2,你希望用户忽略组合对象和单个对象的不同,用户将统一地使用组合结构中的所有对象。


优点:

1,组合模式可以很容易的增加新的构件。

2,使用组合模式可以使客户端变得很容易设计,因为客户端可以对组合和叶节点一视同仁。


缺点:

1,使用组合模式化,控制树枝构件的类型不太容易(instancof?)

2,用继承的方法增加新的行为很困难(类型多,不定?)。


看个例子,

JUnit是一个单元测试框架,按照此框架下的规范来编写测试代码,就可以使单元测试自动化。为了达到“自动化”的目的,JUnit中定义了两个概念:TestCase和TestSuite。TestCase是对一个类或者jsp等等编写的测试类;而TestSuite是一个不同TestCase的集合,当然这个集合里面也可以包含TestSuite元素,这样运行一个TestSuite会将其包含的TestCase全部运行。
然而在真实运行测试程序的时候,是不需要关心这个类是TestCase还是TestSuite,我们只关心测试运行结果如何。这就是为什么JUnit使用组合模式的原因。
JUnit为了采用组合模式将TestCase和TestSuite统一起来,创建了一个Test接口来扮演抽象构件角色,这样原来的TestCase扮演组合模式中树叶构件角色,而TestSuite扮演组合模式中的树枝构件角色。下面将这三个类的有关代码分析如下:
 
//Test接口——抽象构件角色
public interface Test {
       /**
        * Counts the number of test cases that will be run by this test.
        */
       public abstract int countTestCases();
       /**
        * Runs a test and collects its result in a TestResult instance.
        */
       public abstract void run(TestResult result);
}
 
//TestSuite类的部分有关源码——Composite角色,它实现了接口Test
public class TestSuite implements Test {
//用了较老的Vector来保存添加的test
       private Vector fTests= new Vector(10);
       private String fName;
       …… 
/**
        * Adds a test to the suite.
        */
       public void addTest(Test test) {          
//注意这里的参数是Test类型的。这就意味着TestCase和TestSuite以及以后实现Test接口的任何类都可以被添加进来
              fTests.addElement(test);
       }
       ……
       /**
        * Counts the number of test cases that will be run by this test.
        */
       public int countTestCases() {
              int count= 0;
              for (Enumeration e= tests(); e.hasMoreElements(); ) {
                     Test test= (Test)e.nextElement();
                     count= count + test.countTestCases();
              }
              return count;
       }
       /**
        * Runs the tests and collects their result in a TestResult.
        */
       public void run(TestResult result) {
              for (Enumeration e= tests(); e.hasMoreElements(); ) {
                    if (result.shouldStop() )
                           break;
                     Test test= (Test)e.nextElement();
                           //关键在这个方法上面
                     runTest(test, result);
              }
       }
            //这个方法里面就是递归的调用了,至于你的Test到底是什么类型的只有在运行的时候得知
            public void runTest(Test test, TestResult result) {
                   test.run(result);
            }
……
}
 
//TestCase的部分有关源码——Leaf角色,你编写的测试类就是继承自它
public abstract class TestCase extends Assert implements Test {
       ……
       /**
        * Counts the number of test cases executed by run(TestResult result).
        */
       public int countTestCases() {
              return 1;
       }
/**
        * Runs the test case and collects the results in TestResult.
        */
       public void run(TestResult result) {
              result.run(this);
       }
……
}
       可以看出这是一个偏重安全性的组合模式。因此在使用TestCase和TestSuite时,不能使用Test来代替。


参考:http://www.iteye.com/topic/300446

http://www.cnblogs.com/BoyXiao/archive/2010/05/14/1735078.html

http://www.cnblogs.com/god_bless_you/archive/2010/06/10/1755844.html

http://blog.csdn.net/ai92/article/details/298336

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值