精华内容
下载资源
问答
  • 熟练使用JAVA实现行为型设计模式-观察模式,分析观察走模式的实现原理,通过实例学习如何编程实现观察者模式,并且理解观察者模式在java事件处理中的应用。 二、实验内容 1、在某多人联机对战游戏中,多个玩家可以...

    实验一:行为型设计模式之观察模式
    一、实验目的
    熟练使用JAVA实现行为型设计模式-观察模式,分析观察走模式的实现原理,通过实例学习如何编程实现观察者模式,并且理解观察者模式在java事件处理中的应用。
    二、实验内容
    1、在某多人联机对战游戏中,多个玩家可以加入同一战队组成联盟,但战队中的某一成员受到敌人攻击时将给所有起的盟友发送通知,盟友收到通知后将作出响应。
    试使用观察者模式设计并实现的过程,以实现战队成员之间的联动。

    2、微信公众号与微信订阅者的故事,公众号负责推送消息,并且有取消关注和关注,普通微信号是关注公众号,并且公众推出的内容可以更新,可以取消关注。

    三、实验步骤
    实例1的结构图如图所示:

    实例2的结构图如图所示:

    第一个实例具体代码如下:
    1、AllyControlCenter.java

    package a.Observer.game;
    import java.util.*;
    public abstract class AllyControlCenter{
    	protected String allyName;//战队名称
    	protected ArrayList <Observer> players = new ArrayList <Observer>();//定义一个集合用于存储战队成员
    	public void setAllyName(String allyName) {
    		this.allyName = allyName;
    	}
    	public String getAllyName() {
    		return this.allyName;
    	}
    	
    	//注册方法
    	public void join(Observer obs) {
    		System.out.println(obs.getName()+"加入"+this.allyName+"战队!");
    		players.add(obs);
    	}
    	
    	//注销方法
    	public void quit(Observer obs) {
    		System.out.println(obs.getName()+"退出"+this.allyName+"战队!");
    		players.remove(obs);
    }
    	//声明抽象方法
    	public abstract void notifyObserver(String name);
    }
    
    

    2、Client.java

    package a.Observer.game;
    
    public class Client {
    	public static void main(String args[]) {
    		AllyControlCenter acc;
    		acc = new ConcreteAllyControlCenter("金庸群侠");
    		
    		Observer player1,player2,player3,player4;
    		
    		player1 = new Player("杨过");
    		acc.join(player1);
    		
    		player2 = new Player("令狐冲");
    		acc.join(player2);
    
    		player3= new Player("张无忌");
    		acc.join(player3);
    
    		player4 = new Player("段誉");
    		acc.join(player4);
    		
    		player1.beAttacked(acc);
    	}
    
    }
    

    3、ConcreteAllyControlCenter

    package a.Observer.game;
    
    public class ConcreteAllyControlCenter extends AllyControlCenter{
    	public ConcreteAllyControlCenter(String allyName) {
    		System.out.println(allyName+"战队组建成功!");
    		System.out.println("-------------------------");
    		this.allyName = allyName;
    	}
    	
    	//实现通知方法
    	public void notifyObserver(String name) {
    		System.out.println(this.allyName+"战队紧急通知,盟友"+name+"遭受敌人攻击。");
    		//遍历
    		for(Object obs :players) {
    			if(!((Observer)obs).getName().equalsIgnoreCase(name)) {
    				((Observer)obs).help();
    			}
    		}
    	}
    }
    

    4、Observer.java

    package a.Observer.game;
    
    public interface Observer {
    	public String getName();
    	public void setName(String name);
    	public void help();
    	public void beAttacked(AllyControlCenter acc);
    }
    

    5、Player.java

    package a.Observer.game;
    
    public class Player implements Observer{
    	private String name;
    	
    	public Player(String name) {
    		this.name=name;
    	}
    	public void setName(String name) {
    		this.name=name;
    	}
    	public String getName() {
    		return this.name;
    	}
    	public void help() {
    		System.out.println("坚持住,"+this.name+"来救你!");
    		
    	}
    	public void beAttacked(AllyControlCenter acc) {
    		System.out.println(this.name+"被攻击!");
    		acc.notifyObserver(name);
    	}
    
    }
    

    运行结果如下:

    第二个实例具体代码如下:
    1、Observer.java

    package a.Observer.winxin;
    
    public interface Observer {
    	public abstract void update(String message);
    }
    

    2、Subject.java

    package a.Observer.winxin;
    
    public interface Subject {
    	public abstract void registerObserver(Observer observer);
    	public abstract void removeObserver(Observer observer);
    	public abstract void notifyObserver(String message);
    }
    

    3、Test.java

    package a.Observer.winxin;
    
    public class test {
    
    	public static void main(String[] args) {
    		// TODO 自动生成的方法存根'
    		Weixinusers user1=new Weixinusers();
    		Weixinusers user2=new Weixinusers();
    		Weixinusers user3=new Weixinusers();
    		user1.setName("User1");
    		user2.setName("User2");
    		user3.setName("user3");
    		WeiXinSubject su1=new WeiXinSubject();
    		su1.setName("电子与信息工程分院");
    		WeiXinSubject su2=new WeiXinSubject();
    		su2.setName("机电工程分院");
    		WeiXinSubject su3=new WeiXinSubject();
    		su3.setName("土木建筑工程分院");
    		user1.subscribeSubject(su1);
    		user1.subscribeSubject(su2);
    		user2.subscribeSubject(su2);
    		user3.subscribeSubject(su3);
    		su1.setMessage("2019体育课开始选课了!");
    		su1.notifyObserver(su1.getMessage());
    		su2.setMessage("电信分院体育课选课名单公布");
    		su2.notifyObserver(su2.getMessage());
    		su3.setMessage("电信分院体育课选课名单公布");
    		su3.notifyObserver(su3.getMessage());
    	}
    
    }
    

    4、WeiXinSubject.java

    package a.Observer.winxin;
    import java.util.*;
    public class WeiXinSubject implements Subject{
    	private String message,name;
    	private List<Observer> ob=new ArrayList<Observer>();
    	public void setName(String name)
    	{
    		this.name=name;
    	}
    	public String getName() {
    		return this.name;
    	}
    	public void setMessage(String message)
    	{
    		this.message=message;
    	}
    	public String getMessage() {
    		return this.message;
    	}
    	public  void registerObserver(Observer observer) {
    		if(ob.contains(observer))
    				return;
    		else
    		ob.add(observer);
    	}
    	public  void removeObserver(Observer observer) {
    		ob.remove(observer);
    	}
    	public  void notifyObserver(String message) {
    		for(Observer o:ob)
    		o.update(this.message);
    	}
    }
    

    5、Weixinusers.java

    package a.Observer.winxin;
    import java.util.*;
    public class Weixinusers implements Observer{
    	private String name;
    	private List<Subject> users=new ArrayList<Subject>();
    	public void setName(String name)
    	{
    		this.name=name;
    	}
    	public String getName()
    	{
    		return this.name;
    	}
    	public void update(String message)
    	{
    		System.out.println("公众号推送了消息:"+message);
    	}
    	public void subscribeSubject(Subject sub)
    	{
    		if(users.contains(sub))
    			return;
    		else
    		{
    			users.add(sub);
    			System.out.println(this.name+":关注"+((WeiXinSubject)sub).getName()+"公众号成功!");
    			for(Subject u:users)
    				u.registerObserver(this);
    		}
    		
    	}
    	public void deleSubject(Subject sub) 
    	{
    		users.remove(sub);
    		//for(Subject u:users)
    		sub.removeObserver(this);
    	}
    }
    

    四、实验结果分析
    1 观察者模式的设计意图

    2 观察者模式的结构
    在观察者模式结构图中包含如下几个角色:
    ● Subject(目标):目标又称为主题,它是指被观察的对象。在目标中定义了一个观察者集合,一个观察目标可以接受任意数量的观察者来观察,它提供一系列方法来增加和删除观察者对象,同时它定义了通知方法notify()。目标类可以是接口,也可以是抽象类或具体类。
    ● ConcreteSubject(具体目标):具体目标是目标类的子类,通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;同时它还实现了在目标类中定义的抽象业务逻辑方法(如果有的话)。如果无须扩展目标类,则具体目标类可以省略。
    ● Observer(观察者):观察者将对观察目标的改变做出反应,观察者一般定义为接口,该接口声明了更新数据的方法update(),因此又称为抽象观察者。
    ● ConcrertObserver(具体观察者):在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致;它实现了在抽象观察者Observer中定义的update()方法。
    观察者模式描述了如何建立对象与对象之间的依赖关系,以及如何构造满足这种需求的系统。观察者模式包含观察目标和观察者两类对象,一个目标可以有任意数目的与之相依赖的观察者,一旦观察目标的状态发生改变,所有的观察者都将得到通知。作为对这个通知的响应,每个观察者都将监视观察目标的状态以使其状态与目标状态同步,这种交互也称为发布-订阅(Publish-Subscribe)。观察目标是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅它并接收通知。
    3 观察者模式的优点
    1、观察者模式在被观察者和观察者之间建立一个抽象的耦合。被观察者角色所知道的只是一个具体观察者列表,每一个具体观察者都符合一个抽象观察者的接口。被观察者并不认识任何一个具体观察者,它只知道它们都有一个共同的接口。
    由于被观察者和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。如果被观察者和观察者都被扔到一起,那么这个对象必然跨越抽象化和具体化层次。
    2、观察者模式支持广播通讯。被观察者会向所有的登记过的观察者发出通知,

    4 观察者模式的缺点
    1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
    2、如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃。在使用观察者模式是要特别注意这一点。
    3、如果对观察者的通知是通过另外的线程进行异步投递的话,系统必须保证投递是以自恰的方式进行的。
    4、虽然观察者模式可以随时使观察者知道所观察的对象发生了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎么发生变化的。

    欢迎您关注我的微信公众号:学习微站(studysth)在这里插入图片描述

    展开全文
  •  Observer(观察者)模式又被称作发布-订阅(Publish-Subscribe)模式,是一种对象的行为型模式。《设计模式》一书对Observer是这样描述的:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有...

    1.1.13 Observer观察者模式


      Observer(观察者)模式又被称作发布-订阅(Publish-Subscribe)模式,是一种对象的行为型模式。《设计模式》一书对Observer是这样描述的:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知并自动更新。
      Observer模式的功用,是希望两个(或多个)对象,我们称之为Subject和Observer,当一方的状态发生改变的时候,另一方能够得到通知。也就是说,作为Observer的一方,能够监视到Subject的某个特定的状态变化,并为之做出反应。一个简单的例子就是:当一个用户视图中的数据被用户改变后,后端的数据库能够得到更新,而当数据库被其他方式更新后,用户视图中的数据显示也会随之改变。
      Observer的结构如下:

    图13-1 观察者模式

      上面的类图中包括如下组成部分:
      Subject(抽象主题)角色:主题角色把所有对观察者对象的引用保存在一个列表里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象,主题角色又叫做抽象被观察者(Observable)角色,一般用一个抽象类或者一个接口实现。
      Observer(抽象观察者)角色:为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。这个接口叫做更新接口。抽象观察者角色一般用一个抽象类或者一个接口实现。在这个示意性的实现中,更新接口只包含一个方法(即Update()方法),这个方法叫做更新方法。
      ConcreteSubject(具体主题)角色:将有关状态存入具体现察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者角色(Concrete Observerable)。具体主题角色通常用一个具体子类实现。
      ConcreteObserver(具体观察者)角色:存储与主题的状态自恰的状态。具体现察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。如果需要,具体观察者角色可以保存一个指向具体主题对象的引用。具体观察者角色通常用一个具体子类实现。
    举个例子,在现实生活中,父母与孩子是最亲密的人。父母做为孩子(被观察者)的监护人(观察者),当孩子和别人打架后,一定会告诉他的父母这件事,当孩子获得奖学金后,也一定会告诉他的父母。下面我用Observer实现这个程序,代码如下:

    import java.util.ArrayList;
    
    //ConcreteSubject:被观察者,即ConcreteObservable
    class Children {
    
        static private ArrayList<Observer> obs; //维护一个观察者列表
        static private String state = null;
    
        static {
            obs = new ArrayList<>();
        }
    
        //添加观察者
        public static void attach(Observer o) {
            obs.add(o);
        }
    
        //删除观察者
        public static void detach(Observer o) {
            obs.remove(o);
        }
    
        //被观察对象的状态
        public void setState(String str) {
            state = str;
        }
    
        public String getState() {
            return state;
        }
    
        //状态改变时通知所有观察者
        public void notifyObs() {
            for (Observer o : obs) {
                o.update(this);  //使观察者更新状态
            }
        }
    }
    
    //观察者接口
    interface Observer {
    
        public void update(Children child);
    }
    
    //具体的观察者
    class Parent implements Observer {
    
        @Override
        public void update(Children child) {
            switch (child.getState()) {  //使用Java 7的switch语句对string的判断支持
                case "fight":
                    System.out.println("Parent,他和别人打架了");
                    break;
                case "scholarship":
                    System.out.println("告诉Parent,他得到了奖学金");
                    break;
            }
        }
    }
    
    class Mother implements Observer {
    
        @Override
        public void update(Children child) {
            switch (child.getState()) {  //使用Java 7的switch语句对string的判断支持
                case "fight":
                    System.out.println("告诉Mother,他和别人打架了");
                    break;
                case "scholarship":
                    System.out.println("告诉Mother,他得到了奖学金");
                    break;
            }
        }
    }
    
    //客户端程序
    public class ObserverClient {
    
        public static void main(String[] args) {
            Children child = new Children();
            Observer parent = new Parent();
            Observer mother = new Mother();
            Children.attach(parent);
            Children.attach(mother);
            child.setState("fight");
            child.notifyObs();
            child.setState("scholarship");
            child.notifyObs();
        }
    }
      输出如下:

    Parent,他和别人打架了
    告诉Mother,他和别人打架了
    告诉Parent,他得到了奖学金
    告诉Mother,他得到了奖学金
      里没有写抽象的Subject,直接实现了一个具体的Subject。一般被观察者必须可观察,即必须要维护一个观察者列表,并具有添加删除观察者的操作,可以把这些抽象成一个Subject接口或抽象类。
      在JDK中实际上有一个对Observer模式的简单的实现,就是类java.util.Observerable和接口java.util.Observer。 java.util.Observerable类对应于Subject,而java.util.Observer接口就是对应于Observer了。可见JDK中并没有把这两个部分都设计为接口,而是把Subject设计成类java.util.Observerable,它提供了部分的实现,简化了许多编程的工作。当然,这也减少了一定的灵活性。
      下面列出了Observer和Observerable的函数列表,及其简单的功能说明。
      java.util.Observer:
      public void update(Observeable obs, Object obj)
      java.util.Observer 接口很简单,只定义了这一个方法,狭义的按照Observer模式的说法,Observer应该在这个方法中调用Subject的getXXX()方法来取得最新的状态,而实际上,你可以只是在其中对Subject的某些事件进行响应。这便是Java中的代理事件模型的一个雏形---对事件进行响应。只不过,在Observer模式中将事件特定化为某个状态/数据的改变了。
      Java.util.Observable:
      public void addObserver(Observer obs)
      向Subject注册一个Observer。也就是把这个Observer对象添加到了一个java.util.Observable内部的列表中。在JDK中对于这个列表是简单的通过一个java.util.Vector类来实现的,而实际上,在一些复杂的Observer模式的应用中,需要把这个部分单另出来形成一个Manager类,来管理Subject和Observer之间的映射。这样,Subject和Observer进一步的被解藕,程序也会具有更大的灵活性。
      public void deleteObserver(Observer obs)
      从Subject中删除一个已注册的Observer的引用。
      public void deleteObservers()
      从Subjec中删除所有注册的Observer的引用。
      public int countObservers()
      返回注册在Subject中的Observer个数。
      protected void setChanged()
      设置一个内部的标志以指明这个Ovserver的状态已经发生改变。注意这是一个protected方法,也就是说只能在Observer类和其子类中被调用,而在其它的类中是看不到这个方法的。
      protected void clearChanged()
      清除上叙的内部标志。它在notifyObservers()方法内部被自动的调用,以指明Subject的状态的改变已经传递到Ovserver中了。
      public Boolean hasChanged()
      确定Subject的状态是否发生了改变。
      public void notifyObservers(Object obj)
      它首先检查那个内部的标志,以判断状态是否改变,如果是的话,它会调用注册在Subject中的每个Observer的update()方法。在JDK中这个方法内部是作为synchronized来实现的,也就是如果发生多个线程同时争用一个java.util.Observerable的notifyObservers()方法的话,他们必须按调度的等待着顺序执行。在某些特殊的情况下,这会有一些潜在的问题:可能在等待的过程中,一个刚刚被加入的Observer会被遗漏没有被通知到,而一个刚刚被删除了的Observer会仍然收到它已经不想要了的通知。
      public void notifyObservers()
      等价于调用了notifyObservers(null)。
      因而在Java中应用Observer就很简单了,需要做的是:让需要被观察的Subject对象继承java.util.Observerable,让需要观察的对象实现java.util.Observer接口,然后用java.util.Observerable的addObserver(Observer obj)方法把Observer注册到Subject对象中。这已经完成了大部分的工作了。然后调用java.util.Observerable的notifyObservers(Object arg)等方法,就可以实现Observer模式的机理。我们来看一个简单使用了这个模式的例子。这个例子有三个类:FrameSubject,DataSubject,FrameObject和EntryClass,FrameSubject中用户可以设置被观察的值,然后自动的会在FrameObject中显示出来,DataSubject封装被观察的值,并且充当Observer模式中的Subject。
    import java.util.Observable;
    import javax.swing.JFrame;
    import java.awt.event.*;
    
    //ConcreteSubject的数据部分
    class DataSubject extends Observable {
    
        private int widthInfo;
        private int heightInfo;
    
        public int getWidthInfo() {
            return widthInfo;
        }
    
        public void setWidthInfo(int widthInfo) {
            this.widthInfo = widthInfo;
            //数据改变后,setChanged()必须被调用以通知视图,否则视图的
            //notifyObservers()方法会不起作用
            this.setChanged();
        }
    
        public void setHeightInfo(int heightInfo) {
            this.heightInfo = heightInfo;
            this.setChanged();
        }
    
        public int getHeightInfo() {
            return heightInfo;
        }
    }
    
    //ConcreteSubject的视图部分
    class FrameSubject extends JFrame {
    
        //因为无法使用多重继承(不能同时继承JFrame和Observerable),这儿就只能使用对象组合的方式
        //来引入一个java.util.Observerable对象了。
        DataSubject subject = new DataSubject();
        //......
    
        //这个方法转发添加Observer消息到DateSubject。
        public void registerObserver(java.util.Observer o) { //注册观察者
            subject.addObserver(o);
        }
    
        //数据改变,事件被触发后调用notifyObservers()来通知Observer。
        void jButton1_actionPerformed(ActionEvent e) {
            subject.setWidthInfo(Integer.parseInt(jTextField1.getText()));
            subject.setHeightInfo(Integer.parseInt(jTextField2.getText()));
            subject.notifyObservers();
        }
    
        //......
    }
    
    //ConcreteObserver:要有更新方法,并传入Subject,根据Subject的状态变化来
    //来更新自己的状态
    class FrameObserver extends JFrame implements java.util.Observer {
    
        //观察的数据
        int widthInfo = 0;
        int heightInfo = 0;
        //......
    
        //在update()方法中实现对数据的更新和其它必要的反应
        @Override
        public void update(Observable o, Object arg) {
            DataSubject subject = (DataSubject) o;
            widthInfo = subject.getWidthInfo();
            heightInfo = subject.getHeightInfo();
            jLabel1.setText("The heightInfo from subject is:  ");
            jLabel3.setText(String.valueOf(heightInfo));
            jLabel2.setText("The widthInfo from subject is:  ");
            jLabel4.setText(String.valueOf(widthInfo));
        }
    
        //.......
    }
    
    public class EntryClass {
    
        public static void main(String[] args) {
            //......
            FrameSubject frame1 = new FrameSubject();
            FrameObserver frame2 = new FrameObserver();
            //在Subject中注册Observer,将两者联系在一起
            frame1.registerObserver(frame2);
            //......
            frame1.setVisible(true);
            frame2.setVisible(true);
            //......
        }
    }
      可见当FrameSubject中的数据改变时触发事件通知FrameObserver,使FrameObserver的数据也得到更新。这里真正的ConcreteSubject是数据部分DataSubject,因此由它实现Observable接口,注册观察者的工作委托给视图FrameSubject,然后在内部将注册调用转发给DataSubject。
      注意ConcreteObserver的更新操作中并不知道是哪个ConcreteSubject的状态改变了,它只知道所有的ConcreteSubject都满足Subject接口(例子中的Observable类),因此它只要调用其中的方法,根据获得的状态来改变自己的状态。当ConcreteObserver需要知道是哪个ConcreteSubject的状态被改变时,就要保存这个ConcreteSubject的引用,可通过组合的方式包含进来。
      JDK中这个Observer模式的实现,对于一般的Observer模式的应用,已经是非常的足够了的。但是一方面它用一个类来实现了Subject,另一方面它使用Vector来保存Subject对于Observer的引用,这虽然简化了编程的过程,但会限制它在一些需要更为灵活,复杂的设计中的应用,有时候(虽然这种情况不多),我们还不得不重新编写新的Subject对象和额外的Manager对象来实现更为复杂的Observer模式的应用。
      在C++方面,ATL则提供了IConnectionPointContainer、IConnectionPoint、IEnumConnectionPoints、IEnumConnections等以支持所谓的连接点及可连接对象等,而在MFC中,一个文档可以对应多个视图,而当文档发生更新后,可以通过UpdateAllViews来同步更新所有视图,这虽然并非严格意义上的Observer模式的应用,但其本质是相似的。
      另外,聊天室软件实现就可以使用Observer模式。服务器端ChatRoom相当于Subject,客户端各个Chater相当于Observer。当ChatRoom接收到消息时(这里引起ChatRoom发生变化的是各个Chater本身),有两种通知方式,一种是不对消息的内容进行解析,而是直接通过Notify通知所有Chater,由所有Chater自己负责检查该消息是否是发送给自己的,这在一定程度上与广播非常相似,所以有时候, 我们可以采用Observer模式来模拟软件广播。为避免广播风暴,实际的ChatRoom通常使用另一种方式,结合使用Mediator模式和Observer模式,将Subject设计成一个Mediator,对消息内容进行检查,进而根据消息内容进行消息的Publish。
      实现方式:具体被观察者实现Observable接口、维护一个观察者列表、并具有注册和反注册观察者、通知所有观察者的操作。具体观察者实现Observer接口,在更新方法中传入观察者,根据观察者的状态变化更新自己的状态。

      在以下任一情况下可以使用观察者模式:
      1、当一个抽象模型有两个方面,其中一个方面依赖于另一方面,将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
      2、当对一个对象的改变需要同时改变其它对象,而不知道具体有多少对象有待改变。
      3、当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之,你不希望这些对象是紧密耦合的。
      Observer模式实现了表示层和数据逻辑层的分离,允许你独立的改变目标和观察者。你可以单独复用目标对象而无需同时复用其观察者,反之亦然。它也使你可以在不改动目标和其他的观察者的前提下增加观察者。
      下面是观察者模式其它一些优缺点:
      1、目标和观察者间的抽象和松耦合。
    一个目标所知道的仅仅是它有一系列观察者,每个都符合抽象的Observer类的简单接口。目标不知道任何一个观察者属于哪一个具体的类。这样目标和观察者之间的耦合是抽象的和最小的。
      因为目标和观察者不是紧密耦合的,它们可以属于一个系统中的不同抽象层次。一个处于较低层次的目标对象可与一个处于较高层次的观察者通信并通知它,这样就保持了系统层次的完整。如果目标和观察者混在一块,那么得到的对象要么横贯两个层次,违反了层次性;要么必须放在这两层的某一层中,这可能会损害层次抽象。
      2、支持广播通信。不像通常的请求,目标发送的通知不需指定它的接收者。通知被自动广播给所有已向该目标对象登记的有关对象。目标对象并不关心到底有多少对象对自己感兴趣。它唯一的责任就是通知它的各观察者。这给了你在任何时刻增加和删除观察者的自由。处理还是忽略一个通知取决于观察者。
      3、意外的更新。因为一个观察者并不知道其它观察者的存在,它可能对改变目标的最终代价一无所知。在目标上一个看似无害的的操作可能会引起一系列对观察者以及依赖于这些观察者的那些对象的更新。此外,如果依赖准则的定义或维护不当,常常会引起错误的更新,这种错误通常很难捕捉。
      简单的更新协议不提供具体细节说明目标中什么被改变了,这就使得上述问题更加严重。如果没有其他协议帮助观察者发现什么发生了改变,它们可能会被迫尽力减少改变。


    1.1.14 Command命令模式


      Command模式定义:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。
      Commad模式是一种对象行为模式,它可以对请求发送者(sender)和接收者(receiver)完全解耦(decoupling)。"发送者" 是请求操作的对象,"接收者" 是接收请求并执行某操作的对象。有了 "解耦",请求者对接收者的接口一无所知。这里,"请求"(request)这个术语指的是要被执行的命令。Command模式还让我们可以对 "何时" 以及 "如何" 完成请求进行改变。因此,Command模式为我们提供了灵活性和可扩展性。
      Command模式的结构图如下:

    图14-1 Command模式类图

      上图中包括如下角色:
      客户(Client)角色:创建了一个具体命令(ConcreteCommand)对象并确定其接收者。
      命令(Command)角色:声明了一个给所有具体命令类的抽象接口。这是一个抽象角色。
      具体命令(ConcreteCommand)角色:定义一个接收者和行为之间的弱耦合;实现execute()方法,负责调用接收者的相应操作。execute()方法通常叫做执行方法。
      请求者(Invoker)角色:负责调用命令对象执行请求,相关的方法叫做行动方法。
      接收者(Receiver)角色:负责具体实施和执行一个请求。任何一个类都可以成为接收者,实施和执行请求的方法叫做行动方法。
      从Command模式的结构似乎可以看出几分Adapter模式的影子,确实如此,Invoker(源)通过Command(Adapter)对Receiver(Adptee)来执行Receiver::Action(),但Command模式的意义不在于此,Command模式的目的在于通过将需要操作的对象及所执行的操作封装成一个独立的对象,而不在于简单的接口的转换;此外,二者还有一个显著的区别在于Adaptee对client往往是不可见的,而Receiver对Client往往是可见的。Client只知道Adapter::Request(),而不知(或无需知道)其内部其实调的是Adaptee::SpecialRequest(),而在Command模式中Client往往需要显式地把一个Receiver对象传给一个ConcreteCommand对象,以使其能调用Receiver::Action()。
      下面例子模拟文档的显示、撤消和重做命令:

    class Document {  //Receiver:请求接收者,负责执行各命令
    
        public void display() {  //显示命令
            System.out.println("显示文档内容");
        }
    
        public void undo() {  //撤消命令
            System.out.println("撤销文档内容");
        }
    
        public void redo() {
            System.out.println("重做文档内容");
        }
    }
    
    interface DocumentCommand {  //Command:抽象的接口,对多种命令进行抽象
    
        public void execute();
    }
    
    class DisplayCommand implements DocumentCommand { //具体命令,要聚集一个命令接收对象,
                                                        //并在执行方法中将命令执行转发给接收者
        private Document document;
    
        public DisplayCommand(Document doc) {
            document = doc;
        }
    
        @Override
        public void execute() {
            document.display();
        }
    }
    
    class UndoCommand implements DocumentCommand {  //具体命令
    
        private Document document;
    
        public UndoCommand(Document doc) {
            document = doc;
        }
    
        @Override
        public void execute() {
            document.undo();
        }
    }
    
    class RedoCommand implements DocumentCommand {  //具体命令
    
        private Document document;
    
        public RedoCommand(Document doc) {
            document = doc;
        }
    
        @Override
        public void execute() {
            document.redo();
        }
    }
    
    class DocumentInvoker { //Invoker:命令请求者
    
        private DisplayCommand _dcmd;
        private UndoCommand _ucmd;
        private RedoCommand _rcmd;
    
        public DocumentInvoker(DisplayCommand dcmd, UndoCommand ucmd, RedoCommand rcmd) {
            this._dcmd = dcmd;
            this._ucmd = ucmd;
            this._rcmd = rcmd;
        }
    
        public void display() { //调用命令对象执行请求,即把命令执行转发给命令对象
            _dcmd.execute();
        }
    
        public void undo() {
            _ucmd.execute();
        }
    
        public void redo() {
            _rcmd.execute();
        }
        /* 要对请求进行排队、调度时可用下面的方式实现
         private java.util.ArrayList<Command> comList=new java.util.ArrayList<Command>();
        
         public void addCommand(Command com){
            comList.add(com);
         }
         public void schedulePerform(){
            for(Command co:comList){
                co.execute();
            }
         }
         */
    }
    
    public class CommandTest {
    
        public static void main(String[] args) {
            Document doc = new Document();  //客户端要把接收者对象传给具体命令对象
            DisplayCommand display = new DisplayCommand(doc);
            UndoCommand undo = new UndoCommand(doc);
            RedoCommand redo = new RedoCommand(doc);
            DocumentInvoker invoker = new DocumentInvoker(display, undo, redo);
            invoker.display();
            invoker.undo();
            invoker.redo();
        }
    }
      回味一下, 基本思想是把一个请求/命令本身封装成一个对象,并有一个请求者和一个或多个接收者,真正的命令函数代码在接收者中实现,每个命令写一个命令类,它要引入某个接收者并有一个执行函数,真正的执行动作转发给接收者完成。请求者一般会维护一个命令对象队列以便对请求进行排队(当命令很多时),然后调用各命令对象执行请求。注意任何一个类都可以成为接收者。
      实际上,对于请求的处理有两种不同的方法,一种是Command只充当Proxy,将请求转发给某个接受者对象(如上面的例子),还有一种是Command对象自己处理完所有的请求操作。当然,这只是两个极端,更多的情况是Command完成一部分的工作,而另外的一部分这则交给接受者对象来处理。
      当Command对象自己完成请求操作时,一个Command对象中包含了待执行的一个动作语句序列,以执行特定的任务(当然并不是随便怎么样的语句序列都可以构成一个Command对象的),按照Command模式的设计,Command对象和它的调用者Invoker之间应该具有接口约定的(如上面例子中都有undo, redo,display,这时可以抽象出一个接口来)。也就是说,Invoker得到Command对象的引用,并调用其中定义好的方法,而当Command对象改变(或者是对象本身代码改变,或者干脆完全另外的一个Command对象)之后,Invoker中的代码可以不用更改。这样,通过封装请求,可以把任务和任务的实现加以分离。
      在新的JDK的代理事件模型中,一个事件监听者类EventListener监听某个事件,并根据接口定义,实现特定的操作。比如,当用Document对象的addDocumentListener(DocumentListener listener) 方法注册了一个DocumentListener后,以后如果在Document对象中发生文本插入的事件,DocumentListener中实现的insertUpdate(DocumentEvent e)方法就会被调用,如果发生文本删除事件,removeUpdate(DocumentEvent e)方法就会被调用。如果把Document理解成被观察者,各DocumentListener理解成观察者,则这是Observer模式,但如果把Document理解成Invoker,各DocumentListener理解成Command,则这也是一个Command模式。
      最经典的Command模式的应用,莫过于Swing中的Action接口。Action实际上继承的是ActionListener,也就是说,它也是一个事件监听者(EventListener)。但是Action作为一种ActionListener的扩展机制,提供了更多的功能。它可以在其中包含对这个Action动作的一个或者多个文字的或图标的描叙,它提供了Enable/Disable的功能许可性标志。并且,一个Action对象可以被多个Invoker,比如实现相同功能的按钮,菜单,快捷方式所共享。而这些Invoker都知道如何加入一个Action,并充分利用它所提供的扩展机制。可以说,在这儿Action更像一个对象了,因为它不仅仅提供了对方法的实现,更提供了对方法的描叙和控制。可以方便的描叙任何的事务,这更是面向对象方法的威力所在。
      JDK的AbstractUndoableEdit为我们提供了基本的Undo/Redo支持,当我们需要为Java应用引入Undo/Redo操作时,只需简单地从AbstractUndoableEdit类派生子类将操作封装成类即可。下面是一个简单的封装adjust操作的例子(代码取自XModeler,Java Code):

    public  class SelectTool extends AbstractTool {
        //...
        public  void mouseDragged(MouseEvent e) {
            //...
            if (!isDragRegistered) {
                desk.addUndoableEdit(new AdjustUndoableEdit(desk, (ModelObject) oldC));
                isDragRegistered = true;
            }
        }
    }
    
    public  class MainFrame extends JFrame implements PropertyChangeListener{                                                                                     
        //Invoker,the Main Window 
        //...
        protected void commandRedo() {
            // Find active child window
            JInternalFrame internalframe = this.getActiveChild();
            // notify the active view to execute the command
            if (internalframe != null && internalframe instanceof ChildFrame) {
                ChildFrame child = (ChildFrame) internalframe;
                ModelView view = child.getView();
                view.redo();
            }
        }
    }
    
    public  class AdjustUndoableEdit extends AbstractUndoableEdit { //ConcreteCommand
        ModelObject com;    // state
        Desktop desk;       // Receiver, the Active View
        Rectangle oldRec;
        
        public AdjustUndoableEdit(Desktop desk, ModelObject com) {
            this.com = com;
            this.desk = desk;
            oldRec = getBounds();
        }
        
        Rectangle getBounds() {
            Rectangle rec = ( (Component) com).getBounds();
            return rec;
        }
        
        public  String getUndoPresentationName() {
            return "Undo_Adjust";
        }
        
        public  String getRedoPresentationName() {
            return "Redo_Adjust";
        }
        
        public  void undo() throws CannotUndoException {    // Execute 1
            super.undo();
            Rectangle newRec = getBounds();
            ((Component)com).setBounds(oldRec);
            oldRec = newRec;
            desk.fireAssociatorChanged();
        }
        
        public  void redo() throws CannotRedoException {    // Execute 2
            super.redo();
            Rectangle newRec = getBounds();
            ((Component)com).setBounds(oldRec);
            oldRec = newRec;
            desk.fireAssociatorChanged();
        }
    }
      上面的代码片断,虽然不是一个完整的Command模式的应用,但其中已经可以十分清晰地看到各个Role,以及他们之间的协作关系。
      又如,要实现这样的一个任务:Task Schedule。也就是说,我想对多个任务进行安排,比如扫描磁盘,我希望它每1个小时进行一次,而备份数据,我希望它半个小时进行一次,等等。但是,我并不希望作为TaskSchedule的类知道各个任务的细节内容,TaskSchedule应该只是知道Task本身,而对具体的实现任务的细节并不理会。因而在这儿,我们就需要对TaskSchedule和Task进行解耦,将任务和具体的实现分离出来,这不正是Command模式的用武之地吗?
      在设计一般用途的软件的时候,在C或者C++语言中,用的很多的一个技巧就是回调函数(Callback),所谓的回调函数,意指先在系统的某个地方对函数进行注册,让系统知道这个函数的存在,然后在以后,当某个事件发生时,再调用这个函数对事件进行响应。在C或者C++中,实现的回调函数方法是使用函数指针。但是 在Java中,并不支持指针,而Command模式则可作为这一回调机制的面向对象版本。Command中的各命令动作函数相当于回调函数,先把各Command对象注册到Invoker中,以后当Invoker触发某个请求时,这些Command对象中的函数就会被调用。
       在下面的情况下应当考虑使用命令模式:
      1、使用命令模式作为"CallBack"在面向对象系统中的替代。
    "CallBack"讲的便是先将一个函数登记上,然后在以后调用此函数。
       2、需要在不同的时间指定请求、将请求排队。一个命令对象和原先的请求发出者可以有不同的生命期。换言之,原先的请求发出者可能已经不在了,而命令对象本身仍然是活动的。这时命令的接收者可以是在本地,也可以在网络的另外一个地址。命令对象可以在串形化之后传送到另外一台机器上去。
       3、系统需要支持命令的撤消(undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo()方法,把命令所产生的效果撤销掉。命令对象还可以提供redo()方法,以供客户端在需要时,再重新实施命令效果。
      4、如果一个系统要将系统中所有的数据更新到日志里,以便在系统崩溃时,可以根据日志里读回所有的数据更新命令,重新调用execute()方法一条一条执行这些命令,从而恢复系统在崩溃前所做的数据更新。
       5、一个系统需要支持交易(Transaction)。一个交易结构封装了一组数据更新命令,使用命令模式来实现交易结构可以使系统增加新的交易类型。
      Command模式允许请求的一方和接收请求的一方能够独立演化, 从而且有以下的优点:
      Command模式使新的命令很容易地被加入到系统里。
      允许接收请求的一方决定是否要否决(Veto)请求。
      能较容易地设计一个命令队列。
      可以容易地实现对请求的Undo和Redo。
      在需要的情况下,可以较容易地将命令记入日志。
      Command模式把请求一个操作的对象与知道怎么执行一个操作的对象分割开。
      Command类与其他任何别的类一样,可以修改和推广。
      你可以把Command对象聚合在一起,合成为Composite Command。比如宏Command便是Composite Command的例子。
      由于加进新的具体命令类不影响其他的类,因此增加新的具体命令类很容易。
       Command模式的缺点如下:

      使用Command模式会导致某些系统有过多的具体Command类。某些系统可能需要几十个,几百个甚至几千个具体Command类,这会使Command模式在这样的系统里变得不实际。


    1.1.15 Chain of Responsibility责任链模式


      Chain of Responsibility模式定义: 为了避免请求的发送者和接收者之间的耦合关系,使多个接受对象都有机会处理请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
      在不止一个对象可以处理客户端请求的时候,为了使每个对象都有处理请求的机会,把这些对象顺序地串联起来形成一个链,每个被串联的对象都有一个指向下一个对象的指针,当请求到来是,按照顺序,先有第一个对象来处理这个请求,这个对象有两个选择:要么处理,要么把请求传给下一个对象(每个对象都有这两个选择),就这样一直到有一个对象来处理这个请求为止,一旦有一个对象处理了这个请求就停止对请求的传递。当然也有可能到了对象链的最后,也没有一个对象来处理请求。我觉得这个与我们平常写的if…else if…else…要完成的功能太相似了。
      以上所说的只是Chain of Responsibility的一种情况,有的书上叫它纯责任链模式,也就是规定一个具体处理者角色只能对请求作出两种动作:自己处理、传给下家。不能出现处理了一部分,把剩下的传给了下家的情况,而且请求在责任链中必须被处理,而不能出现无果而终的结局。另一种情况也就是非纯责任链模式,把能处理的部分处理掉,处理不了的部分再转发给下一处理者。
      对于Chain中的各个对象,可以采用类似单向链表或双向链表的结构,保存各自后继或者前接元素的引用/指针来实现链接(紧密链接),也可以仅仅由各对象的Container保存这种逻辑上的链接关系,而各对象彼此间并不需要知晓Chain中的其它对象(松散链接)。对于Chain的结构,一个链可以是一条线,一个树(普通的树,或者平衡树、红黑树等),也可以是一个环,在使用中可以根据需要选择。
      以下是紧密链接CoR模式的典型结构:

    图16-1 CoR模式类图示意

      上述类图中包括如下角色:
      抽象处理者(Handler)角色:定义出一个处理请求的接口。如果需要,接口可以定义出一个方法,以设定和返回对下家的引用。这个角色通常由一个抽象类或接口实现。
      具体处理者(ConcreteHandler)角色:具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家。由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问下家。
      先举一个纯责任链模式的例子。现在的女孩子找男朋友基本上都有三个要求:有车、有房、有责任心,如果你这三样都没有,就险了。代码如下:

    class Boy {  //请求的接收者:完成对请求的响应
    
        private boolean hasCar; //是否有车
        private boolean hasHouse; //是否有房
        private boolean hasResponsibility; //是否有责任心
    
        public Boy() {
        }
    
        public Boy(boolean hasCar, boolean hasHouse, boolean hasResponsibility) {
            this.hasCar = hasCar;
            this.hasHouse = hasHouse;
            this.hasResponsibility = hasResponsibility;
        }
    
        public boolean isHasCar() {
            return hasCar;
        }
    
        public void setHasCar(boolean hasCar) {
            this.hasCar = hasCar;
        }
    
        public boolean isHasHouse() {
            return hasHouse;
        }
    
        public void setHasHouse(boolean hasHouse) {
            this.hasHouse = hasHouse;
        }
    
        public boolean isHasResponsibility() {
            return hasResponsibility;
        }
    
        public void setHasResponsibility(boolean hasResponsibility) {
            this.hasResponsibility = hasResponsibility;
        }
    }
    
    interface Handler {  //抽象处理者: 也可以多定义出一个方法,以设定和返回对下一个处理者的引用
    
        public void handleRequest(Boy boy);
    }
    
    class CarHandler implements Handler {  //具体处理者1
    
        private Handler handler; //下一处理者的引用
    
        public CarHandler(Handler handler) {  //由构造函数传入下一处理者
            this.handler = handler;
        }
    
        public Handler getHandler() {
            return handler;
        }
    
        public void setHandler(Handler handler) {  //也可用setter方法传入下一处理者
            this.handler = handler;
        }
    
        @Override
        public void handleRequest(Boy boy) {
            //本对象不能处理任何请求,直接把请求传递给下一处理者
            handler.handleRequest(boy);
        }
    }
    
    class HouseHandler implements Handler {  //具体处理者2
    
        private Handler handler;
    
        public HouseHandler(Handler handler) {
            this.handler = handler;
        }
    
        public Handler getHandler() {
            return handler;
        }
    
        public void setHandler(Handler handler) {
            this.handler = handler;
        }
    
        @Override
        public void handleRequest(Boy boy) {
            //本处理器能处理所有请求,处理完后终止,不传给下一处理者 
            if (boy.isHasCar()) {
                System.out.println("呵呵,我有辆车");
            } else {
                System.out.println("我也没有房");
            }
            if (boy.isHasHouse()) {
                System.out.println("没想到吧,我还有房子");
            } else {
                System.out.println("我也没有房");
            }
            if (boy.isHasResponsibility()) {
                System.out.println("同时我也非常有责任心");
            } else {
                System.out.println("更没有责任心");
            }
        }
    }
    
    class ResponsibilityHandler implements Handler {  //具体处理器3
    
        private Handler handler;
    
        public ResponsibilityHandler(Handler handler) {
            this.handler = handler;
        }
    
        public Handler getHandler() {
            return handler;
        }
    
        public void setHandler(Handler handler) {
            this.handler = handler;
        }
    
        @Override
        public void handleRequest(Boy boy) {
            //本处理器能处理所有请求,处理完后终止,不传给下一处理者 
            if (boy.isHasCar()) {
                System.out.println("呵呵,我有辆车");
            } else {
                System.out.println("我也没有房");
            }
            if (boy.isHasHouse()) {
                System.out.println("没想到吧,我还有房子");
            } else {
                System.out.println("我也没有房");
            }
            if (boy.isHasResponsibility()) {
                System.out.println("同时我也非常有责任心");
            } else {
                System.out.println("更没有责任心");
            }
        }
    }
    
    class GirlForTest {  //请求发起者
    
        public static void main(String[] args) {
            //这个boy有车,也有房,也很有责任心
            Boy boy = new Boy(true, true, true);
            //也可以使用setHanlder方法
            Handler handler = new CarHandler(new HouseHandler(new ResponsibilityHandler(null)));
            handler.handleRequest(boy);
        }
    }
      可以把上面的例子改为不纯的责任链模式。为了丰富性,这里用另外一个非纯责任链的例子。这个例子模拟了汽车组装的过程:假设一辆汽车从生产到出厂要经过以下四个过程:组装车头,车身,车尾,以及上色。具体处理者按它能处理的功能来命名。代码如下:
    abstract class CarHandler { //抽象处理者:这里定义了一个设定和返回对下家的引用的方法
    
        public static final int STEP_HANDLE_HEAD = 0;
        public static final int STEP_HANDLE_BODY = 1;
        public static final int STEP_HANDLE_TAIL = 2;
        public static final int STEP_HANDLE_COLOR = 3;
        protected CarHandler carHandler;
    
        public CarHandler setNextCarHandler(CarHandler carHandler) {
            this.carHandler = carHandler;
            return this.carHandler;
        }
    
        abstract public void handleCar(int lastStep);
    }
    
    class CarHeadHandler extends CarHandler { //具体处理者:组装车头
    
        @Override
        public void handleCar(int lastStep) {
            if (STEP_HANDLE_HEAD <= lastStep) { //处理能处理的部分:组装车头
                System.out.println("Handle car's head.");
            }
            if (carHandler != null) {
                carHandler.handleCar(lastStep); //不能处理的部分传递给下一处理者
            }
        }
    }
    
    class CarBodyHandler extends CarHandler {
    
        @Override
        public void handleCar(int lastStep) {
            if (STEP_HANDLE_BODY <= lastStep) {
                System.out.println("Handle car's body.");
            }
            if (carHandler != null) {
                carHandler.handleCar(lastStep);
            }
        }
    }
    
    class CarTailHandler extends CarHandler {
    
        @Override
        public void handleCar(int lastStep) {
            if (STEP_HANDLE_TAIL <= lastStep) {
                System.out.println("Handle car's tail.");
            }
            if (carHandler != null) {
                carHandler.handleCar(lastStep);
            }
        }
    }
    
    class CarColorHandler extends CarHandler {
    
        @Override
        public void handleCar(int lastStep) {
            if (STEP_HANDLE_COLOR == lastStep) {
                System.out.println("Handle car's color.");
            }
            if (carHandler != null) {
                carHandler.handleCar(lastStep);
            }
        }
    }
    
    public class CarClient {
    
        public static void main(String[] args) {
                    //工作流程1:先组装车头,然后是车身,车尾,最后是上色
            System.out.println("---workfolow1----");
            CarHandler carHandler1 = new CarHeadHandler();
            carHandler1.setNextCarHandler(
                    new CarBodyHandler()
            ).setNextCarHandler(
                    new CarTailHandler()
            ).setNextCarHandler(
                    new CarColorHandler()
            );
            carHandler1.handleCar(CarHandler.STEP_HANDLE_COLOR);
            //工作流程2:因为某种原因,我们需要先组装车尾,然后是车身,车头,最后是上色
            System.out.println(
                    "---workfolow2---");
            CarHandler carHandler2 = new CarTailHandler();
            carHandler2.setNextCarHandler(
                    new CarBodyHandler()).setNextCarHandler(
                            new CarHeadHandler()).setNextCarHandler(
                            new CarColorHandler());
            carHandler2.handleCar(CarHandler.STEP_HANDLE_COLOR);
        }
    }
      大部分的实际应用中使用不纯的责任链模式,即每个对象都要处理部分请求。纯的责任链用的较少,因为如果一个对象不能处理任何请求,那它的存在就没有意义了。如果坚持责任链不纯便不是责任链模式,那么责任链模式便不会有太大的意义了。
      MFC中的消息处理机制也是典型的CoR模式,它采用的是前面所说的松散链接的线状Chain。虽然采用CoR进行消息处理的机制在MFC中根深蒂固地存在着(MFC采用消息映射表屏蔽了内部的复杂处理,DefWindowProc等辅助机制更进一步让整个消息处理显得自然而简单),但 采用CoR进行消息处理在现代软件设计中往往被认为是低效的。MFC,Java AWT 1.0都是这样的例子。
      Java的1.0版中AWT库使用了责任链模式和命令模式来处理GUI的事件。由于视窗部件往往处在容器部件里面,因此当事件发生在一个部件上时,此部件的事件处理器可以处理此事件,然后决定是否将事件向上级容器部件传播;上级容器部件接到事件后可以在此处理此事件然后决定是否将事件再次向上级容器部件传播,如此往复,直到事件到达顶层部件。一般责任链模式要求链上所有的对象都继承自一个共同的父类,在AWT中这个类便是java.awt.Component类。AWT1.0的事件处理的模型是不纯的责任链模式。显然,由于每一级的部件在接到事件时,都可以处理此事件;而不论此事件是否在这一级得到处理,事件都可以停止向上传播或者继续向上传播。这是典型的不纯的责任链模式。
    比如,当一个视窗部件接到一个MOUSE_CLICKED事件时,事件首先传播到它所发生的部件上,然后向其容器部件传播。容器可以选择处理这个事件,或者再将此事件向更高一级的容器部件传播。事件如此一级级地向上传播,就像水底的气泡一点一点地冒到水面上一样,因此又叫做事件浮升(Event Bubbling)机制。下面就是一段典型的Java1.0版的AWT库里处理事件的代码:
    public boolean action(Event event, Object obj) {        
            if (event.target == btnOK) {            
                doOKBtnAction();            
            } else if (event.target == btnExit) {            
                doExitBtnAction();            
            } else {            
                return super.action(event, obj);            
            }        
            return true;
        }
      在这段代码里面,action()判断目标部件是不是btnOK或btnExit;如果是,便运行相应的方法;如果不是,便返还true。一个方法返还true便使得事件停止浮升。
    又如下面的代码:
    import java.applet.Applet;
    import java.awt.*;
    public class MouseSensor extends Frame {
    
        public static void main(String[] args) {
            MouseSensor ms = new MouseSensor();
            ms.setBounds(10, 10, 200, 200);
            ms.show();
        }
    
        public MouseSensor() {
            setLayout(new BorderLayout());
            add(new MouseSensorCanvas(), "Center");
        }
    }
    
    class MouseSensorCanvas extends Canvas {
    
        public boolean mouseUp(Event event, int x, int y) {
            System.out.println("mouse up");
            return true; // Event has been handled. Do not propagate to container.
        }
    
        public boolean mouseDown(Event event, int x, int y) {
            System.out.println("mouse down");
            return true; // Event has been handled. Do not propagate to container.
        }
    }

      AWT1.0的事件处理的模型的缺点:
      (1)AWT1.0的事件处理的模型是基于继承的。如为了给按钮添加鼠标移动处理,必须通过派生新的按钮子类来完成,也就是置换掉action()方法或者handleEvent()方法。这不是应当提倡的做法:在一个面向对象的系统里,经常使用的应当是委派,继承不应当是常态。
      在一个复杂的GUI系统里,这样为所有有事件的部件提供子类,会导致很多的子类,这是不是很麻烦的吗?当然,由于事件浮升机制,可以在部件的树结构的根部部件里面处理所有的事件。但是这样一来,就需要使用复杂的条件转移语句在这个根部部件里辨别事件的起源和处理方法。这种非常过程化的处理方法很难维护,并且与面向对象的设计思想相违背。
      (2)由于每一个事件都会沿着部件树结构向上传播,因此事件浮升机制会使得事件的处理变得较慢。这也是缺点之一。
      比如在有些操作系统中,鼠标每移动一个色素,都会激发一个MOUSE_MOVE事件。每一个这样的事件都会沿着部件的容器树结构向上传播,这会使得鼠标事件成灾。
      (3)AWT1.0的事件处理的模型只适用于AWT部件类。这是此模型的另一个缺点。
      (4)消息处理与其它类的方法混杂在类的实现中,这种方式有失灵活性,不便于消息处理的动态添加、删除(虽然可以通过添加flag来封闭消息处理逻辑,但相信没有人会认为这是一种好办法),不便于管理。
      基于以上诸多原因,Java设计者为了维持语言本身的简单性,坚决地淘汰了AWT中旧的基于CoR的消息处理机制,在引入新的界面方案javax.swing的同时,引入了新的基于Observer模式的消息处理机制。
      以下是新的基于Observer模式的消息处理:

    import java.awt.*;
    import java.awt.event.*;
    public class MouseSensor extends Frame {
    
        public static void main(String[] args) {
            MouseSensor ms = new MouseSensor();
            ms.setBounds(10, 10, 200, 200);
            ms.show();
        }
    
        public MouseSensor() {
            Canvas canvas = new Canvas();
            canvas.addMouseListener(new MouseAdapter() {
                public void mousePressed(MouseEvent e) {
                    System.out.println("mouse down");
                }
                public void mouseReleased(MouseEvent e) {
                    System.out.println("mouse up");
                }
            });
            setLayout(new BorderLayout());
            add(canvas, "Center");
        }
    }
      与基于CoR的消息处理相比,基于Observer的消息处理由于将消息处理交给了单独的Observer来处理,使得程序结构更清晰,而且高效(消息直接被对应的Observer处理,无需轮询)。
      但这不表示基于CoR的消息处理没有存在的价值,基于CoR的消息处理可以极大程度上降低消息的发送者与接收者之间的耦合程度,同时,在有些情况下,Observer模式并不能替代CoR模式进行消息处理,如:
      1、需要消息被多个接收者依次处理时,并且消息可能在处理的过程中被修改或者处理顺序为我们所关注时;
      2、当消息没有经过严格分类时,应用Observer模式会变得比较困难。
      总结:
       在以下情况下可以考虑使用CoR模式:
      1、有多个的对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定。
      2、你想在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
      3、可处理一个请求的对象集合应被动态指定。
       优缺点:
      CoR模式使得发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织链和分配责任。从而可以很大程度地降低处理请求与处理对象,以及处理对象之间的耦合关系。
      需要注意的时,CoR模式并不创建Responsibility Chain,Responsibility Chain的创建必须由系统的其它部分创建出来,即需要上层应用对Chain进行维护,同时,需要注意的是,在大多数情况下,Chain的构建应该遵循一定的规则,如由主到次,由特殊到普通(就好像在if..else if...else中,将较General的限制条件放在前面,可能使得较Rigorous的限制条件永远得不到执行)。

    1.1.16 Interpreter解释器模式


      GOF定义:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。Interpreter是行为模式之一,它是一种特殊的设计模式,它建立一个解释器,对于特定的计算机程序设计语言,用来解释预先定义的文法。Interpreter描述了一个语言解释器是如何构成的,在实际应用中,我们可能很少去构造一个语言的文法。
      Interpreter(解释器)模式描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子。在这里使用语言这个词似乎将Interpreter模式的应用范围限制到了一个过于狭小的范围,毕竟,我们不是自然语言或者编程语言设计者,需要注意的是,这里所讨论的语言并非指复杂的自然语言或者编程语言,而是一种语义标记,Interpreter模式负责实现这种标记的定义以及将其转换为实际功能。
      Interpreter模式的结构图如下:


    图16-1 Interpreter模式类图示意

      1、抽象表达式角色:声明一个抽象的解释操作,这个接口为所有具体表达式角色(抽象语法树中的节点)都要实现的。
      什么叫做抽象语法树呢?《java与模式》中给的解释为:抽象语法树的每一个节点都代表一个语句,而在每个节点上都可以执行解释方法。这个解释方法的执行就代表这个语句被解释。由于每一个语句都代表一个常见的问题的实例,因此每一个节点上的解释操作都代表对一个问题实例的解答。
      2、终结符表达式角色:具体表达式。实现与文法中的终结符相关联的解释操作。句子中的每个终结符需要该类的一个实例与之对应。
      3、非终结符表达式角色:具体表达式。文法中的每条规则R=R1R2…Rn都需要一个非终结符表达式角色。对于从R1到Rn的每个符号都维护一个抽象表达式角色的实例变量。实现解释操作,解释一般要递归地调用表示从R1到Rn的那些对象的解释操作。
      4、上下文(环境)角色:包含解释器之外的一些全局信息。解释器上下文环境类,用来存储解释器的上下文环境,比如需要解释的文法等。
      5、客户角色:构建(或者被给定)表示该文法定义的语言中的一个特定的句子的抽象语法树,调用解释操作。
      举一个加减乘除运算解释器例子,实现思路来自于《java与模式》中的例子。代码如下:

    import java.util.HashMap;
    import java.util.Map;
    
    class Context {  //上下文环境
    
        private Map valueMap = new HashMap();
    
        //一个变量对应一个值,相当于文法的一个推导规则
        public void addValue(Variable x, int y) {
            Integer yi = new Integer(y);
            valueMap.put(x, yi);
        }
    
        //从左边的变量(即非终结符),返回右边的终结符
        public int LookupValue(Variable x) {
            int i = ((Integer) valueMap.get(x)).intValue();
            return i;
        }
    }
    
    //AbstractExpression:抽象表达式,也可以用接口来实现
    abstract class Expression {
    
        public abstract int interpret(Context con); //声明一个抽象的解释操作
    }
    
    //TerminalExpression:终结符表达式
    //实现与文法中的终结符相关联的解释操作。句子中的每个
    //终结符需要该类的一个实例与之对应
    class Constant extends Expression {
    
        private int i;
    
        public Constant(int i) {
            this.i = i;
        }
    
        @Override
        public int interpret(Context con) { //用文法解释终结符:直接返回此终结符
            return i;
        }
    }
    
    //单个非终结符(变量)的表达式,文法每条规则左边的非终结符需要该类的一个实例
    class Variable extends Expression {
    
        @Override
        public int interpret(Context con) {
            //this为调用interpret方法的Variable对象
            return con.LookupValue(this);
        }
    }
    
    //NonterminalExpression:非终结符表达式R1+R2,有多个非终结符
    class Add extends Expression {
    
        private Expression left, right;
    
        public Add(Expression left, Expression right) {
            this.left = left;
            this.right = right;
        }
    
        @Override
        public int interpret(Context con) { //用文法来解释非终结符表达R1+R2
            //一般要递归地调用表示从R1到Rn的那些对象的解释操作        
            return left.interpret(con) + right.interpret(con);
        }
    }
    
    class Subtract extends Expression {
    
        private Expression left, right;
    
        public Subtract(Expression left, Expression right) {
            this.left = left;
            this.right = right;
        }
    
        @Override
        public int interpret(Context con) {
            return left.interpret(con) - right.interpret(con);
        }
    }
    
    class Multiply extends Expression {
    
        private Expression left, right;
    
        public Multiply(Expression left, Expression right) {
            this.left = left;
            this.right = right;
        }
    
        @Override
        public int interpret(Context con) {
            return left.interpret(con) * right.interpret(con);
        }
    }
    
    class Division extends Expression {
    
        private Expression left, right;
    
        public Division(Expression left, Expression right) {
            this.left = left;
            this.right = right;
        }
    
        @Override
        public int interpret(Context con) {
            try {
                return left.interpret(con) / right.interpret(con);
            } catch (ArithmeticException ae) {
                System.out.println("被除数为0!");
                return -11111;
            }
        }
    }
    
    //测试程序,计算 (a*b)/(a-b+2)
    public class InterpreterTest {
    
        private static Expression ex;
        private static Context con;
    
        public static void main(String[] args) {
            con = new Context();
            //设置变量、常量
            Variable a = new Variable();
            Variable b = new Variable();
            Constant c = new Constant(2);
            //为变量赋值
            con.addValue(a, 8);
            con.addValue(b, 7); //这些工作相当于完成文法的存储
            //运算,对句子的结构(即抽象语法树)由我们自己来分析和构造
            //可见解释器模式并没有说明如何创建一个抽象语法树        
            ex = new Division(new Multiply(a, b), new Add(new Subtract(a, b), c));
            System.out.println("运算结果为:" + ex.interpret(con));
        }
    }
       解释器模式并没有说明如何创建一个抽象语法树,抽象语法树是则Client来负责构建(或被给定)的。因此它的实现可以多种多样,在上面我们是直接在客户端Test类中提供的,当然还有更好、更专业的实现方式。
      上面的Context中只有一条文法规则,一般每一条文法规则用一个类来表示,通过类之间的组合来实现一定的语法规则。
      基本思想:用一个Context类存储所有文法规则(使用HashMap),并有一个文法规则的推导函数。一个AbstractExpression类,里面有一个解释操作函数,解释操作要用到文法规则,故要传入Context对象。在具体的终结符表达式中,解释操作直接返回终结符。在只有单个非终结符的表达式中,解释操作要用到一条文法规则,故把调用转发给Context对象的推导函数,以获得规则右边的符号。在一般的非终结符表达式中,对其中的每个符号递归调用此符号的解释操作。
      NonterminalExpression对象由各单个的终结符或非终结符组成,是这些原子对象的组合,而他们都有相同的解释操作接口,可见Interpreter模式实际上只是Composite模式的针对特殊应用的一个特化版本,但这并不表示Interpreter模式的提出没有意义,Interpreter模式的提出使Composite模式扩展到了更深的领域--语义转换,这一点有着一定的实际意义。
      对于终结符,GOF建议采用享元模式来共享它们的拷贝,因为它们要多次重复出现。但是考虑到享元模式的使用局限性,我建议还是当你的系统中终结符重复的足够多的时候再考虑享元模式。
      当有一个语言需要解释执行,并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。
      上面的说法未免有点过于formal,简单说来,基应用可归纳为:
       1、当我们需要一个命令解释器以解释执行用户输入的指令时可以考虑使用Interpreter模式。
      2、当我们需要根据客户的输入对数据进行不同显示时可以考虑使用Interpreter模式。

      <<Java Design Pattern: A Tutorial>>一书给出了一个根据客户输入对数据进行不同形式输出的例子,很好地体现了以上几点。实质上,更多的情况下,我们可能通过组合客户选择(多项选择)得到一个命令串,交给专门的Interpreter进行解释执行,并将处理结果回显给客户,这样,可以很好地避免客户输入错误造成的不必要的复杂性。
       当存在以下情况时Interpreter模式效果最好:
      1、文法简单的情况。对于复杂的文法,文法的类层次变得庞大而无法管理,此时语法分析程序生成器这样的工具是更好的选择。它们无需构建抽象语法树即可解释表达式,这样可以节省空间而且还可能节省时间。
      2、效率不是一个关键问题的情况。最高效的解释器通常不是通过直接解释语法分析树实现的,而是首先将它们转换成另一种形式。例如,正则表达式通常被转换成状态机。但即使在这种情况下,转换器仍可用解释器模式实现,该模式仍是有用的。
       优缺点:
      解释器模式提供了一个简单的方式来执行语法,而且容易修改或者扩展语法。
      在解释器中不同的规则是由不同的类来实现的,这样使得添加一个新的语法规则变得简单。
    正如应用部分所说,Interpreter模式比较适用于文法简单,并且对处理的效率要求较低的情况,由于Interpreter模式使用类来标示每一条文法规则,因此,当处理复杂文法时,各规则类之间的调用及组合关系将变得难以维护,效率也将大大降低。

    转载于:https://my.oschina.net/abcijkxyz/blog/722872

    展开全文
  • 1.1.21 Strategy策略模式 ...GOF《设计模式》一书对Strategy模式是这样描述的: 定义一系列的算法,把他们一个个封装起来,并且使它们可相互替换。Strategy模式使算法可独立于使用它的客户而变化。 这里的...

    1.1.21 Strategy策略模式


      Strategy(策略)模式又称Policy模式。GOF《设计模式》一书对Strategy模式是这样描述的: 定义一系列的算法,把他们一个个封装起来,并且使它们可相互替换。Strategy模式使算法可独立于使用它的客户而变化。
      这里的算法并非狭义的数据结构或算法理论中所讨论的KMP、shell sort等算法,而是指应用程序设计中不同的处理逻辑,前面所说的狭义的算法只是其中的一部分。Strategy模式使得算法与算法的使用者相分离,减少了二者间的耦合度,使得算法可独立于使用它的客户而变化;同时,由于设计粒度的减小,程序的复用性也得到了进一步提高,分离出来的算法可以更好地适应复用的需要。
      Strategy模式主要用来将算法实现从类中分离出来,并封装在一个单独的类中。更简单的说,对象与其行为(behaviour)这本来紧密联系的两部分被解耦,分别放在了两个不同的类中。这使得对同一个行为,可以方便的在任何时候切换不同的实现算法。而通过对策略的封装,为其提供统一的接口,也可以很容易的引入新的策略。

      Strategy模式结构图如下:

       图21-1 Strategy模式类图

      策略模式涉及到三个角色:
      1、抽象策略(Strategy)角色:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,Context使用这个接口调用不同的算法,一般使用接口或抽象类实现。
      2、环境(Context)角色:需要使用ConcreteStrategy提供的算法。内部维护一个Strategy的实例。负责动态设置运行时Strategy具体的实现算法。负责跟Strategy之间的交互和数据传递。
      3、具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。实现了Strategy定义的接口,提供具体的算法实现。
      Strategy模式以下列几条原则为基础:
      1)每个对象都是一个具有职责的个体。
      2)这些职责不同的具体实现是通过多态的使用来完成的。
      3)概念上相同的算法具有多个不同的实现,需要进行管理。
      下面通过一个实例来说明它的具体使用,这个例子是关于数据库连接的。代码如下:

    interface DatabaseStrategy { //Strategy:抽象策略
    
        public void process();
    }
    
    class MysqlDBStrategy implements DatabaseStrategy { //具体策略
    
        @Override
        public void process() {
            System.out.println("处理Mysql数据库连接");
        }
    }
    
    class OracleDBStrategy implements DatabaseStrategy {
    
        @Override
        public void process() {
            System.out.println("处理Oracle数据库连接");
        }
    }
    
    class DataBaseManager { //Context角色
    
        public void process(DatabaseStrategy dbStrategy) {
            dbStrategy.process();
        }
    }
    
    public class StrategyClient {
    
        public static void main(String[] args) {
            MysqlDBStrategy mysql = new MysqlDBStrategy();
            DataBaseManager manager = new DataBaseManager();
            manager.process(mysql);
            OracleDBStrategy oracle = new OracleDBStrategy();
            manager.process(oracle);
        }
    }
      在我们的实际编程中经常会遇到系统要连接的数据库可能不只一种,如果采用传统的方法,即修改连接Url的方法,这种方法确实可行,但是有一个问题要经常修改源代码,不利于以后的维护,那么有没有一种更好的方法呢?答案是有,使用Strategy模式,首先定义一个连接数据库通用的接口(在上面的例子中是DatabaseStrategy),然后再定义实现该接口的具体类(MysqlDBStrategy、OracleDBStrategy),在这些具体类,实现具体的逻辑。最后再定义一个管理数据库连接的类(DataBaseManager),它的内部有一个方法可以接受具体类实例的参数。我们可以看到这个参数是DatabaseStrategy类型的,也就是说它可以接受任何一个实现了DatabaseStrategy接口的类的具体实例(这里运用了对象替换机制,多态的一种),从而完成数据库连接的处理。如果我们还需要处理另外一种数据库如sqlserver,我们只需要建立一个SqlserverDBStrategy类实现DatabaseStrategy接口,把该类的实例传给DatabaseManager的process方法即可。
      小结:Strategy模式是一种定义一系列算法的方法。概念上看,这些算法完成的都是相同的工作,只是实现不同。
      AWT的LayoutManager,是Strategy模式的一个例子。对于GUI而言,每个组件(Component)在容器中(Container)的排放是需要遵循一定的算法的。通常的方法是使用绝对坐标,就像VB,Delphi之类的工具所作的那样,记录每个组件在容器中的位置。这当然会带来一些问题,比如在窗体缩放的时候,就需要手工编码改变组件的大小和位置,以使得原来的比例得以保存。而在AWT中,引入了布局管理器(LayoutManager)的概念,使得布局的方法大大丰富,编码过程也变得简单。
      一个容器,比如Applet,Panel等,仅仅记录其包含的组件,而布局管理器中封装了对容器中组件进行布局的算法,具体地说,就是指明容器中组件的位置和尺寸的大小。通过布局管理器,你只需要确定想放置的组件间的相对位置即可,这一方面简化编码,另一方面也有助于实现软件的平台无关性。
      每一个容器均有一个布局管理器,当容器需要布置它的组件时,它调用布局管理器的方法布置容器内的组件。LayoutManager2继承于LayoutManager,提供更为细致的布局功能,它可以让布局管理器为组件加上约束条件已确定组件如何被布置。例如,为了确定组件被摆放在边框内的位置,BorderLayout在它的组件上加上方向指示。
    特别的,通过实现LayoutManager或者LayoutManager2接口,可以很容易实现自定义的布局策略。

    图21-2 AWT中的容器和布局管理器的关系

      如果有几个很相似的类,其区别仅仅是在个别行为上的动作不同,这时候就可以考虑使用Strategy模式。这样,通过策略组合,将原来的多个类精简为一个带有多个策略的类。这很符合OO设计的原则:找到变化的部分,并将其封装起来!Strategy模式同样的为子类继承提供了一个好的替代方案,当使用继承机制的时候,行为的改变是静态的,你只能够改变一次。而策略是动态的,可以在任何时候,切换任何次数。更为重要的是,策略对象可以在不同的环境中被不同的对象所共享。以布局管理器为例,虽然每一个容器只有一个布局管理器,但是一个布局管理器可以为多个容器工作。
      从结构上看,Strategy模式与State模式有几分相似,但二者所讨论的Context(情景)具有显著的差异。
      State模式在于将其状态信息分离出来保存到一个独立的对象中,以便状态信息的获取或状态的转换;Strategy模式在于将可能的算法分离出来,根据需要进行适当的选择。此外,二者的区别还在于,Strategy模式中各个Strategy(算法、策略)往往用于解决相同的问题,即只是解决同一问题的不同“策略”、“途径”,而且,一次只能有一个Strategy为上次应用提供服务。而State模式中的各个State本身往往具有一定的差异,但他们之间存在明显的相互转换的关系,而且这种转换往往会在程序运行过程中经常性地发生,同时存在一个以上State也是可能的。
      区别参考:二者的应用场合不同。状态模式用于处理对象有不同状态(状态机)的场合,策略模式用于随不同外部环境采取不同行为的场合。在状态模式中,状态的变迁是由对象的内部条件决定,外界只需关心其接口,不必关心其状态对象的创建和转化;而策略模式里,采取何种策略由外部条件决定。所以,有人说“状态模式是完全封装且自修改的策略模式”。至于Bridge,在结构上与前两者都不一样了。要说相似之处,就是三者都有具有对外接口统一的类,展现出多态性而已。
      当存在以下情况时可考虑使用Strategy模式:
      1、许多相关的类仅仅是行为有异。“策略”提供了一种用多个行为中的一个行为来配置一个类的方法。
      2、需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间/时间权衡的算法,当这些变体实现为一个算法的类层次时,可以使用策略模式。
      3、算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构。
      4、一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。
      具体的应用实例还可以列举一些,如:
      1、以不同的格式保存文件;
      2、以不同的方式对文件进行压缩或其他处理;
      3、以不同的方式绘制/处理相同的图形数据。


    1.1.22 Template Method模板方法模式


      《设计模式》一书对Template Method模式是这样描述的:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。这使得子类可以不改变算法的结构而重新定义算法的某些特定步骤。
      这里所说的Template跟Generic Programming(范型编程)中讨论的C++的template不是一回事(虽然有一定的相似性),C++的template是一种逻辑复用的方式,它可以不依赖于OO的Inheritance(继承)机制独立存在,因为GP跟OO所讨论的是完全不同的两个方面,虽然二者经常被融合在一起使用。Template Method模式与template不同,它是建立在继承机制 + 虚函数基础上的,它的核心在于在基类中定义好逻辑处理的框架(或称完成一项任务所需依次执行的步骤,或一段通用的处理逻辑),将具体的处理细节交给子类具体实现,从而达到“使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤”的目的。
      Template Method模式的结构如下图所示:

    图22-1 Template Method模式类图

      其中的参与者比较简单:
      1、AbstractClass(抽象类):定义一到多个抽象方法(也可以不是抽象方法,但至少应该是virtual方法。视你的应用需要,如果你的AbstractClass负责实现一个通用版本的算法,各子类对该方法进行进一步细化,则只需定义成virtual方法即可),具体的子类将重定义它们以实现一个算法;而且还实现一个模板方法,来定义一个算法的骨架。该模板方法不仅调用前面的抽象方法,也可以调用其他的操作。各抽象方法往往被定义成protected(保护)成员,以保证它们只被模板方法调用,而TemplateMethod往往被定义成public非虚成员函数。
      2、ConcreteClass(具体类):实现父类中的抽象方法以完成算法中与特定子类相关的步骤。
      这里的关键是基类中的TemplateMethod方法,因为正是它定义了对各子类对象适用的通用的处理逻辑。
      下面给出一个例子:

    abstract class QueryTemplate {  //抽象类:算法的模板
    
        //Template Method:模板方法,一般为public的,用来执行算法的各步
        public void doQuery() {
            formatConnect();
            formatSelect();
        }
    
        //算法的骨架,一般为protected的,由子类来实现它们
        //可见,子类并不能改变算法的框架结构,但可以改变算法的实现步骤
        protected abstract void formatConnect();
    
        protected abstract void formatSelect();
    }
    
    class OracleQT extends QueryTemplate { //实现算法的具体子类
    
        @Override
        public void formatConnect() {
            System.out.println("格式化Qracle数据库连接");
        }
    
        @Override
        public void formatSelect() {
            System.out.println("格式化Oracle数据库查询");
        }
    }
    
    class MysqlQT extends QueryTemplate {
    
        @Override
        public void formatConnect() {
            System.out.println("格式化Mysql数据库连接");
        }
    
        @Override
        public void formatSelect() {
            System.out.println("格式化Mysql数据库查询");
        }
    }
    
    public class TemplateTestClient {
    
        public static void main(String[] args) {
            QueryTemplate oracleQT = new OracleQT();
            oracleQT.doQuery(); //调用抽象模板的模板方法,以执行算法
            QueryTemplate mysqlQT = new MysqlQT();
            mysqlQT.doQuery();
        }
    }

      在这个例子中,我们定义了一个骨架QueryTemplate,在它的内部定义了一个Template Method和一些步骤(抽象方法),使用Template Method来调用这些步骤。步骤是在子类中实现的。
      理解:定义一个抽象类(接口),在它的内部定义一些抽象的方法(供TemplateMethod调用的步骤)和一个TemplateMethod方法(非抽象方法),封装了这些抽象方法的抽象类(接口)就是骨架。而将它的实现延迟到子类中,也就是用子类实现它。不改变算法的结构而重新定义它的步骤,也就是改写或者实现父类的这些非TemplateMethod的抽象方法。
      有时候,我们会遇到由一系列步骤构成的过程需要执行。这个过程从高层次上看是相同的,但有些步骤的实现可能不同。正如,查询SQL数据库从高层次上看过程是相同的,但某些细节比如如何连接数据库则可能因平台等细节的不同而不同。通过Template Method模式,我们可以先定义步骤序列,然后覆盖那些需要改变的步骤。
      应用:
      Template Method模式是一个使用频率比较高的模式,因为对于同一种类型的对象而言,他们之间一些处理流程往往是一致的,对象之间的差异仅在于具体的处理逻辑,因此,可以将通用的逻辑提取出来放到AbstractClass中实现,而将实现的具体细节交给子类完成。
      从这一点上讲,Template Method与Strategy模式存在一定的相似性,但Template Method中实现的主体是ConcreteClass,AbstractClass仅定义了接口和希望子类重新定义的方法,通过继承来改变算法;而Strategy模式中Context类与Strategy类之间不存在继承关系,体现的是一种委托的关系。


    1.1.23 Visitor访问者模式


      Visitor模式定义:表示一个作用于某对象结构中各元素的操作。它可以使你不修改各元素类的前提下定义作用于这些元素的新操作,也就是动态的增加新的方法。
      Visitor模式的结构如下图所示:

    图23-1 Visitor模式类图

      其中包括以下组成部分:
      Visitor(访问者):为该对象结构中的每个ConcreteElement提供一个visit操作。该操作的名字和特征标识了要访问的具体元素角色,这样访问者就可以通过该元素的特定接口直接访问它。
      ConcreteVisitor(具体访问者):实现每个由Visitor声明的操作。每个操作实现本算法的一部分,而该算法片断乃是对应于结构中对象的类。ConcreteVisitor为该算法提供了上下文并存储它的局部状态,这一状态常常在遍历该结构的过程中累积结果。
      Element(元素):定义一个accept操作,它以一个访问者为参数,接受具体的访问者。
      ConcreteElement(具体元素):实现Element的accept操作,该操作以一个访问者为参数。
      ObjectStructure(对象结构,如Program):这是使用访问者模式必备的角色。能枚举它的元素;可以提供一个高层的接口以允许该访问者访问它的元素;可以是一个复合(组合模式)或是一个集合,如一个列表或一个无序集合。
      下面代码按照类图的结构图来写的:

    import java.util.ArrayList;
    import java.util.Collection;
    
    interface Visitor {  //访问者
    
        public void visitElementA(ConcreteElementA elementA);//针对具体元素A的新方法
    
        public void visitElementB(ConcreteElementB elementB);//针对具体元素B的新方法
    }
    
    interface Element {  //元素
    
        public void accept(Visitor visitor);
    }
    
    class ConcreteVisitor implements Visitor {  //具体的访问者
    
        @Override
        public void visitElementA(ConcreteElementA elementA) { //访问具体元素A
            System.out.println(elementA.getName() + " visited by ConcreteVisitor ");
        }
    
        @Override
        public void visitElementB(ConcreteElementB elementB) {
            System.out.println(elementB.getName() + " visited by ConcreteVisitor ");
        }
    }
    
    class ConcreteElementA implements Element {  //具体元素A
    
        private String name;
    
        public ConcreteElementA(String name) {
            this.name = name;
        }
    
        @Override
        public void accept(Visitor visitor) {  //接受访问者的访问:要把自己推送给访问者
            visitor.visitElementA(this);
        }
    
        public String getName() {
            return name;
        }
    }
    
    class ConcreteElementB implements Element {  //具体元素B
    
        private String name;
    
        public ConcreteElementB(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        @Override
        public void accept(Visitor visitor) {  //接受访问者的访问:要把自己推送给访问者
            visitor.visitElementB(this);
        }
    }
    
    class ObjectStructure {  //对象结构:即元素的集合
    
        private Collection<Element> collection = new ArrayList<>(); //维护一个元素列表
    
        public void attach(Element element) {
            collection.add(element);
        }
    
        public void detach(Element element) {
            collection.remove(element);
        }
    
        public void accept(Visitor visitor) { //让每个元素者接受访问的访问
            for (Element element : collection) {
                element.accept(visitor);
            }
        }
    }
    
    public class VisitorClient {
    
        public static void main(String args[]) {
            Element elementA = new ConcreteElementA("ElementA");
            Element elementB = new ConcreteElementB("ElementB");
            Visitor visitor = new ConcreteVisitor();
            ObjectStructure os = new ObjectStructure();
            os.attach(elementA);
            os.attach(elementB);
            os.accept(visitor);
        }
    }
      在上述实现中,我们可以发现,Visitor模式虽然使得为已有的类型添加新的抽象函数的需求变得容易实现,但是,Element类型与Visitor类型之间的耦合十分严重,出现了循环依赖,Visitor需要有所有Element子类的声明,而所有Element子类也需要包含Visitor类的头文件,当需要增加新的Element类型时,由于Visitor类的改动,将造成Element继承体系和Visitor继承体系全部需要重新编译。那么有什么办法来减轻耦合呢?在C++中,我们为每一个Element类型实现一个ConcreteVisitor,并最终通过多继承来实现IntegratedConcreteVisitor以解除这种耦合关系的实现方法,但这种实现方法使得继承体系变得更加复杂,同时还存在一些其它的开销。个人认为,Visitor模式是GoF所列举的23种模式中最复杂的,同时由于其使用上的约束较多,实际的应用并不太多。
       Visitor模式一般在所谓的“双分派”问题中比较有用。
      Adapter模式告诉我们如何应对接口不一致对我们的设计造成的影响,但是,这并不能在如下的Context下发挥多大的作用:一个类系中的多个类要求支持相同的操作,但是这些类提供的接口并不一致。看到这里,你可能会说,我干嘛要用什么Adapter?我才没那么笨呢,我直接修改整个类系的接口方法,添加新的统一的接口方法不就OK了?
      确实如此,但是,如果你凑巧使用的是另一个不方便修改的模块的代码呢?在这种情况下,就可以使用Visitor(访问者)模式。
      Visitor模式用于表示一个作用于某对象结构中的各元素的操作,使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。Visitor模式借助所谓的“Double-Dispatch(双分派)”来达到以上目的。在C++、Java这样的典型的强类型、单分派语言中,我们通常使用的都是单分派,单分派的意思就是执行的方法是由调用者而不是调用参数决定的,譬如
      a.add(b)
      那么,这时执行的方法就是由a来决定的。add方法用于完成a + b操作,它需要根据a和b的不同类型执行不同的处理逻辑,这就需要双重分派。如果直接支持双分派的语言,那么执行这个方法就可以根据a和b两个的类型来决定。
      前面说过,C++、Java等并不能直接支持双分派,因此,要在C++、Java中支持所谓的双分派,必须通过增加额外的附加层和方法来实现。
      有两种主要的方式来实现Double-Dispatch,一种是type-switch。以下代码取自JDK:

    protected void processEvent(AWTEvent e) {
            if (e instanceof FocusEvent) {
                processFocusEvent((FocusEvent) e);
            } else if (e instanceof MouseEvent) {
                switch (e.getID()) {
                    case MouseEvent.MOUSE_PRESSED:
                    case MouseEvent.MOUSE_RELEASED:
                    case MouseEvent.MOUSE_CLICKED:
                    case MouseEvent.MOUSE_ENTERED:
                    case MouseEvent.MOUSE_EXITED:
                        processMouseEvent((MouseEvent) e);
                        break;
                    case MouseEvent.MOUSE_MOVED:
                    case MouseEvent.MOUSE_DRAGGED:
                        processMouseMotionEvent((MouseEvent) e);
                        break;
                    case MouseEvent.MOUSE_WHEEL:
                        processMouseWheelEvent((MouseWheelEvent) e);
                        break;
                }
            } else if (e instanceof KeyEvent) {
                processKeyEvent((KeyEvent) e);
            } else if (e instanceof ComponentEvent) {
                processComponentEvent((ComponentEvent) e);
            } else if (e instanceof InputMethodEvent) {
                processInputMethodEvent((InputMethodEvent) e);
            } else if (e instanceof HierarchyEvent) {
                switch (e.getID()) {
                    case HierarchyEvent.HIERARCHY_CHANGED:
                        processHierarchyEvent((HierarchyEvent) e);
                        break;
                    case HierarchyEvent.ANCESTOR_MOVED:
                    case HierarchyEvent.ANCESTOR_RESIZED:
                        processHierarchyBoundsEvent((HierarchyEvent) e);
                        break;
                }
            }
        }
      这种方式通过一堆的if-else,switch-case检查b的类型信息进行Re-Dispatch,虽然type-switch在设计上比较简单,但type-switch是OOD中应当尽量避免使用的技术,因为它可能给我们的代码引入一些难以察觉的Bug,以下面的代码为例(Java Code):
    class A {
    }
    
    class B extends A {
    }
    
    public class DispatchTest {
    
        static public void main(String[] args) {
            B b = new B();
            if (b instanceof A) {
                System.out.println("b is an instanceof A");
            } else if (b instanceof B) {
                System.out.println("b is an instanceof B");
            }
        }
    }
      程序运行的结果是:
    b is an instanceof A
      虽然从逻辑上讲,这个结论是正确的,但这显然不是我们期望的答案。要让上面的程序输出“b is an instanceof B”,需要调整上面的if判断的顺序,使子类判断出现在基类判断之前,由特殊到普通,否则,父类判断将屏蔽掉子类判断,对于简单的类型判断,使用type-switch是个不错的选择,但是当继承体系变得十分复杂时,判断顺序上的问题可能给你带来意想不到的麻烦(当然,还有别的办法,如通过getClass来检查类名信息,但这种方式比上面的方式也好不到哪里去)。
      还有一种Double-Dispatch实现方式在使用上相对较为安全,但实现较为复杂,而且需要更多的设计技巧,Visitor模式采用的是这一种形式。以下面的函数调用为例:
      a.add(Number b)
      我们可以在add函数体内采用type-switch方法对b进行判断,完成add操作,也可以像下面这样。对于整数类型,定义:
    public class Integer {
    
        Number add(Number b) { //接受Number b的加动作,并把自己推送给b
            return b.add(this);
        }
        //...
    }
      则不管b是什么类型,只要它实现了add(Integer a)这个方法,就可以准确完成add操作。这里的Number相当于Visitor,Integer相当于ConcreteElement,Integer是由Number来加(访问)的,它自己并没有去主动加b,而被b加了。
      对于浮点类型,定义:
    public class Float {
    
        Number add(Number b) {
            return b.add(this);
        }
        //...
    }
      则不管b是什么类型,只要它实现了add(Float a)这个方法,就可以准确完成add操作。
      可以看到,这时候把到底执行哪一个方法转交给了b,而且,在b执行该方法时,已经有了a的类型信息,因此,无需再进行type-switch。

      那么这种复杂的Double-Dispatch技术有什么好处呢?它的好处之一在于可以使我们在不改变a的同时,通过对b进行扩充,达到为a提供新的功能的目的。以上面的add为例,我们可以从Number派生出一种新的数值类型,在其中实现各种add操作,则可以在不改变已有数值类型的基础上与之协同工作。当然,由于在使用上存在一些限制,限制了Double-Dispatch的应用。

      关于双重分派,还可以参考我之前写的一篇文章,关于在C++中实现多态的双重分派:http://blog.csdn.net/zhoudaxia/article/details/4580438

      再举一个Visitor模式的例子:
    abstract class Parts {  //Element角色
    
        abstract void accept(Visitor visitor);
    }
    
    // component class: Wheel
    class Wheel extends Parts {  //ConcreteElement角色
    
        private String name;
    
        Wheel(String name) {
            this.name = name;
        }
    
        String getName() {
            return this.name;
        }
    
        @Override
        void accept(Visitor visitor) { // function to support double-dispatch
            visitor.visit(this);
        }
    }
    
    // component class: Engine
    class Engine extends Parts {
    
        @Override
        void accept(Visitor visitor) {
            visitor.visit(this);
        }
    }
    
    // component class: Body
    class Body extends Parts {
    
        @Override
        void accept(Visitor visitor) {
            visitor.visit(this);
        }
    }
    
    // class to demonstrate visitor pattern and double-dispatch. 
    //If we don't use double-dispatch, we will lost all class info when we 
    //put all components into an array.
    class Car {   //ObjectStructure角色:管理对各元素的访问
    
        private Parts[] parts = { new Engine(), new Body(), new Wheel("front left"), 
            new Wheel("front right"), new Wheel("back left"), new Wheel("back right") };
    
        //把对各元素的访问委托给Visitor
        void accept(Visitor visitor) {
            visitor.visit(this);
            for (int i = 0; i < parts.length; ++i) {
                parts[i].accept(visitor);
            }
        }
    }
    
    // visitor interface, all concrete visitor class must implement it.
    // need a access-function for each element class in the class-hierachy
    interface Visitor {
    
        void visit(Wheel wheel);
    
        void visit(Engine engine);
    
        void visit(Body body);
    
        void visit(Car car);
    }
    
    // concrete visitor: PrintVisitor
    class PrintVisitor implements Visitor {   //具体的Visitor:完成对每个元素的访问
    
        @Override
        public void visit(Wheel wheel) {
            System.out.println("Visiting " + wheel.getName()
                    + " wheel");
        }
    
        @Override
        public void visit(Engine engine) {
            System.out.println("Visiting engine");
        }
    
        @Override
        public void visit(Body body) {
            System.out.println("Visiting body");
        }
    
        @Override
        public void visit(Car car) {
            System.out.println("Visiting car");
        }
    }
    
    // more concrete visitor class, omitted...
    // entry class
    public class VisitorDemo {
    
        static public void main(String[] args) {
            Car car = new Car();
            Visitor visitor = new PrintVisitor();
            car.accept(visitor);
        }
    }
      基本思想: 把访问对象中各元素(或者一个复合对象中的各原子对象)的工作委托给Visitor来完成。把各元素抽象一个元素类,它有一个接受访问请求的accept方法,此方法里面把真正的访问工作转发给Visitor来完成,并把自己的引用传过去。Visitor里面有各元素的访问方法,根据传来的引用访问该元素。由于一个对象有很多属性元素,故要有一个管理者ObjectStructure维护一个元素集合,并串起对各元素的访问。
       在下列情况下可考虑使用Visitor模式:
      1、一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。
      2、需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。Visitor使得你可以将相关的操作集中起来定义在一个类中。当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作。
      3、定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好。
      Visitor模式通过扩充新的继承体系来为已有的继承体系提供新的针对特殊类型的功能(达到与添加虚成员函数相同的效果),适用于十分稳定,并执行繁重处理的继承体系。
       下面是访问者模式的一些优缺点:
      1、Visitor模式使得易于增加新的操作访问者,使得增加依赖于复杂对象结构的构件的操作变得容易了。仅需增加一个新的访问者即可在一个对象结构上定义一个新的操作。相反,如果每个功能都分散在多个类之上的话,定义新的操作时必须修改每一类。
      2、访问者集中相关的操作而分离无关的操作。相关的行为不是分布在定义该对象结构的各个类上,而是集中在一个访问者中。无关行为却被分别放在它们各自的访问者子类中。这就既简化了这些元素的类,也简化了在这些访问者中定义的算法。所有与它的算法相关的数据结构都可以被隐藏在访问者中。
      3、增加新的ConcreteElement类很困难。Visitor模式使得难以增加新的Element的子类。每添加一个新的ConcreteElement都要在Visitor中添加一个新的抽象操作,并在每一个ConcreteVisitor类中实现相应的操作。有时可以在Visitor中提供一个缺省的实现,这一实现可以被大多数的ConcreteVisitor继承,但这与其说是一个规律还不如说是一种例外。
      所以在应用访问者模式时考虑关键的问题是系统的哪个部分会经常变化,是作用于对象结构上的算法呢,还是构成该结构的各个对象的类。如果老是有新的ConcreteElement类加入进来的话,Visitor类层次将变得难以维护。在这种情况下,直接在构成该结构的类中定义这些操作可能更容易一些。如果Element类层次是稳定的,而你不断地增加操作来修改算法,Visitor模式可以帮助你管理这些改动。
      4、通过类层次进行访问。一个迭代器可以通过调用节点对象的特定操作来遍历整个对象结构,同时访问这些对象。但是迭代器不能对具有不同元素类型的对象结构进行操作。
      5、累积状态。当访问者访问对象结构中的每一个元素时,它可能会累积状态。如果没有访问者,这一状态将作为额外的参数传递给进行遍历的操作,或者定义为全局变量。
      6、破坏封装。访问者方法假定ConcreteElement接口的功能足够强,足以让访问者进行它们的工作。结果是,该模式常常迫使你提供访问元素内部状态的公共操作,这可能会破坏它的封装性。

    转载于:https://my.oschina.net/abcijkxyz/blog/723300

    展开全文
  • 模板模式 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤 案例中都写了注解 案例demo分析: ...(templatePattern) ...

    模板模式

    定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤

    案例中都写了注解
    案例demo分析:
    https://github.com/FlyYant/designModelDemo
    (templatePattern)

    案例中使用到了钩子方法,不理解啥是钩子方法的来这里看看
    https://blog.csdn.net/zuiwanggg/article/details/73998574

    适用场景:

    多个产品有相同或者类似的场景

    优点:

    1. 具体细节步骤实现定义在子类中,子类定义详细处理算法是不会改变算法整体结构。
    2. 存在一种反向的控制结构,通过父类调用其子类的操作,通过子类对父类进行扩展增加新的行为,符合“开闭原则”。
    3. 提取公共代码,便于维护

    缺点:

    每个不同的实现都需要定义一个子类,会导致类的个数增加,系统更加庞大

    迭代器模式

    提供一种方法来访问聚合对象,而不用暴露这个对象的内部表示,其别名为游标(Cursor)

    案例中都写了注解
    案例demo分析:
    https://github.com/FlyYant/designModelDemo
    (iteratorPattern)

    适用场景:

    1. 需要将聚合对象的访问与内部数据的存储分离,使得访问聚合对象时无须了解其内部实现细节。
    2. 需要为一个聚合对象提供多种遍历方式。
    3. 需要为遍历不同的聚合结构提供一个统一的接口,在该接口的实现类中为不同的聚合结构提供不同的遍历方式,而应用层可以一致性地操作该接口

    优点:

    1. 它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。
    2. 在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,我们也可以自己定义迭代器的子类以支持新的遍历方式。
    3. 迭代器模式简化了聚合类。由于引入了迭代器,在原有的聚合对象中不需要再自行提供数据遍历等方法,这样可以简化聚合类的设计。
    4. 在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,符合 “开闭原则”

    缺点:

    1. 由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。
    2. 抽象迭代器的设计难度较大,需要充分考虑到系统将来的扩展,例如JDK内置迭代器Iterator就无法实现逆向遍历,如果需要实现逆向遍历,只能通过其子类ListIterator等来实现,而ListIterator迭代器无法用于操作Set类型的聚合对象。
    3. 在自定义迭代器时,创建一个考虑全面的抽象迭代器并不是件很容易的事情

    策略模式

    定义了一组算法,将每个算法都封装起来,并且使它们之间可以互换,且算法的变化不会影响使用算法的客户。它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理
    案例中都写了注解

    案例demo分析:
    https://github.com/FlyYant/designModelDemo
    (strategyPattern)

    适用场景:

    1. 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
    2. 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
    3. 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为

    优点:

    1. 能够减少代码的if-else逻辑判断。
    2. 可以在不改变源代码的情况下,新增接口实现类,灵活增加新算法,符合开闭原则。
    3. 把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离

    缺点:

    1. 应用层必须明确知道所有算法的区别。
    2. 每新增一个算法,就需要添加一个策略类

    解释器模式

    给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子

    案例中都写了注解
    案例demo分析:
    https://github.com/FlyYant/designModelDemo
    (interpreterPattern)

    案例中涉及到到计算的后缀表达式(逆波兰式),可以去这看看
    https://blog.csdn.net/qq_39445165/article/details/87641261

    适合场景:

    1. 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。
    2. 一些重复出现的问题可以用一种简单的语言进行表达。
    3. 执行效率不是关键问题。高效的解释器通常不是通过直接解释抽象语法树来实现的,而是需要将它们转换成其他形式,使用解释器模式的执行效率并不高

    优点:

    1. 易于改变和扩展文法。由于在解释器模式中使用类表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
    2. 实现文法较为容易。在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂,还可以通过一些工具自动生成节点类代码

    缺点:

    1. 解释器模式会引起类膨胀。
    2. 解释器模式将会导致系统比较复杂, 为维护带来了非常多的麻烦。
    3. 执行效率低。由于在解释器模式中一般采用了大量的循环和递归调用(我们的例子是使用栈来代替递归),因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦

    观察者模式

    观察者模式,又被称为发布-订阅(Publish/Subscribe)模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象

    案例中都写了注解
    案例demo分析:
    https://github.com/FlyYant/designModelDemo
    (observerPattern)

    适用场景:

    1. 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
    2. 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。

    优点:

    1. 观察者和被观察者是抽象耦合的。
    2. 建立了一套触发机制。

    缺点:

    1. 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
    2. 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
    3. 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

    备忘录模式

    在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样就可以将该对象恢复到原先保存的状态。

    像游戏的存档功能,office、wps等文字编辑工具的回退功能,都可以看做备忘录模式的应用

    案例中都写了注解
    案例demo分析:
    https://github.com/FlyYant/designModelDemo
    (mementoPattern)

    适用场景:

    1. 需要保存/恢复数据的相关状态场景。
    2. 提供一个可回滚的操作。

    优点:

    1. 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。
    2. 实现了信息的封装,使得用户不需要关心状态的保存细节。

    缺点:

    消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存

    命令模式

    将命令封装成对象,从而方便对命令对象进行传递、管理与扩展。并且接收者可以识别这些请求对象,然后执行相应的行为

    案例中都写了注解
    案例demo分析:
    https://github.com/FlyYant/designModelDemo
    (commandPattern)

    适用场景:

    1. 请求调用者和请求接收者需要解耦,使得调用者和接收者不直接交互。
    2. 需要抽象出等待执行的行为。

    优点:

    1. 降低了系统耦合度。
    2. 新的命令可以很容易添加到系统中去。

    缺点:

    使用命令模式可能会导致某些系统有过多的具体命令类

    中介者模式

    中介者模式又称为调停者模式,用一个中介者来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,减少类之间的依赖,从而使其耦合松散,而且可以独立地改变它们之间的交互

    案例中都写了注解
    案例demo分析:
    https://github.com/FlyYant/designModelDemo
    (mediatorPattern)

    适用场景:

    1. 系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解。
    2. 一个对象由于引用了其他很多对象并且直接和这些对象通信,导致难以复用该对象。
    3. 想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。可以通过引入中介者类来实现,在中介者中定义对象交互的公共行为,如果需要改变行为则可以增加新的具体中介者类。

    优点:

    1. 中介者模式简化了对象之间的交互,它用中介者和同事的一对多交互代替了原来同事之间的多对多交互,一对多关系更容易理解、维护和扩展,将原本难以理解的网状结构转换成相对简单的星型结构。
    2. 中介者模式可将各同事对象解耦。中介者有利于各同事之间的松耦合,我们可以独立的改变和复用每一个同事和中介者,增加新的中介者和新的同事类都比较方便,更好地符合“开闭原则”。
    3. 可以减少子类生成,中介者将原本分布于多个对象间的行为集中在一起,改变这些行为只需生成新的中介者子类即可,这使各个同事类可被重用,无须对同事类进行扩展。

    缺点:

    1. 在具体中介者类中包含了大量同事之间的交互细节,可能会导致具体中介者类非常复杂,使得系统难以维护。

    (也就是把具体同事类之间的交互复杂性集中到了中介者类中,结果中介者成了最复杂的类)

    责任链模式

    为请求创建一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦

    案例中都写了注解
    案例demo分析:
    https://github.com/FlyYant/designModelDemo
    (chainOfResponsibilityPattern)

    适用场景:

    1. 有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。
    2. 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
    3. 可动态指定一组对象处理请求。

    优点:

    1. 请求者不需要知道处理者,也不需要知道如何处理。每个责任对象只负责自己的责任范围,其他的交给后继者。各个组件间完全解耦。
    2. 责任链模式会把功能分散到单独的责任对象中,然后在使用时动态的组合形成链,从而可以灵活的分配责任对象,也可以灵活的添加责任对象。

    缺点:

    1. 因为功能处理都分散到了单独的责任对象中,每个责任对象功能单一,要把整个流程处理完,需要很多的责任对象,会产生大量的细粒度责任对象。
    2. 每个责任对象都只负责自己的部分,这样就可能出现某个请求,即使把整个链走完,都没有责任对象处理它。这就需要提供默认处理,并且注意构造链的有效性

    访问者模式

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

    案例中都写了注解
    案例demo分析:
    https://github.com/FlyYant/designModelDemo
    (visitorPattern)

    适用场景:

    1. 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。
    2. 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,并且需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。

    优点:

    1. 各个角色职责分离,符合单一职责原则。
    2. 使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化。
    3. 优秀的扩展性。
    4. 灵活性高。

    缺点:

    1. 具体元素对访问者公布细节,违反了迪米特原则。
    2. 具体元素变更时导致修改成本大。
    3. 违反了依赖倒置原则,为了达到区别对待,而依赖了具体类,而不是抽象

    状态模式

    当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类
    案例中都写了注解

    案例demo分析:
    https://github.com/FlyYant/designModelDemo
    (statePattern)

    适用场景:

    1. 行为随状态改变而改变的场景。
    2. 条件、分支语句的代替者。

    优点:

    1. 封装了转换规则。
    2. 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
    3. 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。

    缺点:

    1. 状态模式的使用必然会增加系统类和对象的个数。
    2. 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
    3. 状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码
    展开全文
  • 行为型设计模式:关注对象和行为的分离。(共11个)  甩锅大法:把锅丢出去,只管自己,哪管洪水滔天。...下面我们结合几种具体场景来分析几种行为型设计模式。 1、模板方法模式(TemplateMethod Patter.
  • 行为型设计模式总结

    2010-03-31 10:42:00
    行为型设计模式涉及的算法和对象间职责的分配。它不仅描述了对象或类的模式,还描述了它们之间的通信模式。这一点将在本总结中重点分析。那么首先看下各个行为型设计模式的含义。 一、行为型设计模式的含义。 1、...
  • 1.1.17 Iterator迭代器模式 Iterator模式定义: 提供一个方法顺序访问一个聚合对象的各个元素,而又不暴露该对象的内部表示。 Iterator(迭代器)模式又称Cursor(游标)模式,它是运用于聚合对象的一种模式,...
  • 文章目录设计模式行为型模式模板方法模式基本介绍原理类图原理类图说明模板方法模式的钩子方法案例理解案例类图代码实现优缺点模板方法模式在Spring框架应用的源码分析命令模式基本介绍原理类图原理类图说明案例...
  • 主要介绍了PHP设计模式之迭代器模式Iterator,结合实例形式分析了PHP迭代器模式Iterator相关概念、原理、实现方法与操作注意事项,需要的朋友可以参考下
  • Java设计模式 行为型模式 职责链模式 模式动机:职责链可以是一条直线、一个环或者一个树形结构。链上的每一个对象都是请求处理者,职责链模式可以将请求的处理组织成一条链,并使请求链传递,由链上的处理者对...
  • 设计模式命令模式(行为型)

    千次阅读 2019-04-16 00:02:23
    文章目录一、模式定义二、模式角色三、模式分析四、典型例子五、适用场景 一、模式定义 命令模式(Command Pattern):将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分离,两者之间通过命令对象进行...
  • 命令模式(Command) 请分析上图中这条命令的涉及到的角色以及执行过程,一种可能的理解方式是这样子的: 涉及角色为:大狗子和大狗子他妈 过程为:大狗子他妈角色 调用 大狗子的“回家吃饭”方法 引子 ...
  • 解释器模式(InterpreterPattern)属于行为型设计模式,它主要是为了解决当出现一种特定类型的问题且发生频率足够高时,那么就可以将该类问题的各个实现表述为一个简单的语言句子,然后通过构造一个解释器来解释这些...
  • 书接上文,上一篇对 Java 中常用的结构性设计模式做了介绍与分析,本篇就对 Java 中常用的行为型设计模式进行介绍与分析。目录: 责任链模式 命令模式 迭代器模式 观察者模式 策略模式 模版方法模式 简单...
  • 源码地址:【行为型模式】设计模式之迭代器模式 目录基本介绍基本原理应用案例1、需求2、UML类图3、代码实现源码分析总结 基本介绍 迭代器模式(Iterator Pattern)是常用的设计模式,属于行为型模式 如果...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 664
精华内容 265
关键字:

行为型设计模式分析