【设计模式】观察者模式:被观察者发生变化不应该是被观察者来通知吗?

什么是观察者模式(Observer)

概念

观察者模式(Observer)又称发布-订阅模式,属于行为型模式,研究对象之间的交互。观察者模式定义了对象间一对多的依赖关系,当被观察者对象发生改变时,所有依赖于它的对象都会得到通知并完成自动更新。观察者模式是为了解决一个对象想知道另一个对象状态变化的问题。

当我们处于一对多情况,比如微信公众号和用户,多个用户订阅了一个公众号,用户想要知道公众号是否更新 ,这时候有两种方式可以获取到公众号的更新,第一种是poll方式,也就是拉取,每个用户都去发送请求,看看公众号是否更新了内容,如果更新了再发出获取最新内容的请求,这样的话,就需要每个用户都定时地高频地请求查看公众号是否有新的内容,给服务器带来了非常大的压力;另外一种方式是push方式,也就是观察者模式,每个用户不需要发送请求去查看公众号是否有新内容,而是等待公众号发布新内容后,公众号再向用户发送请求,说内容已经更新了,快点发个请求过来获取新的内容,最后用户才发送请求去获取新的内容。

本公众号已安装空调

还有其他的很多例子,等车大厅,我们不需要一直看着大屏幕,等待广播就可以;煲饭、煮水也是,我们只需要等待叮的一声。

优点

  1. 制定了观察者和被观察者的行为机制。
  2. 释放了观察者和被观察者的压力,观察者不需要再去高频率地定时地请求被观察者了。

缺点

  1. 当观察者对象过多时,通知所有观察者会浪费大量的时间。
  2. 需要防止观察者和被观察者之间的循环依赖,出现循环依赖会导致系统崩溃。
  3. 观察者模式只能让观察者知道被观察者的变化,不能知道被观察者是怎么变化的。

原则

“+”代表遵守,“-”代表不遵守或者不相关

原则开放封闭单一职责迪米特里氏替换依赖倒置接口隔离合成复用
-+--+--

适用场景

  1. 对象间存在一对多的关系,一个对象的状态变化会影响其他对象。
  2. 需要实现类似广播的机制。
知道了

如何实现

想要实现观察者模式,需要以下四样东西:

  1. 主题(Subject)抽象类:提供增加、删除、通知观察者对象的方法,以及定义一个对象来保存观察者。
  2. 主题类(Concrete Subject):也可以成为被观察者,实现主题抽象类,当主题内部发生改变时通知观察者。
  3. 观察者接口:定义观察者的行为,需要有一个跟新自己的方法。
  4. 观察者类:实现观察者接口。

类图

观察è€æ¨¡å¼çš„结构图

这里的具体目标实现抽象目标,需要实现notifyObserver方法,遍历触发观察者的方法,观察者在抽象目标那就做好了聚合关系,具体观察者实现观察者接口,需要各自实现自己的response方法,完成读取最新状态后的更新。

例子

这里以微信公众号为例子,这里需要公众号(主题对象)、用户(观察者)。

类图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1agAagUA-1633180127364)(C:\Users\xuxiaobai\AppData\Roaming\Typora\typora-user-images\image-20210614141655969.png)]

这个类图有点多,类之间的关系也有点复杂。

IObserver和AbstractSubject呢定义了在观察者模式中需要用到的行为,IWeChatUser和AbstractWeChatSubject分别定义了在微信中用户(观察者)和公众号(主题)应该有的行为。取消了主题对象添加观察者这个操作,而是通过观察者自身去follow一个主题对象,然后再让主题对象增加自身到List中。其他的发布操作就是跟观察者模式规定的一样了。

代码

用户

观察者接口

/**
 *
 * 观察者接口
 * 定义一个观察者应该有的方法
 * Created on 2021/6/13.
 *
 * @author xuxiaobai
 */
public interface IObserver {
    /**
     * 响应
     */
    void update();

}

微信用户接口

/**
 *
 * 微信用户接口
 * 定义用户使用的方法
 * Created on 2021/6/14.
 *
 * @author xuxiaobai
 */
public interface IWeChatUser {

    /**
     * 关注公众号
     * 这里采用用户来关注公众号
     * @param subscription
     */
    void follow(AbstractWeChatSubject subscription) ;

    /**
     * 取消关注
     */
    void unfollow(AbstractWeChatSubject subscription);


    /**
     * 获取用户名
     * @return
     */
    String getUserName();

    /**
     * 改名
     */
    void setUserName(String newName,String password);
}

微信用户

/**
 * 观察者
 * 用户
 * Created on 2021/6/13.
 *
 * @author xuxiaobai
 */
public class UserObserver implements IObserver,IWeChatUser{

    /**
     * 添加到那个公众号了,更新时需要使用
     * 这里是依赖于抽象
     */
    private AbstractWeChatSubject subscription;

    //用户名
    private String name;

    /**
     * 密码
     * 改密码的功能就不写了
     */
    private String password;


    public UserObserver(String name,String password){
        this.name=name;
        this.password=password;
    }



    @Override
    public void update() {
        if (this.subscription==null) return;
        //触发跟新后,请求公众号获取最新内容
        String currentArticle = this.subscription.getCurrentArticle();
        System.out.println("<"+this.name);
        System.out.println("----"+this.subscription.getName()+"的最新文章----");
        System.out.println(currentArticle);
        System.out.println(this.name+">");


    }


    /**
     * 取关
     * @param subscription
     */
    @Override
    public void unfollow(AbstractWeChatSubject subscription) {
//        if (subscription==this.subscription){
//            subscription.remove(this);
//            this.subscription=null;
//        }
        System.out.println("这么好的公众号取关干嘛");
    }

    /**
     * 关注公众号
     * @param subscription
     * @return
     */
    @Override
    public void follow(AbstractWeChatSubject subscription) {
        this.subscription = subscription;
        subscription.add(this);
        System.out.println("关注成功!");
    }

    /**
     * 获取当前用户名称
     * @return
     */
    public String getUserName() {
        return this.name;
    }

    @Override
    public void setUserName(String newName, String password) {
        System.out.println("<"+this.name);
        System.out.println("修改名称。。");
        try {
            if (this.password.equals(password)){
                this.name=newName;
                System.out.println("修改成功!");
                return;
            }
            System.out.println("密码错误!");
        }finally {
            System.out.println(this.name+">");
        }

    }
}
主题/公众号

主题抽象类

/**
 * 微信公众号抽象类
 * 所有微信公众号都要实现这个抽象类
 * Created on 2021/6/14.
 *
 * @author xuxiaobai
 */
public abstract class AbstractWeChatSubject extends AbstractSubject{

    //当前微信公众号名称
    protected String name;
    /**
     * 当前最新文章,可以当作本类的状态
     *
     * 可以考虑再增加一个list把过往的文章都保存下来,
     * private List<String>  articles=new ArrayList<>();
     * 然后增加一个getHistory的方法,供用户调用,可以查看过往的文章
     * 我这里就不作演示了
     */
    protected String currentArticle;

    public AbstractWeChatSubject(String name){
        this.name=name;
        System.out.println("注册公众号,名称为"+name);
    }
    /**
     * 获取最新文章
     * @return
     */
    abstract String getCurrentArticle();

    /**
     * 获取公众号名称
     * @return
     */
    abstract String getName();

    /**
     * 发布文章
     * @param article
     */
    abstract void updateArticle(String article);
}

微信主题抽象类

/**
 * 微信公众号抽象类
 * 所有微信公众号都要实现这个抽象类
 * Created on 2021/6/14.
 *
 * @author xuxiaobai
 */
public abstract class AbstractWeChatSubject extends AbstractSubject{

    //当前微信公众号名称
    protected String name;

    public AbstractWeChatSubject(String name){
        this.name=name;
        System.out.println("注册公众号,名称为"+name);
    }
    /**
     * 获取最新文章
     * @return
     */
    abstract String getCurrentArticle();


    /**
     * 获取公众号名称
     * @return
     */
    abstract String getName();

    /**
     * 发布文章
     * @param article
     */
    abstract void updateArticle(String article);
}

微信公众号

/**
 * 主题类
 * 微信公众号
 * Created on 2021/6/13.
 *
 * @author xuxiaobai
 */
public class WeChatSubscription extends AbstractWeChatSubject{

    //是否有变化标志
    private boolean isChange = false;

    


    public WeChatSubscription(String name){
        super(name);
    }
    /**
     * 跟新文章
     * @param article
     */
    @Override
    public void updateArticle(String article){
        super.currentArticle=article;
        this.isChange=true;
        System.out.println("当前公众号("+this.name+")更新文章");
        //跟新完成后推送
        notifyObserver();
    }

    /**
     * 获取最新文章
     * @return
     */
    @Override
    public String getCurrentArticle(){
        return super.currentArticle;
    }

    @Override
    public void notifyObserver() {
        //可以开启一个线程来处理
        if (this.isChange) {
            System.out.println("开始推送。。。。");
            this.isChange=false;
            //当改变时才通知观察者,推送文章
            observers.forEach((item) -> {
                item.update();
            });
            System.out.println("-----推送完成-----");
            return;
        }
        System.out.println("未检测到新文章,请重试!");
    }

    @Override
    public void additionalOperationsInAdd(IObserver observer) {
        /**
         * 转个型后调用获取名称的方法
         */
        System.out.println("新增粉丝:"+((UserObserver) observer).getUserName());
    }

    @Override
    public String getName(){
        return this.name;
    }
}
测试
/**
 * Created on 2021/6/13.
 *
 * @author xuxiaobai
 */
public class ObserverTest {
    public static void main(String[] args) {
        AbstractWeChatSubject subject=new WeChatSubscription("程序员徐小白");
        IWeChatUser user1=new UserObserver("小明同学","123");
        IWeChatUser user2=new UserObserver("小强同学","123");
        user1.setUserName("小明","123");
        user1.follow(subject);
        subject.updateArticle("感觉大家的关注!");
        user2.follow(subject);
        subject.updateArticle("第二篇文章");

        /**
         * 结果:
         * 注册公众号,名称为程序员徐小白
         * <小明同学
         * 修改名称。。
         * 修改成功!
         * 小明>
         * 新增粉丝:小明
         * 关注成功!
         * 当前公众号(程序员徐小白)更新文章
         * 开始推送。。。。
         * <小明
         * ----程序员徐小白的最新文章----
         * 感觉大家的关注!
         * 小明>
         * -----推送完成-----
         * 新增粉丝:小强同学
         * 关注成功!
         * 当前公众号(程序员徐小白)更新文章
         * 开始推送。。。。
         * <小明
         * ----程序员徐小白的最新文章----
         * 第二篇文章
         * 小明>
         * <小强同学
         * ----程序员徐小白的最新文章----
         * 第二篇文章
         * 小强同学>
         * -----推送完成-----
         *
         */
    }
}

要赞真香

差不多三百多行的代码了,看到这了还不给点个赞吗?

这里的代码还有待补充,这里只是用来演示观察者模式,如果你有兴趣的话可以拷贝然后自己做一些补充。

总结

在Java中也有一个观察者接口:java.util.Observer,直接使用这个接口也是可以的,不过自己写的话会比较合适自己的需求。

个人感觉观察者模式和中介者模式有点相似,都是把接收消息的对象注册到一个中间对象上,这个中间对象接收到消息后,再转发给接收消息的对象,只不过中介者模式是处理对象多对多的关系,而观察者模式是处理对象一对多的关系。

在使用观察者模式时,需要注意比秒观察者和被观察者的循环依赖,会导致系统卡死,还有就是被观察者通知观察者这个操作可以采用异步的方式来处理,否者遍历所有观察者会让被观察者卡在这一步。

小黄鸡蹦蹦跳

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员徐小白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值