简介
当对象间存在一对多关系时,则使用观察者模式(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方法,调用了,我就去执行相应的操作。而且一旦订阅,就必须执行预定的操作。(就像是客户端的监听器一样,一旦点击,就一定触发事件,并执行写好的逻辑)
观察者依赖于被观察者,即如果没有被观察者,那观察者就不会存在也不能存在。
而生产者消费者中,生产者只负责生产数据不去做处理(缓解高并发的问题),而消费者只从消费中间件里拿到所要处理的数据,并进行相应的逻辑处理工作,生产者与消费者是相互不知道对方的存在的,或者说他们可以是不同平台的,不同语言的,即解耦的。也就是说生产者生产的数据,不一定有消费者消费,即使存在消费者,也可以选择性消费,而不是一定消费。
消费者也不依赖于生产者,即使没有生产者我写个消费者也没有问题。