行为型模式介绍及实例(下)

  本系列文章共分为六篇:
    设计模式的分类与区别
    创建型模式介绍及实例
    结构型模式介绍及实例
    行为型模式介绍及实例(上)
    行为型模式介绍及实例(下)

六、中介者模式

6.1 中介者模式定义

  定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。

6.2 中介者模式特点

  优点:
   1>降低了对象之间的耦合性,使得对象易于独立地被复用。
   2>将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。
  缺点:
   1>当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护。

6.3 中介者模式主要角色

  抽象中介者角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
  具体中介者角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
  抽象同事类角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
  具体同事类角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。

6.4 中介者模式实现方式

  此处以在班级里收作业为例,一个班级里有不同的课代表,他们会发起收作业的动作,同时,他们又要交作业,这恰好符合中介者模式。示例代码如下:

/*抽象中介者*/
public abstract  class Mediator {
    public abstract void register(Student stu); //将stu加入通知列表
    public abstract void relay(Student stu); //转发消息
}
/*具体中介者*/
public class RealMediator extends Mediator{
    private List<Student> stus=new ArrayList<Student>();
    public void register(Student stu){
        if(!stus.contains(stu)){
        	stus.add(stu);
        	stu.setMedium(this);
        }
    }
    public void relay(Student stu1){
        for(Student stu:stus){
            if(!stu.equals(stu1)){
                ((Student)stu).receive();
            }   
        }
    }
}
/*抽象同事类*/
public abstract  class Student {
    protected Mediator mediator;
    public void setMedium(Mediator mediator){
        this.mediator=mediator;
    }   
    public abstract void receive();   
    public abstract void send();
}
/*具体同事类:物理课代表*/
public class PhysicsClassrepresentative extends Student{
	@Override
	public void receive() {
	    System.out.println("物理课代表说:收到,马上交化学作业");		
	}
	@Override
	public void send() {
        System.out.println("物理课代表说:收物理作业了");
        mediator.relay(this); //请中介者转发
	}
}
/*具体同事类:化学课代表*/
public class ChemistryClassrepresentative extends Student{
	@Override
	public void receive() {
	    System.out.println("化学课代表说:收到,马上交物理作业");		
	}
	@Override
	public void send() {
        System.out.println("化学课代表说:收化学作业了");
        mediator.relay(this); //请中介者转发
	}
}
/*测试类*/
public class MediatorTest {
    public static void main(String[] args) {
        Mediator md=new RealMediator();
        Student stu1,stu2;
        stu1=new PhysicsClassrepresentative();
        stu2=new ChemistryClassrepresentative();
        md.register(stu1);
        md.register(stu2);
        stu1.send();
        System.out.println("-------------");
        stu2.send();
    }
}

  结果如下:

物理课代表说:收物理作业了
化学课代表说:收到,马上交物理作业
化学课代表说:收化学作业了
物理课代表说:收到,马上交化学作业

6.5 中介者模式应用场景

  中介者模式通常适用的场景:
   1)当对象之间存在复杂的网状结构关系而导致依赖关系混乱且难以复用时。
   2)当想创建一个运行于多个类之间的对象,又不想生成新的子类时。

七、迭代器模式

7.1 迭代器模式定义

  提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。

7.2 迭代器模式特点

  优点:
   1>访问一个聚合对象的内容而无须暴露它的内部表示。
   2>遍历任务交由迭代器完成,这简化了聚合类。
   3>它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历。
   4>增加新的聚合类和迭代器类都很方便,无须修改原有代码。
   5>封装性良好,为遍历不同的聚合结构提供一个统一的接口。
  缺点:
   1>增加了类的个数,这在一定程度上增加了系统的复杂性。

7.3 迭代器模式主要角色

  抽象聚合角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口。
  具体聚合角色:实现抽象聚合类,返回一个具体迭代器的实例。
  抽象迭代器角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法。
  具体迭代器角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。

7.4 迭代器模式实现方式

  此处以中国知名的手机品牌为例,这些品牌有华为、小米、OPPO、VIVO、锤子等。示例代码如下:

/*抽象聚合*/
public interface Aggregate {
    public void add(Phone phone); 
    public void remove(Phone phone); 
    public Iterator getIterator(); 
}
/*具体聚合*/
public class ConcreteAggregate implements Aggregate{
    private List<Phone> phoneList=new ArrayList<Phone>(); 
    public void add(Phone phone){ 
    	phoneList.add(phone); 
    }
    public void remove(Phone phone){ 
    	phoneList.remove(phone); 
    }
    public Iterator getIterator(){    return(new ConcreteIterator(phoneList));   }   
}
/*抽象迭代器*/
public interface  Iterator {
    Phone first();
    Phone next();
    boolean hasNext();
}
/*具体迭代器*/
public class ConcreteIterator  implements Iterator{ 
	 private List<Phone> list=null; 
	    private int index=-1; 
	    public ConcreteIterator(List<Phone> list){ 
	        this.list=list; 
	    } 
	    public boolean hasNext(){ 
	        if(index<list.size()-1)
	             return true;
	        else    
	             return false;  
	    }
	    public Phone first(){
	        index=0;
	        Phone phone=list.get(index);;
	        return phone;
	    }
	    public Phone next(){ 
	    	Phone phone=null; 
	        if(this.hasNext()){ 
	        	phone=list.get(++index); 
	        } 
	        return phone; 
	    }   
}
/*实体类:手机*/
public class Phone {
	private String name;
	public Phone(String name){
		this.name = name;
	}
	public String getName(){	return this.name;    }
}
/*测试类*/
public class IteratorTest {
    public static void main(String[] args) {
        Aggregate ag=new ConcreteAggregate(); 
        ag.add(new Phone("华为")); 
        ag.add(new Phone("小米")); 
        ag.add(new Phone("OPPO"));
        ag.add(new Phone("VIVO"));
        ag.add(new Phone("锤子"));
        System.out.print("聚合的手机品牌有:\n");
        Iterator it=ag.getIterator(); 
        while(it.hasNext()){ 
            Phone phone=it.next(); 
            System.out.print(phone.getName()+"\n"); 
        }
        Phone firstPhone=it.first();
        System.out.println("\n第一是:"+firstPhone.getName());
    }
}

  测试结果如下:

聚合的手机品牌有:
华为
小米
OPPO
VIVO
锤子
第一是:华为

7.5 迭代器模式应用场景

  迭代器模式通常适用的场景:
   1)当需要为聚合对象提供多种遍历方式时。
   2)当需要为遍历不同的聚合结构提供一个统一的接口时。
   3)当访问一个聚合对象的内容而无须暴露其内部细节的表示时。

八、解释器模式

8.1 解释器模式定义

  给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。

8.2 解释器模式特点

  优点:
   1>扩展性好。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
   2>容易实现。在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易。
  缺点:
   1>执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。
   2>会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护。
   3>可应用的场景比较少。在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到。

8.3 解释器模式主要角色

  抽象表达式角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
  终结符表达式角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
  非终结符表达式角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
  环境角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
  客户端:主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。

8.4 解释器模式实现方式

  此处以员工是否需要参加新人培训为例,在无锡和北京两地工作的3年以下的员工需要参加,其他的则不需要。代码示例如下:

/*抽象表达式*/
public interface  Expression {
	public boolean interpret(String info);
}
/*终结表达式*/
public class TerminalExpression implements Expression{
    private Set<String> set= new HashSet<String>();
    public TerminalExpression(String[] data){
        for(int i=0;i<data.length;i++)set.add(data[i]);
    }
    public boolean interpret(String info){
        if(set.contains(info)){
            return true;
        }
        return false;
    }
}
/*非终结符表达式*/
public class AndExpression implements Expression{
    private Expression city=null;    
    private Expression person=null;
    public AndExpression(Expression city,Expression person){
        this.city=city;
        this.person=person;
    }
    public boolean interpret(String info){
        String s[]=info.split(",");       
        return city.interpret(s[0])&&person.interpret(s[1]);
    }
}
/*环境*/
public class Context {
    private String[] citys={"无锡","北京"};
    private String[] persons={"1年","2年","3年"};
    private Expression cityPerson;
    public Context(){
        Expression city=new TerminalExpression(citys);
        Expression person=new TerminalExpression(persons);
        cityPerson=new AndExpression(city,person);
    }
    public void train(String info){
        boolean ok=cityPerson.interpret(info);
        if(ok) System.out.println("您入职年份较少,需要参加新人培训");
        else System.out.println("您入职年份较多或工作地不在无锡、北京,不用再参加新人培训");   
    }
}
/*测试类*/
public class InterpreterTest {
	public static void main(String[] args) { 
        Context personnel=new Context();
        personnel.train("无锡,2年");
        personnel.train("北京,5年");
        personnel.train("无锡,3年");
        personnel.train("广州,2年");
        personnel.train("北京,2年");
	}
}

  测试结果如下:

您入职年份较少,需要参加新人培训
您入职年份较多或工作地不在无锡、北京,不用再参加新人培训
您入职年份较少,需要参加新人培训
您入职年份较多或工作地不在无锡、北京,不用再参加新人培训
您入职年份较少,需要参加新人培训

8.5 解释器模式应用场景

  解释器模式通常适用的场景:
   1)当语言的文法较为简单,且执行效率不是关键问题时。
   2)当问题重复出现,且可以用一种简单的语言来进行表达时。
   3)当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候,如 XML 文档解释。

九、命令模式

9.1 命令模式定义

  将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。

9.2 命令模式特点

  优点:
   1>降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
   2>增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
   3>可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
   4>方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
  缺点:
   可能产生大量具体命令类。因为计对每一个具体操作都需要设计一个具体命令类,这将增加系统的复杂性。

9.3 命令模式主要角色

  抽象命令类角色:声明执行命令的接口,拥有执行命令的抽象方法 execute()。
  具体命令角色角色:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
  实现者/接收者角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者。
  调用者/请求者角色:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。

9.4 命令模式实现方式

  此处以项目经理追问各研发小组进度为例,项目经理为调用者,查询项目进度为命令,各研发小组Leader为接收者。示例代码如下:

/*抽象命令*/
public abstract class Command {
	public abstract void execute();
}
/*具体命令:查询项目进度*/
public class QueryProjectCommand extends Command{
    private GroupLeaderA groupLeaderA;
    private GroupLeaderB groupLeaderB;
    QueryProjectCommand(){
    	groupLeaderA = new GroupLeaderA();
    	groupLeaderB = new GroupLeaderB();
    }
    public void execute(){
    	groupLeaderA.action();
    	groupLeaderB.action();
    }
}
/*接收者A:A研发组Leader*/
public class GroupLeaderA {
    public void action(){
        System.out.println("A项目组进度为:已完成70%功能开发");
    }
}
/*接收者B:B研发组Leader*/
public class GroupLeaderB {
    public void action(){
        System.out.println("B项目组进度为:已完成50%功能开发");
    }
}
/*调用者:项目经理*/
public class ProjectManager {
    private Command command;
    public ProjectManager(Command command){
        this.command=command;
    }
    public void setCommand(Command command){
        this.command=command;
    }
    public void call(){
        System.out.println("项目经理查询各小组研发进度");
        command.execute();
    }
}
/*测试类*/
public class CommandTest {
    public static void main(String[] args){
        Command cmd = new QueryProjectCommand();
        ProjectManager projectManager = new ProjectManager(cmd);
        System.out.println("客户访问调用者(项目经理)的call()方法...");
        projectManager.call();
    }
}

  输出结果如下:

客户访问调用者(项目经理)的call()方法…
项目经理查询各小组研发进度
A项目组进度为:已完成70%功能开发
B项目组进度为:已完成50%功能开发

9.5 命令模式应用场景

  命令模式通常适用的场景:
   1)当系统需要将请求调用者与请求接收者解耦时,命令模式使得调用者和接收者不直接交互。
   2)当系统需要随机请求命令或经常增加或删除命令时,命令模式比较方便实现这些功能。
   3)当系统需要执行一组操作时,命令模式可以定义宏命令来实现该功能。
   4)当系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作时,可以将命令对象存储起来,采用备忘录模式来实现。

十、责任链模式

10.1 责任链模式定义

  为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。

10.2 责任链模式特点

  优点:
   1>降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
   2>增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
   3>增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
   4>责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
   5>责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
  缺点:
   1>不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
   2>对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
   3>职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。

10.3 责任链模式主要角色

  抽象处理者角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
  具体处理者角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
  客户类角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

10.4 责任链模式实现方式

  此处以代购为例,不同的代购者可以购买不同价位的产品,代购者A可以购买0-300元的产品,代购者B可以购买300-600元的产品,代购者C可以购买600元以上的产品。示例代码如下:

/*抽象处理者:代购者*/
public abstract class AgentBuyer {
    private AgentBuyer next;
    public void setNext(AgentBuyer next){
        this.next=next; 
    }
    public AgentBuyer getNext(){     return next;  }   
    //处理请求的方法
    public abstract void buyGoods(int money);    
}
/*具体处理者:代购者A*/
public class AgentBuyerA extends AgentBuyer{
	@Override
	public void buyGoods(int money) {
        if(money>0 && money <= 300) {
            System.out.println("您可以通过代购者A购买"+money+"元的商品");       
        }
        else{
            if(getNext() != null) {
                getNext().buyGoods(money);             
            }
        } 
	}
}
/*具体处理者:代购者B*/
public class AgentBuyerB extends AgentBuyer{
	@Override
	public void buyGoods(int money) {
        if(money>300 && money <= 600) {
            System.out.println("您可以通过代购者B购买"+money+"元的商品");       
        }else{
            if(getNext() != null) {
                getNext().buyGoods(money);             
            }
        } 
	}
}
/*具体处理者:代购者C*/
public class AgentBuyerC extends AgentBuyer{
	@Override
	public void buyGoods(int money) {
       System.out.println("您可以通过代购者C购买"+money+"元的商品");       
	}
}
/*测试类*/
public class ResponsibilityTest {
    public static void main(String[] args){
        //组装责任链 
    	AgentBuyer gentBuyerA =new AgentBuyerA();
    	AgentBuyer gentBuyerB =new AgentBuyerB();
    	AgentBuyer gentBuyerC =new AgentBuyerC();
    	gentBuyerA.setNext(gentBuyerB);
    	gentBuyerB.setNext(gentBuyerC);
    	gentBuyerA.buyGoods(500);
    	gentBuyerA.buyGoods(1000);
    }
}

  输出结果如下:

您可以通过代购者B购买500元的商品
您可以通过代购者C购买1000元的商品

10.5 责任链模式应用场景

  责任链模式通常适用的场景:
   1)有多个对象可以处理一个请求,哪个对象处理该请求由运行时刻自动确定。
   2)可动态指定一组对象处理请求,或添加新的处理者。
   3)在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求。

十一、访问者模式

11.1 访问者模式定义

  将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。

11.2 访问者模式特点

  优点:
   1>扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
   2>复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
   3>灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
   4>符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。
  缺点:
   1>增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
   2>破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
   3>违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。

11.3 访问者模式主要角色

  抽象访问者角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
  具体访问者角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
  抽象元素角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。
  具体元素角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
  对象结构角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。

11.4 访问者模式实现方式

  此处以老师查询学生成绩为例,一个学生有在不同科目上有不同的成绩,老师除了询问本科目的成绩之外,有时也会询问一下其他科目的成绩,来看下学生是否在其他科目投入精力过多,导致自己教授的科目成绩下降。示例代码如下:

/*抽象访问者:老师*/
public interface  Teacher {
    void query(PhysicsAchievement physicsAchievement);
    void query(ChemistryAchievement chemistryAchievement);
}
/*具体访问者1:物理老师*/
public class PhysicsTeacher implements Teacher{
    public void query(PhysicsAchievement element){
        System.out.println("物理老师查询物理课成绩,"+element.operationA());
    }
    public void query(ChemistryAchievement element){
        System.out.println("物理老师查询化学课成绩,"+element.operationB());
    }
}
/*具体访问者2:化学老师*/
public class ChemistryTeacher implements Teacher{
    public void query(PhysicsAchievement element){
        System.out.println("化学老师查询物理课成绩,"+element.operationA());
    }
    public void query(ChemistryAchievement element){
        System.out.println("化学老师查询化学课成绩,"+element.operationB());
    }
}
/*抽象元素:成绩*/
public interface  Achievement {
	void accept(Teacher teacher);
}
/*具体元素1:物理成绩*/
public class PhysicsAchievement implements Achievement{
    public void accept(Teacher teacher){
    	teacher.query(this);
    }
    public String operationA(){   return "物理成绩85分";   }
}
/*具体成绩2:化学成绩*/
public class ChemistryAchievement implements Achievement{
    public void accept(Teacher teacher){
    	teacher.query(this);
    }
    public String operationB(){   return "化学成绩90分";   }
}
/*对象结构*/
public class ObjectStructure {
    private List<Achievement> list=new ArrayList<Achievement>();   
    public void accept(Teacher visitor){
        Iterator<Achievement> i=list.iterator();
        while(i.hasNext()){
            ((Achievement) i.next()).accept(visitor);
        }      
    }
    public void add(Achievement element){
        list.add(element);
    }
    public void remove(Achievement element){
        list.remove(element);
    }
}
/*测试类*/
public class VisitorTest {
    public static void main(String[] args){
        ObjectStructure os=new ObjectStructure();
        os.add(new PhysicsAchievement());
        os.add(new ChemistryAchievement());
        Teacher visitor=new PhysicsTeacher();
        os.accept(visitor);
        visitor=new ChemistryTeacher();
        os.accept(visitor);
    }
}

  结果如下:

物理老师查询物理课成绩,物理成绩85分
物理老师查询化学课成绩,化学成绩90分
化学老师查询物理课成绩,物理成绩85分
化学老师查询化学课成绩,化学成绩90分

11.5 访问者模式应用场景

  访问者模式通常适用的场景:
   1)对象结构相对稳定,但其操作算法经常变化的程序。
   2)对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
   3)对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。
  参考资料:行为型模式概述

猜你喜欢

转载自blog.csdn.net/m0_37741420/article/details/107445024