zoukankan      html  css  js  c++  java
  • 观察者模式小试

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

    一、介绍

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

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

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

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

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

    二、我的实现

    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);
        }
    
    }
  • 相关阅读:
    安装Docker-Compose
    Docker微容器Alpine Linux
    Linux 常用命令
    如何定制博客园的个人空间
    Elasticsearch入门之从零开始安装ik分词器
    Elasticsearch入门实践
    写在2017年的总结
    开源ETL工具之Kettle介绍
    常用Java数据库连接池
    细说shiro之七:缓存
  • 原文地址:https://www.cnblogs.com/zrtqsk/p/3699451.html
Copyright © 2011-2022 走看看