看《大话设计模式》第14章观察者模式,发现一个问题:
观察者模式是定义了一种1对多的依赖关系,让多个观察者对象同时监听一个主题对象。在这个主题对象状态发生改变的时候,会通知所有的观察者对象,使它们能够同时更新自己。
所以其实只有一个主题对象,即Boss类和Secretary类在客户端程序中只能有一个出现,即1个观察者只能观察一个主题对象,而不是能观察多个(实际上,一个观察者也可以观察多个主题对象,但这就不属于观察者模式了)。先把这个问题说清楚。
什么时候使用观察者模式呢?
当一个对象的改变需要同时改变其他对象的时候
interface Observer{
void update();
}
class StockObserver implements Observer {
private String name;
// Observer必须知道Notifier的状态信息, 所以它里面必须有个当前Notifier的对象成员.
private Notifier notifier;
public StockObserver(String name, Notifier notifier) {
this.name = name;
this.notifier = notifier;
}
@Override
public void update() {
System.out.println(notifier.getAction() +" " + name + "关闭股票行情,继续工作 ");
}
}
class NBAObserver implements Observer {
private String name;
private Notifier notifier;
public NBAObserver(String name, Notifier notifier) {
this.name = name;
this.notifier = notifier;
}
@Override
public void update() {
System.out.println(notifier.getAction() +" " + name + "关闭NBA,继续工作 ");
}
}
interface Notifier{
void attach(Observer observer);
void detach(Observer observer);
void notifier();
void setAction(String action);
String getAction();
}
class Secretary implements Notifier {
//观察者列表 用LinkedHashSet防止出现重复通知的情况
private Set observers = new LinkedHashSet<>();
//要观察的状态
private String action;
@Override
public void attach(Observer observer) {
observers.add(observer);
}
@Override
public void detach(Observer observer) {
observers.remove(observer);
}
@Override
public void notifier() {
for (Observer o : observers
) {
o.update();
}
}
@Override
public void setAction(String action) {
this.action = action;
}
@Override
public String getAction() {
return action;
}
}
class Boss implements Notifier {
//观察者列表
private Set observers = new LinkedHashSet<>();
//要观察的状态
private String action;
@Override
public void attach(Observer observer) {
observers.add(observer);
}
@Override
public void detach(Observer observer) {
observers.remove(observer);
}
@Override
public void notifier() {
for (Observer o : observers
) {
o.update();
}
}
@Override
public void setAction(String action) {
this.action = action;
}
@Override
public String getAction() {
return action;
}
}
public class Test {
public static void main(String[] args) {
Boss huhansan = new Boss();
StockObserver tongshi1 = new StockObserver("魏关姹", huhansan);
NBAObserver tongshi2 = new NBAObserver("易管查", huhansan);
//注册两位同事
huhansan.attach(tongshi1);
huhansan.attach(tongshi2);
//解除注册一位同事
huhansan.detach(tongshi1);
//发现老板回来
huhansan.setAction("我胡汉三回来了!");
//通知
huhansan.notifier();
}
}
如书中说言观察者模式的不足,如果我们不想(或不能)为已有的StockObserver类及NBAObserver类实现Observer接口,那么如何在Notifier中可以添加若干个StockObserver或NBAObserver的对象并通知他们执行其各自的方法呢?
class StockObserver{
private String name;
private Notifier notifier;
public StockObserver(String name, Notifier notifier) {
this.name = name;
this.notifier = notifier;
}
public void CloseStockMarket(){
System.out.println(notifier.getAction() +" " + name + "关闭股票行情,继续工作 ");
}
public void OpenStockMarket(){
System.out.println(notifier.getAction() +" " + name + "打开股票行情 ");
}
}
class NBAObserver{
private String name;
private Notifier notifier;
public NBAObserver(String name, Notifier notifier) {
this.name = name;
this.notifier = notifier;
}
public void CloseNBADirectSeeding(){
System.out.println(notifier.getAction() +" " + name + "关闭NBA直播,继续工作 ");
}
public void OpenNBADirectSeeding(){
System.out.println(notifier.getAction() +" " + name + "打开NBA直播 ");
}
}
interface Notifier{
void notifier();
void setAction(String action);
String getAction();
}
/*这里稍稍想想, 到底如何将类StockObserver和类的指定方法传入到另1个类呢?
其实我们可以把它拆分成两部分:
1. 传送对象本身(Object).
2. 传送方法的名字(String).
至于怎样把这两种不同类型的东西放入类S的容器? 方法有很多种,
这里我新建1个类ObjMethod, 把这两种东西封装在一起.
而且我是打算把它放入HashSet容器的, 所以重写了hashCode()和 equals()方法, 只要上面两个成员相等, 我们就认为是相同的两个对象.*/
class ObjMethod {
private Object obj;
private String method;
public ObjMethod(Object obj, String method){
this.obj = obj;
this.method = method;
}
public String getMethod() {
return this.method;
}
public Object getObj() {
return this.obj;
}
//自己定义相等的逻辑
@Override
public boolean equals(Object o){
ObjMethod m = (ObjMethod)o;
//对象之间== 比较两个对象引用中存储的对象地址是不是一样的
//两个对象引用的地址相同 并且 调用的方法名也相同 那么就是相同的Object,不再插入HashSet中
return (this.getObj() == m.getObj()) && (this.getMethod().equals(m.getMethod()));
}
//对HashSet而言,存入对象的流程为:
//根据对象的hash码,经过hash算法,找到对象应该存放的位置,如果该位置为空,则将对象存入该位置;
//如果该位置不为空,则使用equals()比较该位置的对象和将要入的对象,
//如果两个相等,则不再插入,如果不相等,根据hash冲突解决算法将对象插入其他位置。
@Override
public int hashCode(){
return this.getObj().hashCode() * this.getMethod().hashCode();
}
}
class Boss implements Notifier {
//需求:在StockObserver及NBAObserver不被修改的前提下(即不像demo03那样可以集成或实现Observe)
//在Notifier中可以添加若干个StockObserver或NBAObserver的对象
//并通知他们执行自己的方法
private HashSet methodList = new HashSet();
private String action;
public void attach(Object obj, String method){
this.methodList.add(new ObjMethod(obj,method));
}
public void detach(Object obj, String method){
this.methodList.remove(new ObjMethod(obj,method));
}
public void detachAll(){
methodList.clear();
}
@Override
public void notifier() {
if (this.methodList.isEmpty()){
return;
}
//1. 从HashSet获取对象Obj 和 方法名method
Iterator it = this.methodList.iterator();
while (it.hasNext()){
ObjMethod m = (ObjMethod)it.next();
Class> objClass = m.getObj().getClass(); //get the class of the object
try{
Method method = objClass.getMethod(m.getMethod(), new Class[]{}); //no any parameters
//在具有指定参数的指定对象上调用此Method对象表示的底层方法。
method.invoke(m.getObj(),new Object[]{});//no parameters
}catch(Exception e){
e.printStackTrace();
}
}
}
@Override
public void setAction(String action) {
this.action = action;
}
@Override
public String getAction() {
return action;
}
}
public class Test {
public static void main(String[] args) {
Boss huhansan = new Boss();
//关注胡汉三
//我想关注多个通知者 如何解决呢? 留坑-->实际上 一个观察者也可以观察多个通知者的,但这就不属于观察者模式了
StockObserver tongshi1 = new StockObserver("魏关姹", huhansan);
NBAObserver tongshi2 = new NBAObserver("易管查", huhansan);
StockObserver tongshi3 = new StockObserver("魏关姹", huhansan);
huhansan.setAction("老板离开了");
//清除通知列表
huhansan.detachAll();
//注册通知列表
huhansan.attach(tongshi1,"OpenStockMarket");
huhansan.attach(tongshi2,"OpenNBADirectSeeding");
huhansan.notifier();
//模拟一段时间后
try {
TimeUnit.SECONDS.sleep(2l);
} catch (InterruptedException e) {
e.printStackTrace();
}
//发现老板回来
huhansan.setAction("老板回来了!");
//清除通知列表
huhansan.detachAll();
//胡汉三注册通知列表
huhansan.attach(tongshi1,"CloseStockMarket");
huhansan.attach(tongshi2,"CloseNBADirectSeeding");
//重复注册(无效)
huhansan.attach(tongshi2,"CloseNBADirectSeeding");
//解除注册
huhansan.detach(tongshi1,"CloseStockMarket");
huhansan.notifier();
}
}
通过类图可以看到
1. 观察者必须知道Notifier的状态信息,所以有一个当前Notifier的对象成员
2. 观察者本身并不知道其他观察者的存在
3. 通知者内部维护一个观察者的链表,可以注册和解除注册观察者。
4. 在notifier方法中执行了观察者的方法。而观察者具体的方法是放在各自的方法体内的。
在微软Webform中,窗体在程序之外的一个单独的线程中运行。当我们双击webform中的按钮后会自动生成一个btn_OnClick的方法,然后在里面编写一些逻辑,同时也生成了btn.Click+=new EventHandler(btn_OnClick)代码(只是2.0之后这个代码就被隐藏起来了),这就是给按钮btn(订阅者)订阅了一个事件。这些逻辑理当属于按钮所在的页面,而不是需要执行这个方法的代码中。
当按钮点击之后,会触发页面的提交,webform框架可以获取是哪个按钮被点击过(发布者),然后执行btn.Click(),就可以执行我们具体的逻辑了。
同时,这里还涉及到线程间的回调,后台线程会执行窗体线程中的btn.Click方法,通过这种方式通知窗体线程按钮被点击了?
设想如果不用这个模式,按钮的Click方法是不是要写很多switch来判断是哪个按钮,然后调用该有的逻辑
综上来看,观察者模式是一个处理未知方法的模式,他漂亮的把具体逻辑分散到他该属于地方。使得发布者的代码保持不变,而订阅者的事件可以散布在他们自己的代码中。
参考:
观察者模式(Observer) 简介
Java 利用反射实现C#的委托
从抽象谈起(二):观察者模式与回调