zoukankan      html  css  js  c++  java
  • 浅谈Java五大设计原则之观察者模式

    定义一下观察者模式:

      观察者模式又叫  发布-订阅  模式,定义的两个对象之间是一种一对多的强依赖关系,当一个对象的状态发生改变,所有依赖它的对象

    将得到通知并自动更新(摘自Hand First)。

     关键角色:

    1.主题(Subject)

      抽象主题以及具体的主题

    2.观察者(Observer)

      抽象观察者以及具体观察者

    我们可以这样理解两者之间的关系:

    这就好比一个多个用户订阅同一个微信公众号,当公众号有内容更新,就立马通知所有的订阅用户。如图:

    举个例子:

    先来定义一个主题抽象类Subject:

    package org.theme;
    
    import org.observer.Observer;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * Created by Administrator on 2016/11/24.
     * 定义一个抽象主题
     */
    public abstract class Subject {
    
        //定义关注这个主题的额所有观察者
        private List<Observer> list = new ArrayList<>();
    
        //提供一个添加观察者的方法
        //( 其实这种行为就是在对应的主题上注册一个观察者 )
        public void addObserver( Observer observer ) {
            list.add( observer );
        }
    
        //移除观察者
        public void removeObserver( Observer observer ) {
            list.remove( observer );
        }
    
        //通知,这个是主题的核心任务,当主题的状态发生改变时
        //将通知所有注册这个主题的观察者对象
        public void notify( String message ) {
            for ( Observer obs : list ) {
                obs.update( message );
            }
        }
    
        //提供一个抽象的发布消息行为
        public abstract void send( String message );
    }
    

    继承抽象主题的具体的主题类Subject1:

    package org.theme;
    
    /**
     * Created by Administrator on 2016/11/24.
     */
    public class Subject1 extends Subject {
    
        @Override
        public void send( String message ) {
            System.out.println( "Subject1 收到了客户端发来的信息,立马通知对应的观察者" );
            message += "。发送者:Subject One:";
            notify( message );
        }
    }
    

    抽象的观察者Observer:

    package org.observer;
    
    /**
     * Created by Administrator on 2016/11/24.
     * 定义一个抽象的观察者
     */
    public abstract class Observer {
    
        //观察者自动更新执行的方法,当主题发出通知,自动调用这个方法
        public abstract void update( String message );
    }
    

    继承Observer的具体观察者Observer1:

    package org.observer;
    
    /**
     * Created by Administrator on 2016/11/24.
     * 定义一个观察者
     */
    public class Observer1 extends Observer {
    
        @Override
        public void update(String message) {
            System.out.println( "Observer1 收到了消息:" + message );
        }
    }

    继承Observer的具体观察者Observer2:

    package org.observer;
    
    /**
     * Created by Administrator on 2016/11/24.
     */
    public class Observer2 extends Observer {
    
        @Override
        public void update(String message) {
            System.out.println( "Observer2 收到了消息:" + message );
        }
    }

    Main方法:

    package org.main;
    
    import org.observer.Observer;
    import org.observer.Observer1;
    import org.observer.Observer2;
    import org.theme.Subject;
    import org.theme.Subject1;
    import org.theme.Subject2;
    
    /**
     * Created by Administrator on 2016/11/24.
     */
    public class Main {
        public static void main(String[] args) {
            //定义一个主题
            Subject subject1 =  new Subject1();
            //定义观察者
            Observer o1 = new Observer1();
            //定义观察者
            Observer o2 = new Observer2();
            //观察者关注主题
            subject1.addObserver( o1 );
         subject1.addObserver( o2 ); //主题发送内容 subject1.send( "今天天气变冷了" ); } }

    运行结果为:

    再举个例子:

     Swing中的事件驱动模型就是典型的观察者模式。

    package org2.main;
    
    import javax.swing.*;
    
    /**
     * Created by Administrator on 2016/11/24.
     *  swing驱动事件
     */
    public class MainFrame {
        //定义个容器变量
        JFrame f = null;
        //定义个按钮变量
        JButton btn = null;
    
        public MainFrame() {
            //创建容器
            f = new JFrame( "事件模型" );
            //创建按钮
            btn = new JButton( "按钮" );
            //把按钮添加到容器中
            f.add( btn );
            //设置容器大小
            f.setSize( 300, 200 );
            //把容器设置为 可见
            f.setVisible( true );
    
            //给按钮添加事件处理
            //在btn上注册一个监听器
            //(btn其实就是具体的主题对象)
            //(而监听器就是具体的观察者)
            //当点击按钮时(其实也就是主题对象发生了改变),就会触发监听器事件(观察者更新了)
            btn.addActionListener( new MyListener() );
        }
    
        //main方法
        public static void main( String[] args ) {
            new MainFrame();
        }
    }

    定义一个监听器(观察者):

    package org2.main;
    
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    
    /**
     * Created by Administrator on 2016/11/24.
     * 定义一个监听器(具体观察者)
     */
    public class MyListener implements ActionListener {
    
        //该方法类似于观察中的update方法
        @Override
        public void actionPerformed( ActionEvent e ) {
            System.out.println( "按钮被点击,执行一些业务逻辑操作" );
        }
    }
    

    运行结果:

      总的来说,观察者模式所做的工作其实就是在解除耦合。让耦合双方都依赖抽象,

    而不是具体。从而使得各自的变化都不影响其他一方。这也符合了依赖倒置原则。

    那我们什么时候能用上观察者模式呢?

    1.当一个对象的改变需要同时改变其他对象时。

    2.一个对象不知道它的改变会影响多少个类的改变。

    3.当有一个抽象模型有两个方面,一面依赖另一面。这时观察者模式可以很好地

    将两者封装在独立的对象使它们各自独立改变和复用。

    观察者模式的不足:

      虽然如此,观察者模式还是存在着不足。“抽象主题”还是依赖了“抽象观察者”,万一没有

    抽象观察者,通知功能就没法实现了。而且,不是每个 “具体观察者” 都会调用 “更新” 方

    法或者说 “更新”方法的更新内容不是都相同的。

    那有没有个好方法解决这个难点呢?

    有,那就是  消息发布/订阅架构模式。

    关键角色:

    1.消息队列(存放消息和定制主题的核心)。

    2.消息的发送方(将消息发布到消息队列中的人)。

    3.消息的接受者。

      消息的发布者只需发布消息存放在消息队列中,不需要依赖任何的抽象接收者,而接收者

    根据自己的订阅的主题从队列中接受消息,两者没有任何联系,自然也就不存在什么依赖关系,

    这很好解决了观察者模式的不足。

    模型简图:

    代码如下:

    消息队列类:

    package org3.demo;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.BlockingQueue;
    import java.util.concurrent.LinkedBlockingQueue;
    
    /**
     * Created by Administrator on 2016/11/24.
     * 编写一个消息队列
     */
    public class MessageQueue {
    
        //定义一个消息队列,这个队列中可以有多个子主题的集合
        //map的key是主题的名称,value是一个阻塞队列
        private Map<String, BlockingQueue<NewsMessage>> map = new HashMap<>();
    
        //构造方法
        public MessageQueue() {
    
        }
    
        //构造方法
        public MessageQueue( String path ) {
            //解析并得到所有的主题名字
            String[] topicNames = PropertiesUtil.gettopicNames( path );
            for ( String name : topicNames ) {
                map.put( name, new LinkedBlockingQueue<>() );
            }
        }
    
        //提供一个创建主题的方法
    //    public void createTopic( String topicName ) {
    //        //判断容器是否存在该主题
    //        if ( !map.containsKey( topicName ) ) {
    //            map.put( topicName, new LinkedBlockingQueue<NewsMessage>() );
    //        }
    //    }
    
        //将消息放入指定的消息队列中
        public void put( String topicName, NewsMessage message ) {
            try {
                //从map中取根据ey取出对应的主题队列
                BlockingQueue queue = map.get( topicName );
                //add方法继承ArrayBlockingQueue,不会产生阻塞
                //put方法,当queue满了就产生阻塞
                queue.put( message );
            } catch ( Exception e ) {
                e.printStackTrace();
            }
        }
    
        //从指定的主题队列中获取消息
        public NewsMessage take( String topicName ) {
            try {
                //从map中取根据ey取出对应的主题队列
                BlockingQueue queue = map.get( topicName );
                //当队列中没有消息,则会阻塞
                NewsMessage message = ( NewsMessage )queue.take();
                System.out.println( "消息队列大小为:" + queue.size() );
                return message;
            } catch ( Exception e ) {
                e.printStackTrace();
            }
            return null;
        }
    
        //移除主题的方法
        public void removeTopic( String topicName ) {
            map.remove( topicName );
        }
    }
    

    消息内容类:

    package org3.demo;
    
    /**
     * Created by Administrator on 2016/11/24.
     * 把发布的消息封装到一个类中
     */
    public class NewsMessage {
    
        //定义字符串变量
        private String content;
    
        //getter
        public String getContent() {
            return content;
        }
    
        //setter
        public void setContent(String content) {
            this.content = content;
        }
    }
    

    获取主题名字的工具类:

    package org3.demo;
    
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.Properties;
    
    /**
     * Created by Administrator on 2016/11/24.
     * 该工具类主要从topics.properties文件中获取主题名字
     */
    public class PropertiesUtil {
    
        //解析properties文件,获取所有主题名称
        public static String[] gettopicNames( String path ) {
            //创建一个Properties对象
            Properties pro = new Properties();
            try {
                //创建一个输出流读取properties文件
                InputStream fis = PropertiesUtil.class.getClassLoader().getResourceAsStream( path );
                //将输入流交给Properties对象进行读写
                pro.load( fis );
                //获取字符串
                String value = pro.getProperty( "topicNames" );
                //把字符串切割为字符串数组
                return split( value );
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        //分割字符串名称
        private static String[] split( String value ) {
            return value.split( "," );
        }
    
        //main方法测试
        public static void main(String[] args) {
            String value[] = PropertiesUtil.gettopicNames( "topics.properties" );
            for ( String v : value ) {
                System.out.println( v );
            }
        }
    }
    

    topics.properties文件,主要是用来存放全部主题名字:

    #配置所有主题的名字
    topicNames = news,sport
    

    发送者:

    package org3.demo;
    
    /**
     * Created by Administrator on 2016/11/24.
     */
    public class SendUser extends Thread {
    
        //定义MessageQueue成员变量
        private MessageQueue queue;
    
        public SendUser( MessageQueue queue ) {
            this.queue = queue;
        }
    
        public void run() {
            while ( true ) {
                //实例化消息内容对象
                NewsMessage message = new NewsMessage();
                //设置内容
                message.setContent( "hello message queue" );
                //将消息发送到指定的主题下面
                queue.put( "news", message );
                System.out.println( "发布了:" + message.getContent() );
                try {
                    //线程睡眠300毫秒
                    Thread.sleep( 300 );
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    接受者:

    package org3.demo;
    
    /**
     * Created by Administrator on 2016/11/24.
     */
    public class ReceiveUser extends Thread{
    
        //定义MessageQueue成员变量
        private MessageQueue queue;
    
        public ReceiveUser( MessageQueue queue ) {
            this.queue = queue;
        }
    
        public void run() {
            while ( true ) {
                //从消息队列中获取消息
                NewsMessage message = queue.take( "news" );
                System.out.println( "接受了:" + message.getContent() );
                try {
                    //线程睡眠500毫秒
                    Thread.sleep( 500 );
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    Main方法:

    package org3.demo;
    
    /**
     * Created by Administrator on 2016/11/24.
     */
    public class Main {
        public static void main(String[] args) {
            //创建一个消息队列
            MessageQueue queue = new MessageQueue( "topics.properties" );
            //给消息队列创建一个子主题
    //        queue.createTopic( "news" );
    
            //创建消息的发送者
            SendUser sender = new SendUser( queue );
            //创建消息的接收者
            ReceiveUser receiver = new ReceiveUser( queue );
    
            //启动两个线程
            sender.start();
            receiver.start();
        }
    }
    

    运行结果为:

    --------------------------------------------------------------------------------

  • 相关阅读:
    MSSQLSERVER数据库 C#里调用存储过程,多参数查询,个人记录
    ASP.NET GridView和Repeater合并单元格
    C# XPath教程
    MSSQLSERVER数据库 导入文本文件
    MSSQLSERVER数据库 递归查询例子
    C# TreeView右键弹出菜单
    tomcat 下War包部署方法
    JAVA自定义标签教程及实例代码
    JAVA tag学习
    Java Quartz 自动调度
  • 原文地址:https://www.cnblogs.com/hmake/p/6099583.html
Copyright © 2011-2022 走看看