观察者模式与ActionListiner的实现

简介

当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。
意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。

如何解决:使用面向对象技术,可以将这种依赖关系弱化。

关键代码:在抽象类里有一个 ArrayList 存放观察者们。

应用实例: 1、拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。 2、西游记里面悟空请求菩萨降服红孩儿,菩萨洒了一地水招来一个老乌龟,这个乌龟就是观察者,他观察菩萨洒水这个动作。

优点:
1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。

缺点:
1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
使用场景:

1:一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
2:一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
3:一个对象必须通知其他对象,而并不知道这些对象是谁。
4:需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。

正文

由于最近负责的项目都是界面客户端相关,所以监听器用的非常多,但监听器到底如何实现的,一个方法为什么就能被绑定到一个按钮上,这就有点说法了。这里我从观察者模式的实现入手,来看javaSwing中的Listiner到底是如何是实现的。
为了说明观察者模式,我们来定一个要实现的场景。我们假定一个类中有一个state属性,当该属性被修改时,执行三种方法,将state分别转成2进制,10进制,16进制的字符串并打印出来。
听起来好像没什么困难的,state给一个set方法,在set方法内部调用转换方法就行了。如下:

import java.util.*;
public class Subject {
    private int state;
    public int getState() {
        return state;
    }
    public void setState(int state) {
        this.state = state;
        changeHex();
                changeOctal();
                changeBinary() ;
    }
  public void changeHex() {
    ...
  }
  public void changeOctal() {
    ...
  }
   public void changeBinary() {
    ...
  }
}

上述代码没有问题,对吧,但是稍微和我想的要求有些出入,我想的是三种转换方法从属于三个类,然后在调用setState的时候,自动执行这三个方法。就像是这三个类盯着state这个属性一样, 哦,明白了,那改成这样?

public void setState(int state) {
        this.state = state;
        new HEX().changeHex();
                new Octal().changeOctal();
               new Binary().changeBinary() ;
    }

.....

上述代码依然没有问题,但是依然有点不舒服的感觉,这种感觉从第一个程序就有了。到底是什么呢?
没错,是因为上面两个程序,都是“死”程序。
虽然都能达到要求,但是执行的顺序是定死的,观察者是定死的。对于调用者来说,观察者执行逻辑,执行顺序都没法改变,如果有顺序需求,那么只有修改一条路可走。
那么理想的代码是怎么样的?
观察者接口

public abstract class Observer {
protected Subject subject;
public abstract void update();
}

观察者实现类

public class HexObserver extends Observer {

    @Override
    public void update() {
        System.out.println( "Hex String: " 
                  + Integer.toHexString( subject.getState() ).toUpperCase() ); 
    }

    public HexObserver(Subject subject) {
        this.subject=subject;
        subject.addListener(this);
    }
}

public class OctalObserver extends Observer {

    public OctalObserver(Subject subject) {
        this.subject=subject;
        subject.addListener(this);
    }

    @Override
    public void update() {
         System.out.println( "Octal String: " 
                 + Integer.toOctalString( subject.getState() ) ); 

    }
}

public class BinaryObserver extends Observer {

    @Override
    public void update() {
        System.out.println( "Binary String: " 
                  + Integer.toBinaryString( subject.getState() ) ); 

    }

    public BinaryObserver(Subject subject) {
        this.subject=subject;
        subject.addListener(this);
    }
}

被观察者,可以看到,我们用一个list来保存我们想要绑定的类。当执行set方法的时候,我们遍历这个list,从中取出相应方法。我们把绑定类的权力从我们交给开发者,要绑谁,绑定顺序,都不是定好的。这样的观察者模式才算完成。

public class Subject {
    private List<Observer> observerList = new ArrayList<>();
    private int state;

    public int getState() {
        return state;
    }
    public void setState(int state) {
        this.state = state;
        notifyAllObservers();
    }
  public void addListener(Observer observer) {
      observerList.add(observer);
  }
  public void notifyAllObservers() {
      for (Observer observer : observerList) {
          observer.update();
    }
  }
}

测试与执行结果

public class Client {
public static void main(String[] args) {
    Subject subject = new Subject();
    new BinaryObserver(subject);
    new HexObserver(subject);
    new OctalObserver(subject); 
    subject.setState(15);
}
}
Binary String: 1111
Hex String: F
Octal String: 17

那么我们在回头看swing中的监听器,通常我们写监听器是这样的场景

new JButton().addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
                 //绑定逻辑代码
        }       
    });

似乎和我们上面写的监听者模式有点不一样。其实两者没有大区别,只是监听器将绑定后的逻辑代码也交给了用户处理,而上面的转换进制则是规定好的逻辑。
为了实现swing的Listiner,我们在规定一种场景,我们现在被监听的是一个开关,它的状态是开关的一个属性,它有开关两个方法,当我们执行open方法时,要执行一条打印语句(打印什么由用户决定,这是监听器的逻辑代码),close同样。

public interface SwitchListener extends EventListener {
    public void handleEvent(SwitchEvent switchEvent);
}

ps:继承EventListener 是为了规范,不继承并不影响代码执行。
事件源

public class EventObject {
    public String resource;
    public EventObject(String resource) {
        super();
        this.resource = resource;
    }
    
}

Switch事件源继承EventObject
ps:继承同样是为了规范,在swing中。继承的是AWTEvent

public class SwitchEvent extends EventObject{
private String switchState;//开关状态

public SwitchEvent(String resource, String switchState) {
    super(resource);
    this.switchState = switchState;
}

public String getSwitchState() {
    return switchState;
}

public void setSwitchState(String switchState) {
    this.switchState = switchState;
}

}
public class Switch {
private List<Object> ListenerList = new ArrayList<>();
public void addListener(EventListener eventListener) {
    ListenerList.add(eventListener);
}
protected void open() {
    SwitchEvent switchEvent = new SwitchEvent(this.getClass().getName(), "开");
    notifyListeners(switchEvent);
}
protected void notifyListeners(SwitchEvent switchEvent) {
    Iterator<Object> iterator = ListenerList.iterator();
    while(iterator.hasNext()) {
        SwitchListener switchListener = (SwitchListener) iterator.next();
        switchListener.handleEvent(switchEvent);//执行handleEvent的实现,在client的override方法中
    }   
}
protected void close() {
    SwitchEvent switchEvent = new SwitchEvent(this.getClass().getName(), "关");
    notifyListeners(switchEvent);
}
}

可以看到,在Switch中同样有一个list来存储我们的监听者,open方法和close方法也都是遍历list执行其中的绑定方法。区别只在于执行的方法是一个抽象方法(因为是接口中的方法),需要用户自己实现方法中的逻辑代码。
测试代码

public class Client {
public static void main(String[] args) {
    Switch sw = new Switch();
    sw.addListener(new SwitchListener() {   
        @Override
        public void handleEvent(SwitchEvent switchEvent) {
            System.out.println(switchEvent.getSwitchState());
        }
    });
    sw.open();
    sw.close();
}
}

输出

开
关

至此,我们分析完观察者模式的基本逻辑和Listiner的实现方式。观察者模式还有一个别名称为发布-订阅模式,即当一个类更新了自己的信息,则依赖于它的所有类都将更新这条信息。就像是人们看新闻一样,新闻一经发布,所有看到新闻的人都将了解到这条信息。

观察者模式(发布-订阅)和生产者-消费者模式的区别

两者的相同之处在于都为了实现程序的解耦产生的。而不同之处在于,观察者模式更注重状态的改变,而不关注如何改变。
正如上面的demo所写,所有观察者只关心subjet有没有调用setstate方法,调用了,我就去执行相应的操作。而且一旦订阅,就必须执行预定的操作。(就像是客户端的监听器一样,一旦点击,就一定触发事件,并执行写好的逻辑)
观察者依赖于被观察者,即如果没有被观察者,那观察者就不会存在也不能存在。


image.png

而生产者消费者中,生产者只负责生产数据不去做处理(缓解高并发的问题),而消费者只从消费中间件里拿到所要处理的数据,并进行相应的逻辑处理工作,生产者与消费者是相互不知道对方的存在的,或者说他们可以是不同平台的,不同语言的,即解耦的。也就是说生产者生产的数据,不一定有消费者消费,即使存在消费者,也可以选择性消费,而不是一定消费。
消费者也不依赖于生产者,即使没有生产者我写个消费者也没有问题。


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

推荐阅读更多精彩内容