设计形式-观察者模式(observer)
设计模式是门很高深的学问,刚开始看的时候感觉就是前辈们总结出的:让代码的的书写更为简便,解偶,以及重用性比较好.
现在发现一个设计好的模式,在接口,OO上都是非常的出色.其中的心得,只可意会不可言传.
观察者模式
定义:定义对象间一对多的依赖,这样一来,当一个对象发生改变,其他依赖者会得到通知并自动更新.
举个例子:
新京报,他是一个主题,很多人都可以定购他,他不需要了解你订购他是干吗,他只知道你付了钱,他给你一份报纸,到什么地址.
如果他修改版面,他会通知你,比如说,最近物价上涨.可能需要上涨几毛钱.(有更改就会通知依赖者)
而订购他的人或者公司,属于观察者也就是依赖者,只有2个参数,一个是注册,一个是解除注册.注册后,就会收到报纸的最新消
息,一有修改就会通知你.解除注册后,你还是你,但是你已经不属于依赖者了,就不会接到相应的消息.你可以今天注册,今天解
除(这是你的事情).不会影响到其他的东西,也就是松耦合.
当2个对象之间送耦合,它们依然可以相互交互,但是彼此不知道细节.
观察者提供了一种设计模式,他让主题和观察者之间松耦合.
作为观察者,主题只知道他实现一个接口 Oberver,主题不需要知道他具体是谁,需要实现什么操作.
任何时候我们都可以增加新的观察者.因为主题实现的是一个实现oberver的抽象接口列表,所以我们可以随时增加和删除观
察者.
主题不用了解其他的,只需要给现有注册的观察者发消息就可以了,观察者的增加和改变,不需要修改主题的代码.
主题和观察者,我们可以重复使用,改变一方不会印象另外一方.
说了好多耦合方面的东西,说到底就是为了达到解耦合的目的.
看代码比较清晰
我实现了一个电脑厂商和地区代理的,一对多关系依赖.名字起的不好,里面有很多的proxy,可别认为是代理模式.
首先实现一个电脑工厂的接口
GeneralProxy.java
package com.linpyi.computer; /** * 电脑总代理接口 * @author work * */ public interface GeneralProxy { /** * 注册电脑代理 * @param o */ public void registerComputerProxy(Observer o); /** * 移除电脑代理 * @param o */ public void removeComputerProxy(Observer o); /** * 更新代理价格 */ public void notifyComputerProxy(); }
他实现了3个接口方法,注册移除和更改.
接着写观察者接口,没有多余的方法,就一个updata
package com.linpyi.computer; /** * 电脑观察者,自己写的代理接口 * @author work * */ public interface Observer { /** * 更新电脑信息 * @param name 电脑品牌 * @param price 电脑价格 * @param info 电脑描述 */ public void update(String name,float price,String info); }
还有个代理厂商要实现的接口,也就是一个代理厂商的报价
package com.linpyi.computer; /** * 电脑分代理接口 * @author work * */ public interface BranchProxy { /** * 默认方法 */ public void display(); }
接着实现具体电脑厂商,我这使用了lenovo,不过名字打错了.
LenvoComputer.java
package com.linpyi.computer; import java.util.ArrayList; /** * lenovo电脑(不好意思这lenovo写错了,暂时不改正了) * @author work * */ public class LenvoComputer implements GeneralProxy { private ArrayList proxys ; private String name; private float price; private String info; public LenvoComputer(){ proxys = new ArrayList(); } /** * 更改lenvo分代理的信息 */ public void notifyComputerProxy() { // TODO Auto-generated method stub for(int i = 0 ;i<proxys.size();i++){ Observer proxy = (Observer)proxys.get(i); proxy.update(name, price, info); } } /** * 设置lenvo的电脑价格和信息 * @param name 电脑名称 * @param price 电脑价格 * @param info 电脑描述信息 */ public void setLenvo(String name,float price,String info){ this.name=name; this.price=price; this.info=info; notifyComputerProxy(); } /** * 注册lenvo代理 */ public void registerComputerProxy(Observer o) { // TODO Auto-generated method stub proxys.add(o); } /** * 移除lenvo代理 */ public void removeComputerProxy(Observer o) { // TODO Auto-generated method stub int i = proxys.indexOf(o); if(i>=0){ proxys.remove(o); } } }
里面的代码应该不会很难,很容易看的懂的,
接着该实现中国区代理 lenovo了,这里要记的实现的是自己写的observer,因为JDK有默认的observer,底下介绍
package com.linpyi.computer; /** * 中国地区代理类,使用自己写的代理接口 * @author * */ public class ChinaProxy implements BranchProxy, Observer { private GeneralProxy generalProxy; private String name ; private float price; private String info; public ChinaProxy(GeneralProxy generalProxy){ this.generalProxy=generalProxy; generalProxy.registerComputerProxy(this);//注册代理 } /** * 默认方法 */ public void display() { // TODO Auto-generated method stub System.out.println("中国代理"); System.out.println("型号"+name); System.out.println("价格"+price); System.out.println("描述信息"+info); } /** * 更新方法 */ public void update(String name, float price, String info) { // TODO Auto-generated method stub this.name=name; this.price=price; this.info=info; display(); } }
一个代理不够,来2个吧
package com.linpyi.computer; /** * 日本代理,使用自己写的代理接口 * * @author work * */ public class JapanProxy implements BranchProxy, Observer { private String name; private String info; private float price; private GeneralProxy generalProxy; public JapanProxy(GeneralProxy generalProxy) { this.generalProxy = generalProxy; generalProxy.registerComputerProxy(this); } public void update(String name, float price, String info) { // TODO Auto-generated method stub this.name = name; this.price = price; this.info = info; display(); } public void display() { // TODO Auto-generated method stub System.out.println("日本代理"); System.out.println("型号" + name); System.out.println("价格" + price); System.out.println("描述信息" + info); } }
看如何使用了
package com.linpyi.computer; public class Client { public static void main(String[] args){ //使用自己写的观察模式方法 LenvoComputer lenvo = new LenvoComputer(); ChinaProxy chinaProxy = new ChinaProxy(lenvo); JapanProxy japanProxy = new JapanProxy(lenvo); lenvo.setLenvo("lenvo0001", 10000, "disk 160G , memory 2G"); } }
看看,我们只需要改变lenovo场商的报价
不管是中国还是日本代理,价格也就改变了
中国代理 型号lenvo0001 价格10000.0 描述信息disk 160G , memory 2G 日本代理 型号lenvo0001 价格10000.0 描述信息disk 160G , memory 2G
然后我们需要修改中国代理的其他什么东西,压根就不会影响到厂家,厂家只负责给代理人发报价和基本信息
其实代码不是很难,主要是思想,如何能真正的面向接口,面向对象编程,
JDK里面有自带的类实现观察者模式,在java.util.Observable,注意这是类而不是接口,类有类的局限性,因为JAVA只能继承
一个类,所以如果你的类有继承了实现起来就比较麻烦了.当然局限性不只这一个地方.
既然JDK有自己的Obervable,我们就从写一个电脑厂商吧,HP吧,也不错的电脑
package com.linpyi.computer; import java.util.Observable; /** * HP电脑代理,使用的是JDK自带的观察者模式类(Observable) * @author work * */ public class HpComputer extends Observable { private String name; private float price; private String info; public void measurementsChanged(){ setChanged();//观察者类里的方法,主要用于开关 notifyObservers();//继承类里的方法(通知观察者) } public void setMeasurements(String name,float price,String info){ this.name=name; this.price=price; this.info=info; measurementsChanged(); } public String getName(){ return name; } public float getPrice(){ return price; } public String getInfo(){ return info; } }
记着这里是实现JDK自带的,别弄混了,里面使用的方法都是已经写好的,只是引用
notifyObservers()不说 ,就是通知观察者
setChanged()其实就是一个开关
setChanged(){ changed=true; } notifyObservers(Object arg){ if(changed){ //something } changed=false; } motifyObservers(){ motifyObservers(null); }
加了一个开关,就会让通知观察者更有弹性,你可以自己设置条件,让他通知观察者,比如几分钟一次,还是什么..这里不做介绍
接着写代理地区,中国日本写完了,就写棒子吧
package com.linpyi.computer; import java.util.Observable; import java.util.Observer; import com.linpyi.weather.WeatherDataOther; /** * 韩国代理,使用的是JDK自带的接口(Observer) * @author work * */ public class KoreaProxy implements Observer, BranchProxy { private Observable observable; private String name; private String info; private float price; public KoreaProxy(Observable observable) { this.observable = observable; observable.addObserver(this); } public void update(Observable o, Object arg) { // TODO Auto-generated method stub if (o instanceof HpComputer) { HpComputer computer = (HpComputer) o; this.name = computer.getName(); this.price = computer.getPrice(); this.info = computer.getInfo(); display(); } } public void display() { // TODO Auto-generated method stub System.out.println("韩国代理"); System.out.println("型号" + name); System.out.println("价格" + price); System.out.println("描述信息" + info); } }
这里实现的Observer接口也是JDK自带的
加个印度的.
package com.linpyi.computer; import java.util.Observable; import java.util.Observer; /** * 印度电脑代理(使用JDK自带的接口) * @author work * */ public class IndiaProxy implements Observer, BranchProxy { private Observable observable; private String name; private String info; private float price; public IndiaProxy(Observable observable) { this.observable = observable; observable.addObserver(this);//增加观察者(JDK自带的) } /** * 更新方法,使用的是JDK自带的方法 */ public void update(Observable o, Object arg) { // TODO Auto-generated method stub if (o instanceof HpComputer) { HpComputer computer = (HpComputer) o; this.name = computer.getName(); this.price = computer.getPrice(); this.info = computer.getInfo(); display(); } } public void display() { // TODO Auto-generated method stub System.out.println("印度代理"); System.out.println("型号" + name); System.out.println("价格" + price); System.out.println("描述信息" + info); } }
OK,完成了,
测试一下吧
package com.linpyi.computer; public class Client { public static void main(String[] args){ //使用自己写的观察模式方法 LenvoComputer lenvo = new LenvoComputer(); ChinaProxy chinaProxy = new ChinaProxy(lenvo); JapanProxy japanProxy = new JapanProxy(lenvo); lenvo.setLenvo("lenvo0001", 10000, "disk 160G , memory 2G"); //使用JDK的观察模式方法 HpComputer hp = new HpComputer(); KoreaProxy koreaProxy = new KoreaProxy(hp); IndiaProxy IndiaProxy = new IndiaProxy(hp); hp.setMeasurements("hp0001", 12000, "disk 250G , memory 2G"); } }
运行结果
中国代理 型号lenvo0001 价格10000.0 描述信息disk 160G , memory 2G 日本代理 型号lenvo0001 价格10000.0 描述信息disk 160G , memory 2G 印度代理 型号hp0001 价格12000.0 描述信息disk 250G , memory 2G 韩国代理 型号hp0001 价格12000.0 描述信息disk 250G , memory 2G
达到的效果一样吧,
看仔细了,真的一样吗
其实看看2个运行的顺序,其实是相反的.难道有错,其实谁都没错,只是计算的方法不一样.
但是如果我们依赖了他们的顺序,就是我们错了,错误的顺序导致错误的结果.
以下引用
java.util.Observable的黑暗面 如同你发现的.可观察者是一个类而不是一个接口,更糟糕的是,他甚至没有实现一个接口.不幸的是,java.util.Observable的实现有许多的问题,限制了她的使用和复用.这并不是说他没提供有用的功能,我们只是提醒大家注意一些事实. Observable 是一个类 首先,因为Observable是一个类.你必须设计一个类继承他,如果某类想同时具有Observable类和另外一个超类的行为,你就会陷入两难,JAVA不支持多重继承.这限制了Observable的复用能力. 再者,因为没有Observable接口,所有无法建立自己的实现.和JAVA内置的ObserverAPI搭配使用,也无法将java.util的实现换成另外一套做法. Observable将关键的方法保护起来 看了ObservableAPI,你会发现setChanged()方法被保护起来,意味着,除非你继承自Observable,否则你无法创建Observable实例并组合到你自己的对象里来.这个设计违反了第二个设计原则:"多用组合,少用继承"
其实观察者模式差不多已经了解了,主要的写法就这些.
当然不在乎怎么写,在乎你怎么想,主要还是思维上要有面向接口和面向对象的思维,面向接口是门很深的学问
介绍一本设计模式的书<Head.First.设计模式.中文版>
很想发到论坛上,结果发现好象没发成功,郁闷,算 了
单机的应用软件可能还可以用上。
设计模式不是设计的,是总结的。
但最后,我终于明白:实际工作里面的设计模式,还是要靠我们自己来发现。
简单易懂.