`

Java程序员从笨鸟到菜鸟之(四十五)大话设计模式(九)迭代器模式和命令模式

 
阅读更多

本文来自:曹胜欢博客专栏。转载请注明出处:http://blog.csdn.net/csh624366188

首先来看一下迭代器模式是干什么用的?

迭代这个名词对于熟悉Java的人来说绝对不陌生。我们常常使用JDK提供的迭代接口进行javacollection的遍历:
Iteratorit=list.iterator();
while(it.hasNext()){
 //using“it.next();”dosomebusinessslogic
}
而这就是关于迭代器模式应用很好的例子。在软件构建过程中,集合对象内部结构常常变化各异。但对于这些集合对象,我们希望在不暴露其内部结构的同时,可以让外部客户代码透明地访问其中包含的元素;同时这种透明遍历也为同一种算法在多种集合对象上进行操作提供了可能。使用面向对象技术将这种遍历机制抽象为迭代器对象应对变化中的集合对象提供了一种优雅的方法。

定义:提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。-------《设计模式》GOF

迭代器模式角色组成:
  1)迭代器角色(Iterator迭代器角色负责定义访问和遍历元素的接口。
  2)具体迭代器角色(ConcreteIterator):具体迭代器角色要实现迭代器接口,并要记录遍历中的当前位置。
  3)容器角色(Container):容器角色负责提供创建具体迭代器角色的接口。
  4)具体容器角色(ConcreteContainer具体容器角色实现创建具体迭代器角色的接口——这个具体迭代器角色于该容器的结构相关。

结构图(Struct):


从结构上可以看出,迭代器模式在客户与容器之间加入了迭代器角色。迭代器角色的加入,就可以很好的避免容器内部细节的暴露,而且也使得设计符号单一职责原则
注意,在迭代器模式中,具体迭代器角色和具体容器角色是耦合在一起的——遍历算法是与容器的内部细节紧密相关的。为了使客户程序从与具体迭代器角色耦合的困境中脱离出来,避免具体迭代器角色的更换给客户程序带来的修改,迭代器模式抽象了具体迭代器角色,使得客户程序更具一般性和重用性。这被称为多态迭代。

适用性:

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

2.支持对聚合对象的多种遍历。

3.为遍历不同的聚合结构提供一个统一的接口(即,支持多态迭代)。

具体的代码如下所示:

Iterator接口:

package iterator;

public interface Iterator{

   public Item first();

   public Item next();

   public boolean isDone();

   public Item currentItem();

}


Controller类实现了Iterator接口。

package iterator;

import java.util.Vector;

public class Controller  implements Iterator{

   private int current =0;

   Vector channel;

   public Controller(Vector v){

      channel = v;

   }

   public Item first(){

      current = 0;

      return (Item)channel.get(current);

   }

   public Item next(){

      current ++;

      return (Item)channel.get(current);

   }

   public Item currentItem(){

      return (Item)channel.get(current);

   }

   public boolean isDone(){

      return current>=channel.size()-1;

   }

}


Television接口:

package iterator;

import java.util.Vector;

public interface Television{

   public Iterator createIterator();

   public Vector getChannel();

}


HaierTV类实现了Television接口。

package iterator;

import java.util.Vector;

public class HaierTVimplements Television{

   private Vectorchannel;

   public HaierTV(){

      channel =new Vector();

      channel.addElement(new Item("channel 1"));

      channel.addElement(new Item("channel 2"));

      channel.addElement(new Item("channel 3"));

      channel.addElement(new Item("channel 4"));

      channel.addElement(new Item("channel 5"));

      channel.addElement(new Item("channel 6"));

      channel.addElement(new Item("channel 7"));

   }

   public Vector getChannel(){

      return channel;

   }

   public Iterator createIterator(){

      return new Controller(channel);

   }

}


Client客户端:

package iterator;

public class Client{

   public static void main(String[] args){

      Television tv =new HaierTV();

      Iterator it =tv.createIterator();

      System.out.println(it.first().getName());

      while(!it.isDone()){

          System.out.println(it.next().getName());

      }

   }

}


Item类的接口:

package iterator;

public class Item{

   private Stringname;

   public Item(String aName){

      name = aName;

   }

   public String getName(){

      return name;

   }

}
    
    从上面的示例中就可以看出,尽管我们没有显示的引用迭代器,但实质还是通过迭代器来遍历的。总地来说,迭代器模式就是分离了集合对象的迭代行为,抽象出一个迭代器类来负责,这样既可做到不暴露集合的内部结构,又可以让外部代码可以透明的访问集合内部的元素。迭代器模式在访问数组、集合、列表等数据时,尤其是数据库数据操作时,是非常普遍的应用,但由于它太普遍了,所以各种高级语言都对他进行了封装,所以反而给人感觉此模式本身不太常用了。

命令模式

定义:将一个请求封装为一个对象,从而使你不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。别名:动作(Action)、事务(Transaction)

面向对象的程序设计中,一个对象调用另一个对象,一般情况下的调用过程是:创建目标对象实例;设置调用参数;调用目标对象的方法。但在有些情况下有必要使用一个专门的类对这种调用过程加以封装,我们把这种专门的类称作command类。

命令模式角色组成
1)命令角色(Command):声明执行操作的接口。有java接口或者抽象类来实现。
2)具体命令角色(ConcreteCommand):将一个接收者对象绑定于一个动作;调用接收者相应的操作,以实现命令角色声明的执行操作的接口。
3)客户角色(Client):创建一个具体命令对象(并可以设定它的接收者)。
4)请求者角色(Invoker):调用命令对象执行这个请求。
5)接收者角色(Receiver):知道如何实施与执行一个请求相关的操作。任何类都可能作为一个接收者。

Command模式应用范围
-整个调用过程比较繁杂,或者存在多处这种调用。这时,使用Command类对该调用加以封装,便于功能的再利用。
-调用前后需要对调用参数进行某些处理。
-调用前后需要进行某些额外处理,比如日志,缓存,记录历史操作等。

结构如下所示:

命令模式的优点:
解耦了发送者和接受者之间联系。发送者调用一个操作,接受者接受请求执行相应的动作,因为使用Command模式解耦,发送者无需知道接受者任何接口。不少Command模式的代码都是针对图形界面的,它实际就是菜单命令,我们在一个下拉菜单选择一个命令时,然后会执行一些动作.将这些命令封装成在一个类中,然后用户(调用者)再对这个类进行操作,这就是Command模式,换句话说,本来用户(调用者)是直接调用这些命令的,如菜单上打开文档(调用者),就直接指向打开文档的代码,使用Command模式,就是在这两者之间增加一个中间者,将这种直接关系拗断,同时两者之间都隔离,基本没有关系了.显然这样做的好处是符合封装的特性,降低耦合度,Command是将对行为进行封装的典型模式,Factory是将创建进行封装的模式,Command模式,我也发现设计模式一个"通病":好象喜欢将简单的问题复杂化,喜欢在不同类中增加第三者,当然这样做有利于代码的健壮性可维护性还有复用性.

如何使用?
具体的Command模式代码各式各样,因为如何封装命令,不同系统,有不同的做法.下面事例是将命令封装在一个CollectionList,任何对象一旦加入List,实际上装入了一个封闭的黑盒中,对象的特性消失了,只有取出时,才有可能模糊的分辨出:

典型的Command模式需要有一个接口.接口中有一个统一的方法,这就是"将命令/请求封装为对象":

publicinterfaceCommand{
  publicabstractvoidexecute();
}


具体不同命令/请求代码是实现接口Command,下面有三个具体命令

publicclassEngineerimplementsCommand{

  publicvoidexecute(){
    //doEngineer'scommand
  }
}

publicclassProgrammerimplementsCommand{

  publicvoidexecute(){
    //doprogrammer'scommand
  }
}

publicclassPoliticianimplementsCommand{

  publicvoidexecute(){
    //doPolitician'scommand
  }
}

按照通常做法,我们就可以直接调用这三个Command,但是使用Command模式,我们要将他们封装起来,扔到黑盒子List里去:

publicclassproducer{
  publicstaticListproduceRequests(){
    Listqueue=newArrayList();
    queue.add(newDomesticEngineer());
    queue.add(newPolitician());
    queue.add(newProgrammer());
    returnqueue;
  }

}

这三个命令进入List中后,已经失去了其外表特征,以后再取出,也可能无法分辨出谁是Engineer谁是Programmer,看下面客户端如何调用Command模式:

publicclassTestCommand{
  publicstaticvoidmain(String[]args){
    
    Listqueue=Producer.produceRequests();
    for(Iteratorit=queue.iterator();it.hasNext();)
      
 //客户端直接调用execute方法,无需知道被调用者的其它更多类的方法名。
        ((Command)it.next()).execute();
  

  }
}

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics