观察者模式小试

观察者模式又叫订阅-发布模式,也是非常常用的设计模式之一。

一、介绍

还是先来看一下《研磨设计模式》的介绍——定义对象间的一种一对多的依赖关系。当一个对象的状态发生改变的时候,所有依赖于它的对象都得到通知,并被自动更新。

观察者模式的本质:触发联动。

什么意思呢?说白了,就是说一个对象的状态发生改变,另一个对象自动做出响应。怎样能够使一个目标对象的状态发生改变时,观察者对象自动做出响应呢?

很简单,让目标对象持有观察者对象就可以了。如果一个目标对象有多个观察者,每次目标对象的状态改变,就自动遍历自己持有的观察者对象,将自己状态改变的情况通知观察者,就是传递参数给观察者。观察者模式无非就是这样。

参看我的博文中介者模式小试,可知道,观察者模式和中介者模式有很多相似的地方。组件间传递信息时,也经常将自身传过去,然后处理时使用强制类型转换进行处理。不过中介者一般是多个组件类将自身传递给代理类,让代理类统一处理组件间的交互。而观察者模式则是反过来,目标类发生改变时,一般将自身传递给自己持有的每个观察者,这样就激活了观察者的方法。

 

二、我的实现

Swing中包含了大量的观察者模式的实现。为了便于理解,在这里我也模仿Swing。我们都知道在画板上画画,每次我们在画板上点击鼠标左键,马上,画板上上就出现了相应的点。拖住不放,就可以画出一条线。这是为什么呢?我们假设画板作为目标对象,有一个监听器在监听。每次点击左键都会产生一个事件,画板会马上接受这样一个事件,然后处理之后传给监听器,监听器把它画出来。

我要模拟的就是这个过程。如下:

1、一个抽象的目标类:

//抽象的目标类

public abstract class Subject {



    //监听器列表

    protected List<Listener> listenerList = new ArrayList<Listener>();



    //添加监听器

    public void addListener(Listener listener)

    {

        listenerList.add(listener);

    }



    //移除监听器

    public void removeListener(Listener listener)

    {

        listenerList.remove(listener);

    }



    //通知所有监听器

    abstract void notifyListener();

}

2、监听器只是一个标识接口,不实现任何方法:

public interface Listener {

}

3、将触发事件的因素封装起来,成为一个Event类,鼠标事件如下:

//模拟鼠标事件

public class MouseEvent {



    //模拟鼠标左、中、右键

    public static final int BUTTON1 = 1;

    public static final int BUTTON2 = 2;

    public static final int BUTTON3 = 3;

    private int x;

    private int y;

    private int ClickCount;

    

    public int getClickCount()

    {

        return ClickCount;

    }



    public void setClickCount(int clickCount)

    {

        ClickCount = clickCount;

    }



    public int getX()

    {

        return x;

    }



    public void setX(int x)

    {

        this.x = x;

    }



    public int getY()

    {

        return y;

    }



    public void setY(int y)

    {

        this.y = y;

    }



}

4、每次都需要从鼠标事件中取出鼠标位置,封装成屏幕上的点,PointOnScreen类,如下:

public class PointOnScreen {



    private int x;

    private int y;



    public int getX()

    {

        return x;

    }



    public void setX(int x)

    {

        this.x = x;

    }



    public int getY()

    {

        return y;

    }



    public void setY(int y)

    {

        this.y = y;

    }



}

5、系统监听器,实现了标识接口Listener:

//系统监听器

public class SystemListener implements Listener {

    // 在屏幕上将这个图形画出来

    public void drawOnScreen(List<PointOnScreen> graph)

    {

        System.out.println();

        System.out.println("刷新时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));

        for (PointOnScreen point : graph)

        {

            System.out.println("当前画到的点是——————(" + point.getX() + "," + point.getY() + ")");

        }

    }

}

6、下面是最重要的目标具体类——画板类:

public class Panel extends Subject {



    // 表示图形

    private List<PointOnScreen> graph = new ArrayList<PointOnScreen>();



    // 添加鼠标事件

    public void addKeyEvent(MouseEvent event)

    {

        // 处理鼠标事件

        PointOnScreen point = new PointOnScreen();

        point.setX(event.getX());

        point.setY(event.getY());

        graph.add(point);

        // 通知所有监听器

        notifyListener();

    }



    @Override

    void notifyListener()

    {

        // 遍历每一个PanelListener,画图!

        for (Listener listener : listenerList)

        {

            if (listener instanceof SystemListener)

            {

                ((SystemListener) listener).drawOnScreen(graph);

            }

        }

    }



}

7、至此,已经完成,我们来测试一下:

public class Test {



    public static void main(String[] args)

    {

        //创建MouseEvent,几个鼠标事件

        MouseEvent event1 = new MouseEvent();

        event1.setX(1);

        event1.setY(2);

        MouseEvent event2 = new MouseEvent();

        event2.setX(2);

        event2.setY(2);

        MouseEvent event3 = new MouseEvent();

        event3.setX(2);

        event3.setY(3);

        

        //创建目标类

        Panel panel = new Panel();

        

        //系统监听器

        SystemListener autoListener = new SystemListener();

        

        //注册监听器

        panel.addListener(autoListener);

        

        //添加事件,模拟鼠标点击操作

        panel.addKeyEvent(event1);

        panel.addKeyEvent(event2);

        panel.addKeyEvent(event3);

    }

}

8、结果如下:

刷新时间:2014-04-29 15:44:26.546

当前画到的点是——————(1,2)



刷新时间:2014-04-29 15:44:26.548

当前画到的点是——————(1,2)

当前画到的点是——————(2,2)



刷新时间:2014-04-29 15:44:26.548

当前画到的点是——————(1,2)

当前画到的点是——————(2,2)

当前画到的点是——————(2,3)

如上,已经模拟出了画图的过程。

 

三、推模型和拉模型

什么是推模型和拉模型呢?上面例子中,事件源是什么呢?是MouseEvent。可是传给监听器对象的时候,我们是将MouseEvent包装成PointOnScreen对象去传递的。这就是推模型。

相对的,拉模型指的就是,传递信息给监听器的时候,将本身的引用传递过去,那么监听器对象希望处理什么信息就处理什么信息,那就是拉模型。

我们将Panel改变一下:

public class Panel extends Subject {



    // 表示图形

    private List<PointOnScreen> graph = new ArrayList<PointOnScreen>();



    private KeyEvent keyEvent;

    

    public KeyEvent getKeyEvent(){

        return keyEvent;

    }

    

    // 添加鼠标事件

    public void addKeyEvent(MouseEvent event)

    {

        // 处理鼠标事件

        PointOnScreen point = new PointOnScreen();

        point.setX(event.getX());

        point.setY(event.getY());

        graph.add(point);

        // 通知所有监听器

        notifyListener();

    }



    void notifyListener(){

        for (Listener listener : listenerList)

        {

            if (listener instanceof SystemListener)

            {

                //将自身对象传过去

                listener.update(this);

            }

        }

    }

}

然后,把SystemListener变成这样:

//系统监听器

public class SystemListener implements Listener {

    // 在屏幕上将这个图形画出来

    public void drawOnScreen(List<PointOnScreen> graph)

    {

        System.out.println();

        System.out.println("刷新时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));

        for (PointOnScreen point : graph)

        {

            System.out.println("当前画到的点是——————(" + point.getX() + "," + point.getY() + ")");

        }

    }



    // 表示图形

    private List<PointOnScreen> graph = new ArrayList<PointOnScreen>();



    public void update(Subject subject)

    {

        if (subject instanceof Panel)

        {

            MouseEvent event = ((Panel) subject).getMouseEvent();

            // 处理鼠标事件

            PointOnScreen point = new PointOnScreen();

            point.setX(event.getX());

            point.setY(event.getY());

            graph.add(point);

            drawOnScreen(graph);

        }

    }



}

如上,目标对象传递信息的时候将自身传递过去,监听器处理信息的时候,需要什么就从目标对象哪里拿什么,非常方便。这就是拉模型。

 

四、Java中的观察者模式

要实现观察者模式,其实完全不用那么麻烦,目标类和监听者接口Java已经帮我们实现了。目标类是java.util.Observable,监听器接口是java.util.Observer

即,目标类要继承java.util.Observable,监听器接口实现java.util.Observer。怎么传值呢?

目标类状态改变之后,调用这样几个方法:

        //状态改变

        this.keyEvent = event;

        //状态改变了,不可少

        this.setChanged();

        // 拉模型

        this.notifyObservers();

        //推模型所传的对象

        this.notifyObservers(graph);    

同时,监听器接口实现了这样一个方法:

  public void update(Observable o, Object arg)

    {

        //拉模型处理o

        //推模型处理arg

    }

非常简单!

下面用Java的观察者模式实现示例

1、目标类如下:

public class Panel extends java.util.Observable {



    // 表示图形,用于推模型

    private List<PointOnScreen> graph = new ArrayList<PointOnScreen>();

    

    private MouseEvent keyEvent;

    

    public MouseEvent getMouseEvent(){

        return keyEvent;

    }

    

    // 添加鼠标事件

    public void addKeyEvent(MouseEvent event)

    {   //状态改变

        this.keyEvent = event;

        //状态改变了,不可少

        this.setChanged();

        // 拉模型

        this.notifyObservers();

        

        // 推模型,处理鼠标事件

        PointOnScreen point = new PointOnScreen();

        point.setX(event.getX());

        point.setY(event.getY());

        graph.add(point);

        //推模型所传的对象

        this.notifyObservers(graph);

    }

}

2、监听器类如下:

//系统监听器

public class SystemListener  implements java.util.Observer {

    // 在屏幕上将这个图形画出来

    public void drawOnScreen(List<PointOnScreen> graph)

    {

        System.out.println();

        System.out.println("刷新时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));

        for (PointOnScreen point : graph)

        {

            System.out.println("当前画到的点是——————(" + point.getX() + "," + point.getY() + ")");

        }

    }



    // 表示图形

    private List<PointOnScreen> graph = new ArrayList<PointOnScreen>();



    @Override

    public void update(Observable o, Object arg)

    {

        if (o instanceof Panel)

            

        {

            MouseEvent event = ((Panel) o).getMouseEvent();

            // 处理鼠标事件

            PointOnScreen point = new PointOnScreen();

            point.setX(event.getX());

            point.setY(event.getY());

            graph.add(point);

            drawOnScreen(graph);

        }

        

        //推模型

        List<PointOnScreen> graph = (ArrayList<PointOnScreen>)arg;

        drawOnScreen(graph);

    }



}

 

 

你可能感兴趣的:(观察者模式)