Java设计模式之观察者模式

1、观察者模式的定义:

观察者模式(Observer),又叫发布-订阅模式(Publish/Subscribe),是一种行为型模式,定义对象间一对多的依赖关系(注册),使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新(通知)。说白了就是个注册和通知的过程。

实现观察者模式时要注意具体目标对象和具体观察者对象之间不能直接调用,否则将使两者之间紧密耦合起来,这违反了面向对象的设计原则。

2、观察者模式的主要角色如下:

1.抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。

2.具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。继承Subject类,在这里实现具体业务,在具体项目中,该类会有很多变种。

3.抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。

4.具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。

3、观察者模式的优缺点

优点:

1.降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。

2.目标与观察者之间建立了一套触发机制。

缺点:

1.目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。

2.当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。

4、观察者模式注意事项

1.JAVA 中已经有了对观察者模式的支持类。

2.避免循环引用。

3.如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。

4.异步处理线程安全问题。

被观察者发生动作了,观察者要做出回应,如果观察者比较多,而且处理时间比较长怎么办?那就用异步,异步处理就要考虑线程安全和队列的问题,这个大家有时间看看 Message Queue,就会有更深的了解。

下面分别通过自定义和java自带观察者模式的样例代码看一下观察者模式如何使用。

5、自定义观察者模式

需求:当公众号发布文章时,通知关注的用户

首先是抽象主题角色(一个接口和一个抽象类):

java 复制代码
package com.design.mode.observer.observer01;

/**
 * @Author: 倚天照海
 * @Description: 抽象主题角色
 */
public interface Subject {

    public void addObserver(MyObserver observer);

    public void removeObserver(MyObserver observer);

    public void notifyObserver(OfficialAccount account);

}

抽象主题角色(抽象类):

java 复制代码
package com.design.mode.observer.observer01;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @Author: 倚天照海
 * @Description: 抽象主题角色
 */
public abstract class AbstractSubject implements Subject {

    private List<MyObserver> observerList = Collections.synchronizedList(new ArrayList<MyObserver>());

    @Override
    public void addObserver(MyObserver observer){
        observerList.add(observer);
    }

    @Override
    public void removeObserver(MyObserver observer){
        observerList.remove(observer);
    }

    @Override
    public void notifyObserver(OfficialAccount account){
        for (MyObserver observer : observerList){
            observer.update(account);
        }
    }

    public abstract void publish(OfficialAccount account);

}

具体主题角色:

java 复制代码
package com.design.mode.observer.observer01;

/**
 * @Author: 倚天照海
 * @Description: java开发公众号,一个具体目标类
 */
public class JavaDevSubject extends AbstractSubject {

    @Override
    public void publish(OfficialAccount account){
        super.notifyObserver(account);
    }

}

抽象观察者角色:

java 复制代码
package com.design.mode.observer.observer01;

/**
 * @Author: 倚天照海
 * @Description:
 */
public interface MyObserver {

    void update(OfficialAccount account);

}

两个具体观察者角色:

java 复制代码
package com.design.mode.observer.observer01;

import java.util.List;

/**
 * @Author: 倚天照海
 * @Description: 用户1,一个具体观察者
 */
public class UserOne implements MyObserver {

    @Override
    public void update(OfficialAccount account) {
        String name = this.getClass().getName();
        List<String> articles = account.getArticles();
        String article = articles.get(articles.size() - 1);
        if ("Java开发".equals(account.getAccountName())){
            System.out.println(String.format("用户[%s]收到一篇Java开发公众号的文章,内容是:%s", name,article));
        } else if ("健康养生".equals(account.getAccountName())){
            System.out.println(String.format("用户[%s]收到一篇健康养生公众号的文章,内容是:%s", name,article));
        }
    }
}



package com.design.mode.observer.observer01;

import java.util.List;

/**
 * @Author: 倚天照海
 * @Description: 用户2,一个具体观察者
 */
public class UserTwo implements MyObserver {

    @Override
    public void update(OfficialAccount account) {
        String name = this.getClass().getName();
        List<String> articles = account.getArticles();
        String article = articles.get(articles.size() - 1);
        if ("Java开发".equals(account.getAccountName())){
            System.out.println(String.format("用户[%s]收到一篇Java开发公众号的文章,内容是:%s", name,article));
        }
    }
}

一个辅助实体类,用于表示公众号对象:

java 复制代码
package com.design.mode.observer.observer01;

import java.util.List;

/**
 * @Author: 倚天照海
 * @Description: 公众号实体类
 */
public class OfficialAccount {
    /**
     * 公众号id
     */
    private Integer accountId;
    /**
     * 公众号名称
     */
    private String accountName;
    /**
     * 公众号中发布的文章(应该将文章定义成一个对象,此处简写为字符串类型)
     */
    private List<String> articles;

    public Integer getAccountId() {
        return accountId;
    }

    public void setAccountId(Integer accountId) {
        this.accountId = accountId;
    }

    public String getAccountName() {
        return accountName;
    }

    public void setAccountName(String accountName) {
        this.accountName = accountName;
    }

    public List<String> getArticles() {
        return articles;
    }

    public void setArticles(List<String> articles) {
        this.articles = articles;
    }

    @Override
    public String toString() {
        return "OfficialAccount{" +
                "accountId=" + accountId +
                ", accountName='" + accountName + '\'' +
                '}';
    }
}

测试类:

java 复制代码
package com.design.mode.observer.observer01;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: 倚天照海
 * @Description:
 */
public class ObserverTest01 {

    public static void main(String[] args) {
        //创建被观察者
        AbstractSubject javaDevSubject = new JavaDevSubject();
        //创建观察者,并添加到被观察者中,以便被观察者发生变更时通知观察者
        MyObserver userOne = new UserOne();
        MyObserver userTwo = new UserTwo();
        javaDevSubject.addObserver(userOne);
        javaDevSubject.addObserver(userTwo);
        //发布信息,通知观察者
        OfficialAccount javaAccount = buildJavaAccount();
        javaDevSubject.publish(javaAccount);
    }

    private static OfficialAccount buildJavaAccount(){
        OfficialAccount account = new OfficialAccount();
        account.setAccountId(1);
        account.setAccountName("Java开发");
        String article = "java开发公众号发布了一篇文章,推送给各个订阅用户。";
        List<String> articles = new ArrayList<>();
        articles.add(article);
        account.setArticles(articles);
        return account;
    }

    private static OfficialAccount buildHealthAccount(){
        OfficialAccount account = new OfficialAccount();
        account.setAccountId(2);
        account.setAccountName("健康养生");
        String article = "健康养生公众号发布了一篇文章,推送给各个订阅用户。";
        List<String> healthArticles = new ArrayList<>();
        healthArticles.add(article);
        account.setArticles(healthArticles);
        return account;
    }

}

6、java自带观察者模式

在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。

1、Observable类

Observable 类是抽象目标类,它有一个 Vector 向量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。

void addObserver(Observer o) 方法:用于将新的观察者对象添加到向量中。

void notifyObservers(Object arg) 方法:调用向量中的所有观察者对象的 update() 方法,通知它们数据发生改变。通常越晚加入向量的观察者越先得到通知。

void setChange() 方法:用来设置一个 boolean 类型的内部标志位,注明目标对象发生了变化。当它为真时,notifyObservers() 才会通知观察者。

2、Observer 接口

Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 void update(Observable o,Object arg) 方法,进行相应的工作。

下面以原油期货价格上涨或下跌来模拟多头和空头双方观察者的行为。

需求:当原油价格上涨时,空方伤心,多方局兴;当油价下跌时,空方局兴,多方伤心。

创建具体主题角色:

java 复制代码
package com.design.mode.observer.observer02;

import java.util.Observable;

/**
 * @Author: 倚天照海
 * @Description: 具体主题角色,也就是具体被观察者
 */
public class OilFuturesObservable extends Observable {

    public void adjustPrice(Integer price){
        //在发生变化之后,必须调用这个方法告诉父类Observable发生了改变,
        //否则调用父类的notifyObservers方法是不会通知观察者的
        super.setChanged();
        //通知观察者
        super.notifyObservers(price);
    }

}

定义两个具体观察者角色:

java 复制代码
package com.design.mode.observer.observer02;

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

/**
 * @Author: 倚天照海
 * @Description: 原油期货多方实体类,一个具体观察者角色
 */
public class BullObserver implements Observer {

    @Override
    public void update(Observable o, Object arg) {
        Integer price = (Integer) arg;
        if (price > 0) {
            System.out.println("油价上涨" + price + "元,多方高兴了!");
        } else {
            System.out.println("油价下跌" + (-price) + "元,多方伤心了!");
        }
    }
}



package com.design.mode.observer.observer02;

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

/**
 * @Author: 倚天照海
 * @Description: 原油期货空方实体类,一个具体观察者角色
 */
public class BearObserver implements Observer {

    @Override
    public void update(Observable o, Object arg) {
        Integer price = (Integer) arg;
        if (price > 0) {
            System.out.println("油价上涨" + price + "元,空方伤心了!");
        } else {
            System.out.println("油价下跌" + (-price) + "元,空方高兴了!");
        }
    }
}

测试类:

java 复制代码
package com.design.mode.observer.observer02;

import java.util.Observer;

/**
 * @Author: 倚天照海
 * @Description:
 */
public class ObserverTest02 {

    public static void main(String[] args) {
        //创建具体目标类
        OilFuturesObservable observable = new OilFuturesObservable();
        //创建具体观察者类,并添加到目标类中
        Observer bullObserver = new BullObserver();
        Observer bearObserver = new BearObserver();
        observable.addObserver(bullObserver);
        observable.addObserver(bearObserver);
        //调整数据,发布通知
        observable.adjustPrice(10);
        observable.adjustPrice(-5);
    }

}
相关推荐
Cachel wood2 分钟前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
Code哈哈笑5 分钟前
【Java 学习】深度剖析Java多态:从向上转型到向下转型,解锁动态绑定的奥秘,让代码更优雅灵活
java·开发语言·学习
gb42152878 分钟前
springboot中Jackson库和jsonpath库的区别和联系。
java·spring boot·后端
程序猿进阶8 分钟前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot
zfoo-framework16 分钟前
【jenkins插件】
java
风_流沙21 分钟前
java 对ElasticSearch数据库操作封装工具类(对你是否适用嘞)
java·数据库·elasticsearch
ProtonBase1 小时前
如何从 0 到 1 ,打造全新一代分布式数据架构
java·网络·数据库·数据仓库·分布式·云原生·架构
乐之者v1 小时前
leetCode43.字符串相乘
java·数据结构·算法
大圣数据星球4 小时前
Fluss 写入数据湖实战
大数据·设计模式·flink