设计模式-观察者模式和事件机制

概念

观察者模式

百科这样解释:一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法(updata)来实现。具有典型的一对多的关系。使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知进行更新。观察者模式的目标对象中保留了观察者的集合,当被观察者对象的状态发生变化时,会遍历该集合,然后通知(updata)观察者对象得到更新。强调对象行为 具有观察者和目标的解耦,动态联动(动态注册控制动作);广播通信(面向所有的观察者)的优点
image.png

发布/订阅模式(比生产消费更抽象)参考

基于典型观察者模式实现的生产者和消费者弊端:观察者模式中观察者需要直接订阅目标事件(直接引用目标状态),改变后直接接收响应,由于函数调用是同步的(阻塞),在消费者的方法没有返回之前,生产者只好一直等在那边,阻塞进程。 生产者和消费者可以是两个独立的并发主体。生产者把制造出来的数据往缓冲区一丢,就可以再去生产下一个数据,并不用关心消费者状态; 发布订阅模式就类似的多了一个发布通道(调度中心),一方面从生产者接收事件一方面向订阅者发布事件。
image.png
1. 观察者模式的模板

设计阶段
image.png
//目标对象,它持有观察者抽象的集合
public class Subject {

    //定义抽象目标的状态
    private String subjectState;
   //用来保存注册的观察者集合
    private List<Observer_> observerList = new ArrayList<Observer_>();


    //当状态改变时去通知
    public void setSubjectState(String subjectState) {
        this.subjectState = subjectState;
        this.notifyObservers(); //通知
    }

    //添加观察者
    public void attach(Observer_ observer){
        observerList.add(observer);
    }
    //删除观察者
    public void detach(Observer_ observer){
        observerList.remove(observer);
    }

    //通知所有的观察者
    public void notifyObservers(){
        for (Observer_ observer: observerList){
            observer.updata(this);
        }
}

}

//观察者
public class Observer_ {

    //定义抽象的状态
    private String observerState;

    //获取目标类的状态同步到观察者状态中
    public void updata(Subject subject){
        observerState = subject.getSubjectState();
        System.out.println("通知:" + observerState);
    }
}

public class Client {

    @Test
    public void testObserver(){
        //1 创建目标
        Subject subject = new Subject();
        //2 创建观察者
        Observer_ observer1 = new Observer_();
        observer1.setObserverState("observer1");
        //注册
        subject.attach(observer1);
        //通知
        subject.setSubjectState("这是观察者模式");
    }

}

image.png

以上就是典型的观察者模式的模板

2. 区别对待观察者场景的问题

需求:最近经常加班,身为项目经理的你决定给大家买宵夜,张三只要鸡翅,李四想要鸡翅或者汉堡。这里宵夜种类就是目标,张三李四就是观察者,如果通过以上代码,是所有注册都会被通知,这显然是不合理的。代码修改如下:

观察者的接口
//观察者接口定义一个更新接口的方法,目标发生改变时通知观察者进行调用
public interface Observer {

    //更新的接口
    void update(SupperObject objecr);
    //设置和取得观察者名称
    void setObserverName(String observerName);

    String getObserverName();
}
抽象的目标类
public abstract class SupperObject {

    //持有的观察者集合
    public List<Observer> observers = new ArrayList<Observer>();

    //添加 删除 观察者
    public void attach(Observer observer){
        observers.add(observer);
    }
    public void detach(Observer observer){
        observers.remove(observer);
    }
    //具体的提醒在子类逻辑中完成
    protected abstract void notifyObservers();
    
}
 //具体的目标实现类
public class ConcreteSupperSubject extends SupperObject {

    //定义宵夜的变量,鸡翅,汉堡, 就是根据这个变量进项更新(目标状态)
     private String supperContent;


    @Override
    protected void notifyObservers() {
        //循环所有注册的观察者
        for (Observer observer:observers){
            //定义业务逻辑
            //1 .如果鸡翅就通知张三和李四,2.如果汉堡就只通知李四,3.其他都不通知
            if ("鸡翅".equals(this.getSupperContent())){
                if ("张三".equals(observer.getObserverName())){
                    observer.update(this);
                }
                if ("李四".equals(observer.getObserverName())){
                    observer.update(this);
                }
            }
            if ("汉堡".equals(this.supperContent)){
                if ("李四".equals(observer.getObserverName())){
                    observer.update(this);
                }
            }

        }
    }

     public String getSupperContent() {
         return supperContent;
     }

     public void setSupperContent(String supperContent) {
         this.supperContent = supperContent;
         this.notifyObservers(); //通知 区别对待观察者
     }
 }
//观察者的具体实现
public class ConcreteObserver implements Observer{

    //定义观察者名称 ,宵夜情况,提醒内容
    private String observerName;
    private String supperContent;   //目标处获取的状态
    private String remindThing;


    @Override
    public void update(SupperObject object) {
        //获取目标的状态
        supperContent =  ((ConcreteSupperSubject)object).getSupperContent();
        System.out.println(observerName+"收到了"+supperContent+remindThing);
    }
}
    @Test
    public void testSupperClient(){
        //创建目标
        ConcreteSupperSubject supperSubject = new ConcreteSupperSubject();
        //创建观察者
        ConcreteObserver zhangsan = new ConcreteObserver();
        zhangsan.setObserverName("张三");
        zhangsan.setRemindThing("鸡翅真好吃!");
        ConcreteObserver lisi = new ConcreteObserver();
        lisi.setObserverName("李四");
        lisi.setRemindThing("夜宵不错,吃饱了可以好好干活了");
        //注册
        supperSubject.attach(zhangsan);
        supperSubject.attach(lisi);
        //发布 鸡翅,汉堡
        supperSubject.setSupperContent("汉堡");
    }
image.png
image.png
观察者模式总结
  1. 存在目标对象和观察者两个概念,做到解耦,但通知却依赖了抽象的观察者,假如观察者无法抽象就无法通知更新。
  2. 所有的观察者的动作都一样。如果不一样就不能实现

事件机制

事件机制 就可以解决以上的问题,不需要观察者的抽象。通过相应的listener代替观察者,类似观察者模式却解耦目标和观察。一般把事件对象作为业务接口的参数,再根据相应的条件触发
事件机制一般需要3个角色,事件触发源(source)、事件状态对象(event)、处理逻辑(listener)
在spirng 中ServletContextListener接口通过web中的listener就可以在web启动时初始化spirng也是事件的一种应用。

  1. 定义事件类
//定义的事件状态类 也可以继承java util 中的event
public class MyEvent {

    //事件对象
    private Object obj;
    //状态即触发条件
    private String state;

    /**
     * Constructs a prototypical MyEvent.
     *
     * @param source The object on which the MyEvent initially occurred.
     * @throws IllegalArgumentException if source is null.
     */
    public MyEvent(Object source, String state) {
        //super(source);
        this.obj = source;
        this.state = state;
    }
}
  1. 定义事件源handler
public class EventSource {

    //保存监听器的列表,类似观察者模式中保存所有观察者的集合;子类可以保存自己的监听器。
    private Collection listenets;

    //注册监听器
    public void addEventListener(EventListener e){
        if (listenets == null){
            listenets = new HashSet();
        }
        listenets.add(e);
    }

    //删除监听
    public void removeListener(EventListener e){
        if (listenets != null){
            listenets.remove(e);
        }
    }


    /** 根据触发的条件进行事件的执行
     * @param state 触发条件
     */
    protected void fireEvent(String state){
        if (listenets != null){
            MyEvent event = new MyEvent(this,state);
            notifyA(event);
        }
    }

    /**
     * 事件具体的执行
     * @param e 定义的事件
     */
    public void notifyA(MyEvent e){
        Iterator iterator = listenets.iterator();
        while (iterator.hasNext()){
            //实例监听器对象,并调用监听器的方法
            EventListener evt = (EventListener) iterator.next();
            evt.handEvent(e);
        }
    }
}
  1. 定义监听器,把事件作为参数,也是和观察者模式的区别
public interface EventListener{
    //把事件对象作为参数
    void handEvent(MyEvent e);
}
  1. 实现listener 接口,定义业务逻辑
public class Mylistener implements EventListener {


    @Override
    public void handEvent(MyEvent e) {
        //todo 使用if做逻辑控制
        if (e.getState() != null && e.getState() == "hi" ){
            System.out.println(" hi ,java事件机制");
        }
    }
}
public class Mylistener2 implements EventListener {
    @Override
    public void handEvent(MyEvent e) {
        if (e.getState() != null && e.getState().equals("h")){
            System.out.println(" 我是被 "+e.getState()+" 触发的");
        }
    }
}
  1. 测试
 public static void main(String[] args){
        //事件源
        EventSource source = new EventSource();

        /*
        * 当有需要的事件要被触发时就需要编写并注册 相关的listener
        * 这也是事件的弊端
        * */
        source.addEventListener(new Mylistener());
        source.addEventListener(new Mylistener2());
        //根据状态触发
        source.fireEvent("hi");
        source.fireEvent("h");

    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容

  • 以下是消息队列以下的大纲,本文主要介绍消息队列概述,消息队列应用场景和消息中间件示例(电商,日志系统)。 本次分享...
    文档随手记阅读 1,863评论 0 28
  • 简介 ActiveMQ 特点 ActiveMQ 是由 Apache 出品的一款开源消息中间件,旨在为应用程序提供高...
    预流阅读 5,806评论 4 21
  • 一、 消息队列概述 消息队列中间件是分布式系统中重要的组件,主要解决应用耦合、异步消息、流量削锋等问题。实现高性能...
    步积阅读 56,520评论 10 138
  • “ 消息队列已经逐渐成为企业IT系统内部通信的核心手段。它具有低耦合、可靠投递、广播、流量控制、最终一致性等一系列...
    落羽成霜丶阅读 3,929评论 1 41
  • Medium App 把首页的关注、编辑推荐、热门等板块,由原先的 scrollable tab bar改成了小 ...
    刘英滕阅读 442评论 1 4