一、概念
观察者模式又叫发布-订阅模式。
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同事监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使他们能自动更新自己。
特点:将一个系统分割成一系列相互协作的类有一个很不好的副作用,那就是需要维护相关对象的一致性。我们不希望为了维护一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便。而观察者模式的关键对象使主题subject和观察者observer,一个subject可以有任意数目的依赖它的observer,一旦subject的状态发生了改变,所有的observer都可以得到通知。subject发生通知时并不需要知道谁使它的观察者,也就是说,具体观察者是谁,它根本不需要知道。而任意一个具体观察者不知道也不需要知道其他观察者的存在。
应用:当一个对象的改变需要同时改变其他对象的时候,而且它不知道具体由多少对象有待改变时,应该考虑使用观察者模式。一个抽象模型有两个方面,其中一个方向依赖于另一方面,这时用观察者模式可以将这两者封装在独立的对象中使他们各自独立地改变和复用。观察者模式所做的工作其实就是在解耦。让解耦的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响另一边的变化。
二、需求
小菜上班期间看股票行情,请前台小美帮忙,当老板出门办事回来后,打电话通知他。
三、代码
同事抽象类
/**
* 抽象观察者为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。这个接口也叫更新接口。抽象观察者一般用一个抽象类或者一个接口实现。更新接口通常包含一个update()更新方法。
*/
public abstract class Colleague {
protected String name;
protected Subject sub;
public Colleague(String name, Subject sub) {
this.name = name;
this.sub = sub;
}
public abstract void action();
}
摸鱼的同事
/**
* 具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。具体观察者角色可以保存一个指向具体主题对象的引用。具体观察者角色通常用一个具体子类实现。
*/
public class FishingColleague extends Colleague {
public FishingColleague(String name, Subject sub) {
super(name, sub);
}
@Override
public void action() {
System.out.println(this.sub.getMessage() + this.name + "关闭股票行情,继续办公!");
}
}
聊天的同事
public class TalkingColleague extends Colleague {
public TalkingColleague(String name, Subject sub) {
super(name, sub);
}
@Override
public void action() {
System.out.println(this.sub.getMessage() + this.name + "停止讨论行情,继续办公!");
}
}
前台接口
/**
* 主题或抽象通知者接口,一般用一个抽象类或者一个接口实现。它把所有对观察者对象的引用保存在一个聚集里,每个主题都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
*/
public interface Subject {
public void promise(Colleague obs);
public void unPromise(Colleague obs);
public void notifySub();
public String getMessage();
public void setMessage(String message);
}
前台小美
/**
* 具体主题或具体通知者,将有关状态存入具体观察者对象。在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个具体子类实现。
*/
public class Observer implements Subject {
private List<Colleague> colleagues = new ArrayList<Colleague>();
private String message;
@Override
public void promise(Colleague col) {
colleagues.add(col);
}
@Override
public void unPromise(Colleague col) {
colleagues.remove(col);
}
@Override
public void notifySub() {
for (Colleague col : colleagues) {
col.action();
}
}
@Override
public String getMessage() {
return message;
}
@Override
public void setMessage(String message) {
this.message = message;
}
}
测试类
public class RunMain {
public static void main(String[] args) {
Observer obs = new Observer();
Colleague col1 = new FishingColleague("张三", obs);
Colleague col2 = new TalkingColleague("李四", obs);
obs.promise(col1);
obs.promise(col2);
obs.setMessage("老板来了!");
obs.notifySub();
}
}
扩展:
尽管已经用了依赖倒转原则,但"抽象通知者"还是依赖"抽象观察者",也就是说,万一没有了抽象观察者这样的接口,通知的功能就无法完成了。另外每个具体观察者,调用的方法不一定"更新"方法,也可能是其他不同名的方法。
上面的问题可以通过事件委托解决。
委托就是一种引用方法的类型。一旦为委托分配了方法,委托将与该方法具有完全相同的行为。委托方法的使用可以像其他任何方法一样,具有参数和返回值。委托可以看作使对函数的抽象,是函数的“类”,委托的实例将代表一个具体的函数。一个委托可以搭载多个方法,所有方法被依次唤起。重要的是,它可以使得委托对象所搭载的方法并不需要属于同一个类。委托对象所搭载的所有方法必须具有相同的原形和形式,也就是拥有相同的参数列表和返回值类型。
注意:"委托"在C#中是一个语言级特性,而在Java语言中没有直接的对应,但是java利用反射即可实现委托!