1.1 老板回来?我不知道
上班期间看股票行情,被老板当场看到。
"其实最近项目计划排得紧,是比较忙的。而最近的股市又特别的火,所以很多人都在偷偷地通过网页看行情。老板时常会出门办事,于是大家就可以轻松一些,看看行情,几个人聊聊买卖股票的心得什么的,但是一不小心,老板就会回来,让老板看到工作当中做这些总是不太好,你猜他们想到怎么办?"
让前台帮忙看着,当老板来的时候,及时通知一下。但是有可能前台没看到老板,让老板偷偷溜进去了,那就会抓到一片,也可能抓到一个倒霉鬼。
1.2 双向耦合的代码
这种情形就是典型的观察者模式。
package code.chapter14.observer1;import java.util.ArrayList;public class Test {public static void main(String[] args){System.out.println("**********************************************"); System.out.println("《大话设计模式》代码样例");System.out.println(); //前台小姐童子喆Secretary secretary1 = new Secretary("童子喆");//看股票的同事StockObserver employee1 = new StockObserver("魏关姹",secretary1);StockObserver employee2 = new StockObserver("易管查",secretary1);//前台登记下两伴同事secretary1.attach(employee1);secretary1.attach(employee2);//当发现老板回来了时secretary1.setAction("老板回来了");//通知两个同事secretary1.notifyEmployee();System.out.println();System.out.println("**********************************************");}
}//前台秘书类
class Secretary{protected String name;public Secretary(String name){this.name = name;}//同事列表private ArrayList<StockObserver> list = new ArrayList<StockObserver>();private String action;//增加同事(有几个同事需要前台通知,就增加几个对象)public void attach(StockObserver observer){list.add(observer);}//通知public void notifyEmployee(){//待老板来了,就给所有登记过的同事发通知for(StockObserver item : list){item.update();}}//得到状态public String getAction(){return this.action;}//设置状态(就是设置具体通知的话)public void setAction(String value){this.action = value;}
}//看股票同事类
class StockObserver{private String name;private Secretary sub;public StockObserver(String name,Secretary sub){this.name = name;this.sub = sub;}public void update(){System.out.println(this.sub.name+":"+this.sub.getAction()+"!"+this.name+",请关闭股票行情,赶紧工作。");}
}
前台秘书类和这个看股票类互相耦合,前台秘书要增加观察者,观察者类需要前台秘书的状态。
"对呀,你想想看,如果观察者当中还有人是想看NBA的网上直播(由于时差关系,美国NBA篮球比赛通常都是在北京时间的上午开始),你的'前台秘书'类代码怎么办?"
"那就得改动了。"
"你都发现这个问题了,你说该怎么办?想想我们的设计原则?"
"我就知道,你又要提醒我了。首先开放-封闭原则,修改原有代码就说明设计不够好。其次是依赖倒转原则,我们应该让程序都依赖抽象,而不是相互依赖。OK,我去改改,应该不难的。"
1.3 解耦实践一
增加了抽象的观察者:
增加了两个具体观察者:
"这里让两个观察者去继承'抽象观察者',对于'update(更新)'的方法做重写操作。"
package code.chapter14.observer2;import java.util.ArrayList;public class Test {public static void main(String[] args){System.out.println("**********************************************"); System.out.println("《大话设计模式》代码样例");System.out.println(); //前台小姐童子喆Secretary secretary1 = new Secretary("童子喆");//看股票的同事Observer employee1 = new StockObserver("魏关姹",secretary1);Observer employee2 = new StockObserver("易管查",secretary1);//看NBA的同事Observer employee3 = new NBAObserver("兰秋幂",secretary1);//前台登记下三个同事secretary1.attach(employee1);secretary1.attach(employee2);secretary1.attach(employee3);//当发现老板回来了时secretary1.setAction("老板回来了");//通知三个同事secretary1.notifyEmployee();System.out.println();System.out.println("**********************************************");}
}//前台类
class Secretary{protected String name;public Secretary(String name){this.name = name;}//同事列表private ArrayList<Observer> list = new ArrayList<Observer>();//针对抽象的Observer编程private String action;//增加同事(有几个同事需要前台通知,就增加几个对象)public void attach(Observer observer){list.add(observer);}//减少同事public void detach(Observer observer){list.remove(observer);}//通知public void notifyEmployee(){//待老板来了,就给所有登记过的同事发通知for(Observer item : list){item.update();}}//得到前台状态public String getAction(){return this.action;}//设置前台状态(就是设置具体通知的话)public void setAction(String value){this.action = value;}
}//抽象观察者
abstract class Observer{protected String name;protected Secretary sub;public Observer(String name,Secretary sub){this.name = name;this.sub = sub;}public abstract void update();
}//看股票同事类
class StockObserver extends Observer{public StockObserver(String name,Secretary sub){super(name,sub);}public void update(){System.out.println(super.sub.name+":"+super.sub.getAction()+"!"+super.name+",请关闭股票行情,赶紧工作。");}
}//看NBA同事类
class NBAObserver extends Observer{public NBAObserver(String name,Secretary sub){super(name,sub);}public void update(){System.out.println(super.sub.name+":"+super.sub.getAction()+"!"+super.name+",请关闭NBA直播,赶紧工作。");}
}
考虑问题为什么就不能全面点呢?你仔细看看,在具体观察者中,有没有与具体的类耦合的?
"嗯?这里有什么?哦!我明白了,你的意思是'前台秘书'是一个具体的类,也应该抽象出来。"
"对呀,你想想看,你们公司最后一次,你们的老板回来,前台秘书来不及电话了,于是通知大家的任务变成谁来做?"
"是老板,对的,其实老板也好,前台秘书也好,都是具体的通知者,这里观察者也不应该依赖具体的实现,而是一个抽象的通知者。"
"另外,就算是你们的前台秘书,如果某一个同事和她有矛盾,她生气了,于是不再通知这位同事,此时,她是否应该把这个对象从她加入的观察者列表中删除?"
"这个容易,调用'detach'方法将其减去就可以了。"
1.4 解耦实践二
增加了抽象通知者,可以是接口,也可以是抽象类。
具体的通知者类可能是前台秘书,也可能是老板,它们也许有各自的一些方法,但对于通知者来说,它们是一样的,所以它们都去继承这个抽象类Subject。
对于具体的观察者,需更改的地方就是把与"前台秘书"耦合的地方都改成针对抽象通知者。"由于'魏关姹'没有被通知到,所以他被当场'抓获',下场很惨。
package code.chapter14.observer3;import java.util.ArrayList;public class Test {public static void main(String[] args){System.out.println("**********************************************"); System.out.println("《大话设计模式》代码样例");System.out.println(); //老板胡汉三Subject boss1 = new Boss("胡汉三");//看股票的同事Observer employee1 = new StockObserver("魏关姹",boss1);Observer employee2 = new StockObserver("易管查",boss1);//看NBA的同事Observer employee3 = new NBAObserver("兰秋幂",boss1);//老板登记下三个同事boss1.attach(employee1);boss1.attach(employee2);boss1.attach(employee3);boss1.detach(employee1); //魏关姹其实没有被通知到,所有减去//老板回来boss1.setAction("我胡汉三回来了");//通知两个同事boss1.notifyEmployee();System.out.println();System.out.println("**********************************************");}
}//通知者接口
abstract class Subject{protected String name;public Subject(String name){this.name = name;}//同事列表private ArrayList<Observer> list = new ArrayList<Observer>();//针对抽象的Observer编程private String action;//增加同事(有几个同事需要秘书通知,就增加几个对象)public void attach(Observer observer){list.add(observer);}//减少同事public void detach(Observer observer){list.remove(observer);}//通知public void notifyEmployee(){//给所有登记过的同事发通知for(Observer item : list){item.update();}}//得到状态public String getAction(){return this.action;}//设置状态(就是设置具体通知的话)public void setAction(String value){this.action = value;}
}//老板
class Boss extends Subject{public Boss(String name){super(name);}//拥有自己的方法和属性
}//前台类
class Secretary extends Subject{public Secretary(String name){super(name);}//拥有自己的方法和属性
}//抽象观察者
abstract class Observer{protected String name;protected Subject sub;public Observer(String name,Subject sub){this.name = name;this.sub = sub;}public abstract void update();
}//看股票同事类
class StockObserver extends Observer{public StockObserver(String name,Subject sub){super(name,sub);}public void update(){System.out.println(super.sub.name+":"+super.sub.getAction()+"!"+super.name+",请关闭股票行情,赶紧工作。");}
}//看NBA同事类
class NBAObserver extends Observer{public NBAObserver(String name,Subject sub){super(name,sub);}public void update(){System.out.println(super.sub.name+":"+super.sub.getAction()+"!"+super.name+",请关闭NBA直播,赶紧工作。");}
}
1.5 观察者模式
观察者模式又叫作发布-订阅(Publish/Subscribe)模式。
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。[DP]
观察者模式(Observer)结构图
package code.chapter14.observer0;import java.util.ArrayList;public class Test {public static void main(String[] args){System.out.println("**********************************************"); System.out.println("《大话设计模式》代码样例");System.out.println(); Subject subject = new ConcreteSubject();subject.attach(new ConcreteObserver("NameX",subject));subject.attach(new ConcreteObserver("NameY",subject));subject.attach(new ConcreteObserver("NameZ",subject));subject.setSubjectState("ABC");subject.notifyObserver();System.out.println();System.out.println("**********************************************");}
}//通知者抽象类
abstract class Subject{private ArrayList<Observer> list = new ArrayList<Observer>();//针对抽象的Observer编程//增加观察者public void attach(Observer observer){list.add(observer);}//减少观察者public void detach(Observer observer){list.remove(observer);}//通知观察者public void notifyObserver(){for(Observer item : list){item.update();}}protected String subjectState;public String getSubjectState(){return this.subjectState;}public void setSubjectState(String value){this.subjectState = value;}
}//具体通知者
class ConcreteSubject extends Subject{//具体通知者的方法
}//抽象观察者
abstract class Observer{public abstract void update();
}//具体观察者类
class ConcreteObserver extends Observer{private String name;private Subject sub;public ConcreteObserver(String name,Subject sub){this.name = name;this.sub = sub;}public void update(){System.out.println("观察者"+this.name+"的新状态是"+this.sub.getSubjectState());}
}interface Observer{public void update();
}
Subject类,可翻译为主题或抽象通知者,一般用一个抽象类或者一个接口实现。它把所有对观察者对象的引用保存在一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
Observer类,抽象观察者,为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。这个接口叫作更新接口。抽象观察者一般用一个抽象类或者一个接口实现。更新接口通常包含一个update()方法,这个方法叫作更新方法。
ConcreteSubject类,叫作具体主题或具体通知者,将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个具体子类实现。
ConcreteObserver类,具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。具体观察者角色可以保存一个指向具体主题对象的引用。具体观察者角色通常用一个具体子类实现。
1.6 观察者模式的特点
用观察者模式的动机是什么呢?
将一个系统分割成一系列相互协作的类有一个很不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便[DP]。而观察者模式的关键对象是主题Subject和观察者Observer,一个Subject可以有任意数目的依赖它的Observer,一旦Subject的状态发生了改变,所有的Observer都可以得到通知。Subject发出通知时并不需要知道谁是它的观察者,也就是说,具体观察者是谁,它根本不需要知道。而任何一个具体观察者不知道也不需要知道其他观察者的存在。"
"什么时候考虑使用观察者模式呢?"
"当一个对象的改变需要同时改变其他对象的时候。"
"补充一下,而且它不知道具体有多少对象有待改变时,应该考虑使用观察者模式。还有吗?"
"我感觉当一个抽象模型有两个方面,其中一方面依赖于另一方面,这时用观察者模式可以将这两者封装在独立的对象中使它们各自独立地改变和复用。"
"非常好,总的来讲,观察者模式所做的工作其实就是在解除耦合。让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响另一边的变化。"
"啊,这实在是依赖倒转原则的最佳体现呀。"
"我问你,在抽象观察者时,你的代码里用的是抽象类,为什么不用接口?"
"因为我觉得两个具体观察者,看股票观察者和看NBA观察者是相似的,所以用了抽象类,这样可以共用一些代码,用接口只是方法上的实现,没太大意义。"
"那么抽象观察者可不可以用接口来定义?"
"用接口?我不知道,应该没必要吧。"
"哈,那是因为你不知道观察者模式的应用都是怎么样的。现实编程中,具体的观察者完全有可能是风马牛不相及的类,但它们都需要根据通知者的通知来做出update()的操作,所以让它们都实现下面这样的一个接口就可以实现这个想法了。"
"这时用接口比较好,"那具体怎么使用呢?"
1.7 Java内置接口实现
事实上,Java已经为观察者模式准备好了相关的接口和抽象类了。"观察者接口java.util.Observer和通知者类java.util. Observable。有了这些Java内置代码的支持,你只需要扩展或继承Observable,并告诉它什么时候应该通知观察者,就OK了,剩下的事Java会帮你做。JDK中的原生代码你可以自己去查看,我们来看如何用它们来实现刚才的代码结构。"
"由于已经有了Observable实现的各种方法,比如加观察者(addObserver)、减观察者(deleteObserver)、通知观察者(notifyObservers)等。所以Boss类继承了Observable,已经无须再实现这些代码了。Boss继承Observable类,当addObserver添加一些观察者后,它在setAction里是这样工作的:调用setChanged方法,标记状态已经改变,然后调用notifyObservers方法来通知观察者。"
package code.chapter14.observer4;import java.util.ArrayList;
import java.util.Observable;
import java.util.Observer;// //jdk中Observer代码
// public interface Observer {
// void update(Observable o, Object arg);
// }// //jdk中Observable代码
// public class Observable {
// private boolean changed = false;
// private Vector obs;
// public Observable() {
// obs = new Vector();
// }
// public synchronized void addObserver(Observer o) {
// if (o == null)
// throw new NullPointerException();
// if (!obs.contains(o)) {
// obs.addElement(o);
// }
// }
// public synchronized void deleteObserver(Observer o) {
// obs.removeElement(o);
// }
// public void notifyObservers() {
// notifyObservers(null);
// }
// public void notifyObservers(Object arg) {
// Object[] arrLocal;// synchronized (this) {
// if (!changed)
// return;
// arrLocal = obs.toArray();
// clearChanged();
// }// for (int i = arrLocal.length-1; i>=0; i--)
// ((Observer)arrLocal[i]).update(this, arg);
// }
// public synchronized void deleteObservers() {
// obs.removeAllElements();
// }
// protected synchronized void setChanged() {
// changed = true;
// }
// protected synchronized void clearChanged() {
// changed = false;
// }
// public synchronized boolean hasChanged() {
// return changed;
// }
// public synchronized int countObservers() {
// return obs.size();
// }
// }public class Test {public static void main(String[] args){System.out.println("**********************************************"); System.out.println("《大话设计模式》代码样例");System.out.println(); //老板胡汉三Boss boss1 = new Boss("胡汉三");//看股票的同事Observer employee1 = new StockObserver("魏关姹");Observer employee2 = new StockObserver("易管查");Observer employee3 = new NBAObserver("兰秋幂");//老板登记下三个同事boss1.addObserver(employee1); //增加观察者boss1.addObserver(employee2);boss1.addObserver(employee3);boss1.deleteObserver(employee1); //魏关姹其实没有被通知到,所有减去观察者//老板回来boss1.setAction("我胡汉三回来了");System.out.println();System.out.println("**********************************************");}
}//老板
class Boss extends Observable{protected String name;private String action;public Boss(String name){this.name = name;}//得到状态public String getAction(){return this.action;}//设置状态(就是设置具体通知的话)public void setAction(String value){this.action = value;super.setChanged(); //改变通知者的状态super.notifyObservers();//调用父类Observable方法,通知所有观察者}
}//看股票同事类
class StockObserver implements Observer{protected String name;public StockObserver(String name){this.name = name;}public void update(Observable o, Object arg){ //两个参数是原生接口要求的参数Boss b=(Boss)o; //需要拆箱将Observable对象转成BossSystem.out.println(b.name+":"+b.getAction()+"!"+this.name+",请关闭股票行情,赶紧工作。");}
}//看NBA同事类
class NBAObserver implements Observer{protected String name;public NBAObserver(String name){this.name = name;}public void update(Observable o, Object arg){Boss b=(Boss)o;System.out.println(b.name+":"+b.getAction()+"!"+this.name+",请关闭NBA直播,赶紧工作。");}
}
"StockObserver实现了JDK中的Observer接口,但update有两个固定参数,其中Observable对象可以让观察者知道是哪个主题通知它的。NBAObserver与此类类似,就不写了。看代码。"
addObserver与attach,deleteObserver与detach都是同一个意思。
"你有发现问题吗?比如如果增加前台秘书类,会不会有问题。"
"在StockObserver类中,竟然出现了Boss,具体类中耦合了具体类了,这就没有针对接口编程了。"
"所以我们可以像下面这样改。"
package code.chapter14.observer5;import java.util.ArrayList;
import java.util.Observable;
import java.util.Observer;// //jdk中Observer代码
// public interface Observer {
// void update(Observable o, Object arg);
// }// //jdk中Observable代码
// public class Observable {
// private boolean changed = false;
// private Vector obs;
// public Observable() {
// obs = new Vector();
// }
// public synchronized void addObserver(Observer o) {
// if (o == null)
// throw new NullPointerException();
// if (!obs.contains(o)) {
// obs.addElement(o);
// }
// }
// public synchronized void deleteObserver(Observer o) {
// obs.removeElement(o);
// }
// public void notifyObservers() {
// notifyObservers(null);
// }
// public void notifyObservers(Object arg) {
// Object[] arrLocal;// synchronized (this) {
// if (!changed)
// return;
// arrLocal = obs.toArray();
// clearChanged();
// }// for (int i = arrLocal.length-1; i>=0; i--)
// ((Observer)arrLocal[i]).update(this, arg);
// }
// public synchronized void deleteObservers() {
// obs.removeAllElements();
// }
// protected synchronized void setChanged() {
// changed = true;
// }
// protected synchronized void clearChanged() {
// changed = false;
// }
// public synchronized boolean hasChanged() {
// return changed;
// }
// public synchronized int countObservers() {
// return obs.size();
// }
// }public class Test {public static void main(String[] args){System.out.println("**********************************************"); System.out.println("《大话设计模式》代码样例");System.out.println(); //老板胡汉三Boss boss1 = new Boss("胡汉三");//看股票的同事Observer employee1 = new StockObserver("魏关姹");Observer employee2 = new StockObserver("易管查");Observer employee3 = new NBAObserver("兰秋幂");//老板登记下三个同事boss1.addObserver(employee1);boss1.addObserver(employee2);boss1.addObserver(employee3);boss1.deleteObserver(employee1); //魏关姹其实没有被通知到,所有减去//老板回来boss1.setAction("我胡汉三回来了");System.out.println();System.out.println("**********************************************");}
}//Subject
class Subject extends Observable{protected String name;private String action;public Subject(String name){this.name = name;}//得到状态public String getAction(){return this.action;}//设置状态(就是设置具体通知的话)public void setAction(String value){this.action = value;setChanged();notifyObservers();}
}//老板
class Boss extends Subject{public Boss(String name){super(name);}
}//看股票同事类
class StockObserver implements Observer{protected String name;public StockObserver(String name){this.name = name;}public void update(Observable o, Object arg){Subject b=(Subject)o;System.out.println(b.name+":"+b.getAction()+"!"+this.name+",请关闭股票行情,赶紧工作。");}
}//看NBA同事类
class NBAObserver implements Observer{protected String name;public NBAObserver(String name){this.name = name;}public void update(Observable o, Object arg){Subject b=(Subject)o;System.out.println(b.name+":"+b.getAction()+"!"+this.name+",请关闭NBA直播,赶紧工作。");}
}
"上面这样的设计,可以充分复用Java内置类和接口,达到针对接口编程的目的,又保证了我们的代码不会因为紧耦合而不能复用的问题。"
此时StockObserver并不知道Boss的存在,而只是知道Subject,达到了我们松耦合的目的。
"事实上,这里Java内置的Observable是一个类,这样设计是有问题的。一个类,就只能继承它,但我们自己的类可能本身就需要继承其他抽象类,这就产生了麻烦。Java不支持多重继承,这就严重限制了Observable的复用潜力。所以,当你这段代码用javac编译时,会给出提示:警告:[deprecation] java.util中的Observable已过时。系统其实是建议你不要复用这样的方法。所以真实编程中,我们也要考虑怎么取舍,如何修改的问题。"
1.8 观察者模式的应用
"那在现实中,观察者模式主要用在哪里呢?"
"举个例子。我们所用的几乎所有的应用软件,内部窗体相互通信就都是利用观察者模式的原理在工作的。比如,使用Word时,当你单击右上角的'样式窗格'之前的时候,整个界面是下面这样的。"
"当'样式窗格'之后,右侧会弹出一个样式编辑的窗体。也就是说,一个开关按钮给一个样式编辑窗体的观察者发了通知,让它显示出来。"
"所有的控件,事实上都是有实现'Observer'的接口(实际编程比较复杂,这里不展开),它们都是在等通知中,单击某个开关按钮后,就向这些控件——观察者发了通知,于是产生了窗体上的变化。这其实就是观察者模式的实际应用。具体细节可以去了解Android或Swing编程。"
1.9 石守吉失手机后
石守吉手机丢失后,其实就是我在这里给我们班级所有同学群发一条短消息,通知他们,你石守吉已换新号,请大家更新号码,有事可及时与石守吉联系。