设计模式就该这么学:以微信订阅号来讲观察者模式(第三篇)

前言:继续《设计模式就该这么学》系列文章,今天以当前比较火的微信订阅号给大家介绍应用得比较多的一种设计模式——观察者模式,之后再来介绍java拉模型方式的内置设计模式实现,最后附带一个项目实际观察者应用的例子!

 《设计模式就该这么学》系列文章:

设计模式就该这么学:为什么要学设计模式?(开篇漫谈)

设计模式就该这么学:要走心才能遵循设计模式五大原则(第二篇)

设计模式就该这么学:以微信订阅号来讲观察者模式(第三篇)

观察者模式实际应用:监听线程,意外退出线程后自动重启

一. 什么是观察者模式

以《Head First 设计模式》这本书中的定义:

 观察者模式:它定义了对象之间的一(Subject)对多(Observer)的依赖,这样一来,当一个对象(Subject)改变时,它的所有的依赖者都会收到通知并自动更新。   

首先看下观察者模式的类图

  • 主题(Subject)接口:对象使用此接口注册为观察者,或者把自己从观察者中移除。
  • 观察者(Observer)接口:所有潜在的观察者都必须实现该接口,这个接口只有update一个方法,他就是在主题状态发生变化的时候被调用。
  • 具体主题(ConcreteSubject)类:它是要实现Subject接口,除了注册(registerObserver)和撤销(removeObserver)外,它还有一个通知所有的观察者(notifyObservers)方法。
  • 具体观察者(ConcreteObserver)类:它是要实现ObserverJ接口,并且要注册主题,以便接受更新。

二、以微信订阅号来深入介绍观察者模式

看了上面定义及类图好像不太容易理解,微信订阅号我相信大家都不陌生,接下来就微信订阅号的例子来介绍下观察者模式。首先看下面一张图:

如上图所示,微信订阅号就是我们的主题,用户就是观察者。他们在这个过程中扮演的角色及作用分别是:

  1. 订阅号就是主题,业务就是推送消息
  2. 观察者想要接受推送消息,只需要订阅该主题即可
  3. 当不再需要消息推送时,取消订阅号关注即可
  4. 只要订阅号还在,观察者可以一直去进行关注

接下来让我们通过一段示例,三位同事zhangsai、liyong、liujing订阅人民日报订阅号为例来介绍观察者模式(Obsever),代码如下:

//1、人民日报接口
   public interface PeoplesDaily
   {
       //添加订阅者
       void RegisterObserver(Observer observer);
       //取消订阅
       void RemoveObserver(Observer observer);
       //发送人民日报
       void notifyObservers();
   }

   //2、订阅者接口
   public interface Observer
   {
       //有新的人民日报了就会被执行通知
       void update();
   }

   //3、人民日报
   public class PeopleNewsPaper implements PeoplesDaily
   {
       private List<Observer> subList = new List<Observer>();
       public void RegisterObserver(Observer observer)
       {
           subList.Add(observer);
       }

       public void RemoveObserver(Observer observer)
       {
           if (subList.IndexOf(observer) >= 0)
           {
               subList.Remove(observer);
           }
       }

       //推送人民日报消息了~~
       public void notifyObservers()
       {
           for (Observer sub : subList)
           {
               sub.update();
           }
       }
   }

  //4、订阅者
   public class subHuman implements Observer
   {
       //订阅者的名字
       private string name;

       public subHuman(string f_name)
       {
           name = f_name;
       }
       //通知订阅者有新人民日报推送消息了
       public void update()
       {
           system.out.println(p_name + "!! 有新的人民日报消息了,请查收!");
       }

   }

   //5、测试开始订阅,和调用了
  public static void Main(string[] args)
        {
            PeopleNewsPaper paper = new PeopleNewsPaper();
            subHuman zhsangsai = new subHuman("张赛");
            subHuman liyong = new subHuman("李勇");
            subHuman liujin = new subHuman("刘晶");
            //张赛订阅人民日报
            paper.RegisterObserver(zhsangsai);
            //李勇订阅人民日报
            paper.RegisterObserver(liyong);
            //刘晶订阅人民日报
            paper.RegisterObserver(liujin);
            //有新人民日报推送消息了
            paper.notifyObservers();
            system.out.println("---------------发完人民日报了------------------");

            //张赛不想订了,取消人民日报
            paper.RemoveObserver(zhsangsai);
            //又有新人民日报了  就没有张赛的人民日报 了
            paper.notifyObservers();
        }

测试结果:

张赛!! 有新的人民日报消息了,请查收!

李勇!! 有新的人民日报消息了,请查收!

刘晶!! 有新的人民日报消息了,请查收!

---------------发完人民日报了------------------
张赛!! 有新的人民日报消息了,请查收!

李勇!! 有新的人民日报消息了,请查收!

  

三、再来说设计模式的推拉模型

在观察者模式中,又分为推模型和拉模型两种方式。

  • 推模型:主题对象向观察者推送主题的详细信息,不管观察者是否需要,每次有新的信息就会推送给它的所有的观察者。
  • 拉模型:主题对象是根据观察者需要更具体的信息,由观察者主动到主题对象中获取,相当于是观察者从主题对象中拉数据。

而它们的区别在于:

“推”的好处包括:
1、高效。如果没有更新发生,不会有任何更新消息推送的动作,即每次消息推送都发生在确确实实的更新事件之后,所以这种推送是有意义的。
2、实时。事件发生后的第一时间即可触发通知操作。
“拉”的好处包括:
1、如果观察者众多,那么主题要维护订阅者的列表臃肿,把订阅关系解脱到Observer去完成,什么时候要自己去拉数据就好了。
2、Observer可以不理会它不关心的变更事件,只需要去获取自己感兴趣的事件即可。

根据上面的描述,发现前面的例子就是典型的推模型,下面我先来介绍下java内置的拉模型设计模式实现,再给出一个拉模型的实例。

在JAVA编程语言的java.util类库里面,提供了一个Observable类以及一个Observer接口,用来实现JAVA语言对观察者模式的支持。
  Observer接口:这个接口代表了观察者对象,它只定义了一个方法,即update()方法,每个观察者都要实现这个接口。当主题对象的状态发生变化时,主题对象的notifyObservers()方法就会调用这一方法。

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

 Observable类:这个类代表了主体对象,主题对象可以有多个观察者,主题对象发生变化时,会调用Observable的notifyObservers()方法,此方法调用所有的具体观察者的update()方法,从而使所有的观察者都被通知更新自己

package java.util;

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

    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) {

        Object[] arrLocal;

        synchronized (this) {

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

        //调用Observer类通知所有的观察者
        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }

    public synchronized void deleteObservers() {
        obs.removeAllElements();
    }

    protected synchronized void setChanged() {
        changed = true;
    }

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

    //省略......
}

接下来再介绍我用java这种内置的观察者设计模式在项目中的一个实际应用,详细请看我的这篇博文:观察者模式实际应用:监听线程,意外退出线程后自动重启

这里只介绍下思路:

项目场景:用户那边会不定期的上传文件到一个ftp目录,我需要实现新上传的文件做一个自动检测,每次只要有文件新增,自动解析新增文件内容入库,并且要保证该功能的稳定性!!

实现思路

1、监听器初始化创建:首先在tomcat启动的时候,利用监听器初始化创建一个监控文件新增线程,如下:  

@Component
public class ThreadStartUpListenser implements ServletContextListener
{
    //监控文件新增线程
    private static WatchFilePathTask r = new WatchFilePathTask();

    private Log log = LogFactory.getLog(ThreadStartUpListenser.class);

    @Override
    public void contextDestroyed(ServletContextEvent paramServletContextEvent)
    {
        // r.interrupt();

    }

    @Override
    public void contextInitialized(ServletContextEvent paramServletContextEvent)
    {
        //将监控文件类添加为一个观察者,并启动一个线程
        ObserverListener listen = new ObserverListener();
        r.addObserver(listen);
        new Thread(r).start();
        // r.start();
        log.info("ImportUserFromFileTask is started!");
    }

}
     

2、主体对象:即下面的监控文件新增类WatchFilePathTask ,每次有新文件进来,自动解析该文件,挂掉之后,调用动doBusiness()里面的notifyObservers()方法,伪代码如下:

 

//继承java内置观察者模式实现的Observable 类
public class WatchFilePathTask extends Observable implements Runnable
{
    private Log log = LogFactory.getLog(WatchFilePathTask.class);

    private static final String FILE_PATH = ConfigUtils.getInstance()
            .getValue("userfile_path");

    private WatchService watchService;

    /**
     * 此方法一经调用,立马可以通知观察者,在本例中是监听线程
     */
    public void doBusiness()
    {
        if (true)
        {
            super.setChanged();
        }
        notifyObservers();
    }

    @Override
    public void run()
    {
        try
        {
            //这里省略监控新增文件的方法
        }catch (Exception e)
        {
            e.printStackTrace();

            doBusiness();// 在抛出异常时调用,通知观察者,让其重启线程
        }
    }
}

3、观察者对象:即上面出现的ObserverListener类,当主题对象的的notifyObservers()方法被调用的时候,就会调用该类的update()方法,伪代码如下:

//实现java内置观察者模式实现的Observer接口,并且注册主题WatchFilePathTask,以便线程挂掉的时候,再重启这个线程
public class ObserverListener implements Observer
{
    private Log log = LogFactory.getLog(ObserverListener.class);

    /**
     * @param o
     * @param arg
     */
    public void update(Observable o, Object arg)
    {
        log.info("WatchFilePathTask挂掉");
        WatchFilePathTask run = new WatchFilePathTask();
        run.addObserver(this);
        new Thread(run).start();
        log.info("WatchFilePathTask重启");
    }
}

关于这个例子更多详细实现,请查看我的这篇文章:观察者模式实际应用:监听线程,意外退出线程后自动重启

 

   

学习本就是一个不断模仿、练习、再到最后面自己原创的过程。

虽然可能从来不能写出超越网上通类型同主题博文,但为什么还是要写?
于自己而言,博文主要是自己总结。假设自己有观众,毕竟讲是最好的学(见下图)。

于读者而言,笔者能在这个过程get到知识点,那就是双赢了。
当然由于笔者能力有限,或许文中存在描述不正确,欢迎指正、补充!
感谢您的阅读。如果本文对您有用,那么请点赞鼓励。

  

原文地址:https://www.cnblogs.com/zishengY/p/8970961.html

时间: 2024-12-17 11:10:08

设计模式就该这么学:以微信订阅号来讲观察者模式(第三篇)的相关文章

微信订阅号开发笔记(三)

1.接收语音识别结果 if($msgType=="voice"){ //收到语音消息 //MediaId 语音消息媒体id,可以调用多媒体文件下载接口拉取数据. //Format 语音格式,如amr,speex等 $format = $postObj->Format; $mediaId = $postObj->MediaId; //开通语音识别功能,用户每次发送语音给公众号时,微信会在推送的语音消息XML数据包中,增加一个Recongnition字段. //注:由于客户端缓

微信订阅号的关注和消息推送中的观察者模式

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,主体对象的状态变化会通知所有观察者对象.观察者模式又叫做发布-订阅模式.模型-视图模式.源-监听器模式或从属者模式.这种模式在我们实际生活中并不鲜见,比如订牛奶.订报纸.我们订阅了某报纸之后,一旦报纸有新版出来,就会送到我们报箱或手中,去过取消订阅,那么也就再也收不到了.有了互联网之后,无论是微博好友还是微信订阅号,我们都可以"关注"和"取消关注",关注了就可以收到信息推动.这些都是观察者

腾讯员工内部培训:微信订阅号运营从入门到精通

这篇文章算是对微信运营的小小总结了,说不上是经验,毕竟一万个哈姆雷特有一万种活法.这篇文章从定位.运营(内容运营.用户运营.微信元素拆解).推广.工具.公众号推荐几个方面来总结微信运营的一些规律. 1.定位 在开始运营一个微信公众号之前可以从以下三个维度来思考定位 1.1.用户定位:搞清楚目标用户是谁,目标用户的特征是什么,做用户画像 1.2.服务定位:提供什么服务,是否有差异化 1.3.平台定位:结合用户定位与服务定位来决定平台的基调,学术型?恶搞型?创意型?……平台的基调将决定内容运营与用户

微信订阅号开发笔记(二)

微信开发的流程其实很简单 o(∩_∩)o 哈哈!在微信网站的编辑操作 额,就不说了.虽然有人问过.下面是我的微信开发过程,简单记录下. 成为开发者 材料:1.自己的服务器资源,百度的BAE,新浪的SAE都不错. 2.懂那么点编程语言. 3.注册微信公众号. 上面的都有了之后,就可以自己动手开发了.哇咔咔,好兴奋.有木有. 在登录进去之后,怎么成为开发者?不知道,自己看去. 开始coding吧. 1.验证 if (! empty ( $_GET ['echostr'] ) && ! empt

微信订阅号开发笔记(四)

1.创建菜单 //创建菜单 public function createMenu(){ $url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token="; $url.=$this->getacctoken(); //目前自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单.一级菜单最多4个汉字, //二级菜单最多7个汉字,多出来的部分将会以"..."代替.请注意,创建自定义菜单

如何在微信订阅号里实现oauth授权登录

前端时间折腾过的蛋疼问题,好不容易解决了,现在把这个分享出去: 众所周知,微信公众号分订阅号.服务号.企业号:每个号的用途不一样,接口开放程度也不一样. 微信还有个扯淡的开放平台,号称统一管理众多公众号的.反正都是交钱的功能多,两个平台把我弄得傻傻分不清楚. 切入正题,上个公司有个微信订阅号,内嵌了一个微网站,并且要实现授权登录. 这个授权登录的接口只有认证的服务号才能调用,订阅号要实现这个功能只能另辟蹊径: 这个是微信公众号的api地址 http://mp.weixin.qq.com/wiki

微信订阅号开发笔记(五)

1.用户管理 //查询所有分组 public function queryGroups(){ $url = "https://api.weixin.qq.com/cgi-bin/groups/get?access_token="; $url.=$this->getacctoken(); $result = $this->cget($url); header("Content-type: text/html; charset=utf-8"); print_

PHPthinking官网微信订阅号正式来袭

原文地址:http://bbs.phpthinking.com/thread-770-1-1.html PHPthinking跟随微信的脚步,正式开通官网微信订阅号! 为了更加方便大家交流,特开通PHPthinking微信订阅号,提供给大家学习交流.网站建议.博文分享! 当然还有不定期红包发放哦,与此同时,我们还开通了PHPthinking官方微信群,大家扫描添加PHPthinking微信订阅号后,回复"微信群",自行扫描二维码,即可添加PHPthinking微信群! 红包活动正在火热

本人的微信订阅号

本人的微信订阅号,以后3D数学基础系列视频(还有很多其他内容),将首发订阅号(新浪微博,51CTO Blog作为同步).为了保证大家每次都能第一时间收到更新内容,请关注此号.