设计模式之观察者模式

一、定义

​ 观察者模式,又称发布-订阅模式、源-监听器模式和从属者模式。当一个对象的状态发生改变时,所有依赖它的对象均会接收到通知并被自动更新。

1、特点

(1)目标与观察者的关系:一对多

(2)单向依赖:只有目标知道什么时候通知观察者

(3)命名模式:目标接口定义后面跟subject,观察者接口定义后面跟observer,观察者接口的更新方法建议为update,方法的参数是根据需要定义的。

(4)触发通知的时机:先改变后通知

在这里插入图片描述

2、实现方式

在这里插入图片描述

(1)推模型:目标对象主动向观察者推送目标的详细信息,推送的信息通常是目标对象的全部或部分数据。

(2)拉模型:目标对象在通知的时候只传递少量信息,如果观察者需要更具体的信息,由观察者主动到目标对象获取,相当于是观察者主动在目标对象中拉数据。

3、两种方式的比较

(1)区别

​ ①“推”的方式是指,Subject维护一份观察者的列表,每当有更新发生,Subject会把更新消息主动推送到各个Observer去。

​ ②“拉”的方式是指,各个Observer维护各自所关心的Subject列表,自行决定在合适的时间去Subject获取相应的更新数据。

(2)两种方式的优缺点
1)推模型

​ 所有的观察者都会直接得到全部的消息,并进行相应的处理程序,与主体对象没什么关系,两者之间的关系是一种松散耦合。

①优点
  • 高效。如果没有更新发生,不会有任何更新消息推送的动作,即每次消息推送都发生在确确实实的更新事件之后,都是有意义的。
  • 实时。事件发生后的第一时间即可触发通知操作。
  • 可以由Subject确立通知的时间,可以避开一些繁忙时间。
  • 可以表达出不同事件发生的先后顺序。
②缺点
  • 所有观察者得到的消息均一样,也许有些信息对某个观察者来说根本用不上,即:观察者不能“按需所取”。
  • 当通知消息的参数有变化时,所有的观察者对象都要变化。
2)拉模型
  • 如果观察者众多,Subject来维护订阅者的列表,可能困难,或者臃肿,把订阅关系解脱到Observer去完成。
  • Observer可以不理会它不关心的变更事件,只需要去获取自己感兴趣的事件即可。
  • Observer可以自行决定获取更新事件的时间。
  • 拉的形式可以让Subject更好地控制各个Observer每次查询更新的访问权限。

4、优缺点

(1)优点

​ ①实现了观察者和目标之间的抽象耦合。

​ ②实现了动态联动。

​ ③支持广播通信。

(2)缺点

​ ①如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。

​ ②如果观察者和观察目标之间有循环依赖,观察目标会触发它们进行循环调用,可能导致系统崩溃。

​ ③观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

​ ④如果对观察者的通知是通过另外的线程进行异步投递的话,系统必须保证投递是以自恰的方式进行的。

二、使用场景

1、应用场景

在这里插入图片描述

(1)商城产品更新

​ 一个商家有一些产品和一些电商合作,每当有新产品时,就会把这些产品推送到电商,例如淘宝、京东等。

(2)Wiki空间

​ 当我们关注了Wiki中感兴趣的人或事,我们这些关注者便会随时接收到变更消息。

(3)银行办理业务

​ 去银行办理业务,当人多的时候,需要按顺序取号等待。直到银行叫到自己的号时,才轮到自己去进行办理。

(4)订单

​ 当订单状态发生改变,即:关闭时,订单关联的工单和事件 ,便可以关闭了;否则 ,如果订单状态是开启的, 则不允许关闭对应的工单和事件。

(5)MQ消息队列
1)特点
  • 一条消息可以被多个消费者接收。
  • 消息被消费后,默认持久化,消息还存在。
  • 消息必须先被监听,才能收到消息。

在这里插入图片描述

2)MQ中的推拉模型
①推模型
  • 推模式是服务器端根据用户需要,由目的、按时将用户感兴趣的信息主动发送到用户的客户端
  • Push模式的主要优点
    • 对用户要求低,方便用户获取需要的信息
    • 及时性好,服务器端即使地向客户端推送更行的动态信息
  • Push模式的缺点
    • 不能确保发送成功。Push模式采用广播方式,只有服务器端和客户端在同一个频道上,推模式才有效,用户才能接收到信息
    • 没有信息状态跟踪。Push模式采用开环控制技术,一个信息推送后的状态,比如客户端是否接收等,无从得知
    • 针对性较差。推送的信息可能并不能满足客户端的个性化需求。
②拉模型
  • 拉模式是客户端主动从服务器端获取信息
  • 拉模式的主要优点
    • 针对性强,能满足客户端的个性化需求
    • 信息传输量较小,网络中传输的知识客户端的请求和服务器端对该请求的响应
    • 服务器端的任务轻。服务器端只是被动接收查询,对客户端的查询请求做出响应
  • 拉模式的缺点
    • 实时较差,针对于服务器端实时更新的信息,客户端难以获取实时信息
    • 对于客户端用户的要求较高,需要对服务器端具有一定的了解。

2、代码举例

(1)推模型:
电商的应用

一个商家有一些产品和一些电商合作,每当有新产品时,就会把这些产品推送到电商,例如淘宝、京东等。

①定义一个被观察者:产品列表ProductList,继承了Observable

import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

public class ProductListSubject extends Observable {

    private List<String> productList = null;//产品列表

    private static ProductListSubject instance;//类唯一实例

    private ProductListSubject() {}//构建方法私有化

    /**
     * 取得唯一实例
     * @return 产品列表唯一实例
     */
    public static ProductListSubject getInstance() {
        if (instance == null) {
            instance = new ProductListSubject();
            instance.productList = new ArrayList<String>();
        }
        return instance;
    }

    /**
     * 增加观察者(电商接口)
     * @param observer 观察者
     */
    public void addProductListObserver(Observer observer) {
        this.addObserver(observer);
    }

    /**
     *  新增产品
     * @param newProduct 新产品
     */
    public void addProudct(String newProduct) {
        productList.add(newProduct);
        System.err.println("产品列表新增了产品:"+newProduct);
        this.setChanged();//设置被观察对象发生变化
        this.notifyObservers(newProduct);//通知观察者,并传递新产品
    }
}

②观察者1:淘宝商城TaoBaoObserver

import java.util.Observable;
import java.util.Observer;

public class TaoBaoObserver implements Observer {

    @Override
    public void update(Observable o, Object product) {
        String newProduct = (String) product;
        System.err.println("发送新产品【"+newProduct+"】同步到淘宝商城");
    }
}

③观察者2:京东商城JingDongObserver

import java.util.Observable;
import java.util.Observer;

public class JingDongObserver implements Observer {
    @Override
    public void update(Observable o, Object product) {
        String newProduct = (String) product;
        System.err.println("发送新产品【"+newProduct+"】同步到京东商城");
    }
}

④然后,测试代码:

public class ObserverTest {

    public static void main(String[] args) {
        ProductListSubject observable = ProductListSubject.getInstance();
        TaoBaoObserver taoBaoObserver = new TaoBaoObserver();
        JingDongObserver jdObserver = new JingDongObserver();
        observable.addObserver(jdObserver);
        observable.addObserver(taoBaoObserver);
        observable.addProudct("新增产品1");
    }
}

⑤最后,结果展示:

在这里插入图片描述

(2)拉模型
报社应用

报社有新报纸后马上通知用户。

各个Observer维护各自所关心的Subject列表,自行决定在合适的时间去Subject获取相应的更新数据。

①自定义一个被观察者类Subject

public class Subject {
    private List<Observer> observers = new ArrayList<Observer>();

    public void attach(Observer observer){
        observers.add(observer);
    }

    public void detach(Observer observer){
        observers.remove(observer);
    }

    public void notifyObservers(){
        for (Observer observer : observers) {
            observer.update(this);
        }
    }
}

②继承自定义Subject,实现一个被观察者类

public class ConcreteSubject extends Subject{
    private String subjectState;

    public String getSubjectState() {
        return subjectState;
    }

    public void setSubjectState(String subjectState) {
        this.subjectState = subjectState;
        notifyObservers();
    }
}

③自定义观察者接口Observer

interface Observer {
    public void update(Subject subject);
}

④定义了观察者ConcreteObserver

public class ConcreteObserver implements Observer{
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void update(Subject subject) {
        System.out.println(name + "状态:" + ((ConcreteSubject)subject).getSubjectState());
    }
}

⑤测试代码,定义了三个观察者

public class MainTest {
    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject();
        ConcreteObserver observer1 = new ConcreteObserver();
        observer1.setName("张三");

        ConcreteObserver observer2 = new ConcreteObserver();
        observer2.setName("李四");

        ConcreteObserver observer3 = new ConcreteObserver();
        observer3.setName("王二");

        subject.attach(observer1);
        subject.attach(observer2);
        subject.attach(observer3);

        subject.setSubjectState("看完报纸");
    }
}

⑥测试结果

在这里插入图片描述

(3)消息队列MQ

生产者:

public class ProducerFanout {

    private static final String EXCHANGE_NAME = "fanout_exchange";

    public static void main(String[] args) throws IOException, TimeoutException {
        /** 1.创建新的连接 */
        Connection connection = MQConnectionUtils.newConnection();
        /** 2.创建通道 */
        Channel channel = connection.createChannel();
        /** 3.绑定的交换机 参数1交互机名称 参数2 exchange类型 */
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
        /** 4.发送消息 */
        for (int i = 0; i < 10; i++)
        {
            String message = "用户注册消息:" + i;
            System.out.println("[send]:" + message);
            //发送消息
            channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("utf-8"));
            try {
                Thread.sleep(5 * i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        /** 5.关闭通道、连接 */
        channel.close();
        connection.close();
        /** 注意:如果消费没有绑定交换机和队列,则消息会丢失 */
    }

}

邮件消费者:

public class ConsumerEmailFanout {

    private static final String QUEUE_NAME = "consumerFanout_email";
    private static final String EXCHANGE_NAME = "fanout_exchange";

    public static void main(String[] args) throws IOException, TimeoutException {
        System.out.println("邮件消费者启动");
        /* 1.创建新的连接 */
        Connection connection = MQConnectionUtils.newConnection();
        /* 2.创建通道 */
        Channel channel = connection.createChannel();
        /* 3.消费者关联队列 */
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        /* 4.消费者绑定交换机 参数1 队列 参数2交换机 参数3 routingKey */
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
                    throws IOException {
                String msg = new String(body, "UTF-8");
                System.out.println("消费者获取生产者消息:" + msg);
            }
        };
        /* 5.消费者监听队列消息 */
        channel.basicConsume(QUEUE_NAME, true, consumer);
    }

}

短信消费者:

public class ConsumerSMSFanout {

    private static final String QUEUE_NAME = "ConsumerFanout_sms";
    private static final String EXCHANGE_NAME = "fanout_exchange";

    public static void main(String[] args) throws IOException, TimeoutException {
        System.out.println("短信消费者启动");
        /* 1.创建新的连接 */
        Connection connection = MQConnectionUtils.newConnection();
        /* 2.创建通道 */
        Channel channel = connection.createChannel();
        /* 3.消费者关联队列 */
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        /* 4.消费者绑定交换机 参数1 队列 参数2交换机 参数3 routingKey */
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
                    throws IOException {
                String msg = new String(body, "UTF-8");
                System.out.println("消费者获取生产者消息:" + msg);
            }
        };
        /* 5.消费者监听队列消息 */
        channel.basicConsume(QUEUE_NAME, true, consumer);
    }

}

运行结果:

生产者

在这里插入图片描述

短信消费者

在这里插入图片描述

邮件消费者

在这里插入图片描述

三、java源码剖析

观察者接口:Observer

public interface Observer {
    void update(Observable o, Object arg);
}

被观察者类:Observable

public class Observable {
    private boolean changed = false;
    private Vector<Observer> obs;

    /** Construct an Observable with zero Observers. */

    public Observable() {
        obs = new Vector<>();
    }

    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }

    public void notifyObservers() {
        notifyObservers(null);
    }

    public void notifyObservers(Object arg) {
        /*
         * a temporary array buffer, used as a snapshot of the state of
         * current Observers.
         */
        Object[] arrLocal;

        synchronized (this) {
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }

        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }

    /**
     * Clears the observer list so that this object no longer has any observers.
     */
    public synchronized void deleteObservers() {
        obs.removeAllElements();
    }

    /**
     * Marks this <tt>Observable</tt> object as having been changed; the
     * <tt>hasChanged</tt> method will now return <tt>true</tt>.
     */
    protected synchronized void setChanged() {
        changed = true;
    }

    protected synchronized void clearChanged() {
        changed = false;
    }

    public synchronized boolean hasChanged() {
        return changed;
    }

    /**
     * Returns the number of observers of this <tt>Observable</tt> object.
     *
     * @return  the number of observers of this object.
     */
    public synchronized int countObservers() {
        return obs.size();
    }
}

四、学习进展

学习内容JavaJavaspring
第 一 周Java设计模式(5种)和排序算法(3种)
第 二 周Java设计模式(5种)和排序算法(3种)Java基础章节和集合部分,以及通过牛客网练习Java基础题
第 三 周Java设计模式(5种)和排序算法(3种)Java基础章节和集合部分,以及通过牛客网练习Java基础题通过搭建maven项目,集成tomcat服务器,练习spring的常用注解以及xml方式
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值