设计模式之观察者模式
今天放假,又有时间继续啃《java设计模式》这本书了。每次学会一种设计模式内心都会有一种小小的成就感,但是懂是懂了,不知道会不会用。主要是现在没有什么项目经验,设计模式学了也派不上用场。不管怎样,学了总比没学好,以后总会派上用场的。
首先,何为观察者模式?观察者模式是关于多个对象想知道一个对象中数据的变化的情况一种模式。比如说现在几乎所有的高校附近都会有大学生兼职中心,也就是兼职中介吧(大一大二的时候还去过,加会员还交了100大洋呢。),兼职中心每天都会把兼职信息通知给所有的会员。这就是一个观察者模式,这里会员也就是观察者,兼职中心就是被观察者,也称作主题。
观察者模式结构中包括四种角色:
一、主题:主题是一个接口,该接口规定了具体主题需要实现的方法,比如添加、删除观察者以及通知观察者更新数据的方法。
二、观察者:观察者也是一个接口,该接口规定了具体观察者用来更新数据的方法。
三、具体主题:具体主题是一个实现主题接口的类,该类包含了会经常发生变化的数据。而且还有一个集合,该集合存放的是观察者的引用。
四:具体观察者:具体观察者是实现了观察者接口的一个类。具体观察者包含有可以存放具体主题引用的主题接口变量,以便具体观察者让具体主题将自己的引用添加到具体主题的集合中,让自己成为它的观察者,或者让这个具体主题将自己从具体主题的集合中删除,使自己不在时它的观察者。
在上面的兼职中心的例子中,兼职中心就是一个具体主题,它可以更新各种兼职信息,并且通知给它的会员,当然还可以增加会员和删除会员。会员就是一个观察者,它关注着兼职中心的信息,能够及时收到兼职中心更新过来的信息。
代码如下:
1.主题
1 package com.observer; 2 3 public interface Subject { 4 void addObserver(Observer o); 5 void deleteObserver(Observer o); 6 void notifyObservers(); 7 }
2.具体主题
1 package com.observer; 2 3 import java.util.ArrayList; 4 5 public class SeekJobCenter implements Subject { 6 String mess; 7 boolean changed; 8 ArrayList<Observer> personList; 9 public SeekJobCenter() { 10 personList = new ArrayList<Observer>(); 11 mess = ""; 12 changed = false; 13 } 14 //添加求职者 15 public void addObserver(Observer o) { 16 if(!(personList.contains(o))){ 17 personList.add(o); 18 } 19 } 20 //删除求职者 21 public void deleteObserver(Observer o) { 22 if(personList.contains(o)){ 23 personList.remove(o); 24 } 25 } 26 //通知所有求职者 27 public void notifyObservers() { 28 if(changed){ 29 for (int i = 0; i < personList.size(); i++) { 30 Observer o = personList.get(i); 31 o.hearTelephone(mess); 32 } 33 changed = false; 34 } 35 } 36 //刷新信息 37 public void giveNewMess(String str){ 38 if(str.equals(mess)){ 39 changed = false; 40 }else{ 41 mess = str; 42 changed = true; 43 } 44 } 45 46 }
3.观察者
1 package com.observer; 2 3 public interface Observer { 4 public void hearTelephone(String mess); 5 }
4.具体观察者
在该例子中,具体观察者有两个,一个关注兼职中心的所有信息,另一个只关注“促销员”和“发单员”的兼职信息。
UniversityStudent.java
1 package com.observer; 2 3 import java.io.File; 4 import java.io.FileNotFoundException; 5 import java.io.IOException; 6 import java.io.RandomAccessFile; 7 8 public class UniversityStudent implements Observer{ 9 Subject subject; 10 File myFile; 11 public UniversityStudent(Subject subject,String fileName) { 12 this.subject = subject; 13 subject.addObserver(this); 14 myFile = new File(fileName); 15 } 16 public void hearTelephone(String mess) { 17 try { 18 RandomAccessFile out = new RandomAccessFile(myFile,"rw"); 19 out.seek(out.length()); 20 byte[] b = mess.getBytes(); 21 out.write(b); 22 System.out.println("我是会员一"); 23 System.out.println("我向文件"+myFile.getName()+"写入如下内容:"); 24 System.out.println(mess); 25 } catch (IOException e) { 26 e.printStackTrace(); 27 } 28 } 29 30 }
UniversityStudent2.java
1 package com.observer; 2 3 import java.io.File; 4 import java.io.IOException; 5 import java.io.RandomAccessFile; 6 7 public class UniversityStudent2 implements Observer { 8 Subject subject; 9 File myFile; 10 public UniversityStudent2(Subject subject,String fileName) { 11 this.subject = subject; 12 subject.addObserver(this); 13 myFile = new File(fileName); 14 } 15 public void hearTelephone(String mess) { 16 try { 17 boolean boo = mess.contains("促销员")||mess.contains("发单员"); 18 if(boo){ 19 RandomAccessFile out = new RandomAccessFile(myFile,"rw"); 20 out.seek(out.length()); 21 byte[] b = mess.getBytes(); 22 out.write(b); 23 System.out.println("我是会员二"); 24 System.out.println("我向文件"+myFile.getName()+"写入如下内容:"); 25 System.out.println(mess); 26 } 27 } catch (IOException e) { 28 e.printStackTrace(); 29 } 30 } 31 }
下面写一个测试程序:
1 package com.observer; 2 3 public class Application { 4 /** 5 * @param args 6 */ 7 public static void main(String[] args) { 8 SeekJobCenter center = new SeekJobCenter(); 9 UniversityStudent zhanglin = new UniversityStudent(center,"zhanglin.txt"); 10 UniversityStudent2 wangHao = new UniversityStudent2(center,"wanghao.txt"); 11 center.giveNewMess("XX公司需要10个促销员。"); 12 center.notifyObservers(); 13 center.giveNewMess("XX公司需要8个发单员。"); 14 center.notifyObservers(); 15 center.giveNewMess("XX公司需要9个临时工。"); 16 center.notifyObservers(); 17 center.giveNewMess("XX公司需要9个临时工。"); 18 center.notifyObservers(); 19 } 20 21 }
运行结果如下:
1 我是会员一 2 我向文件zhanglin.txt写入如下内容: 3 XX公司需要10个促销员。 4 我是会员二 5 我向文件wanghao.txt写入如下内容: 6 XX公司需要10个促销员。 7 我是会员一 8 我向文件zhanglin.txt写入如下内容: 9 XX公司需要8个发单员。 10 我是会员二 11 我向文件wanghao.txt写入如下内容: 12 XX公司需要8个发单员。 13 我是会员一 14 我向文件zhanglin.txt写入如下内容: 15 XX公司需要9个临时工。
兼职中心这个例子使用的事观察者模式中的“推”数据方式,也就是说具体主题将变化后的信息全部发送给具体观察者。这种方式往往适用于具体主题认为具体观察者需要这些变化后的全部数据时。
设计模式中还有另外一种方式也就是“拉”数据方式。如果使用这种方式,具体主题信息一更新,它只会通知所有的观察者:“我的信息已经更新”,具体观察者就可以调用相关方法,获得自己需要的那部分信息。
下面看另外一个例子:
问题描述:一家商店每天都发布当天打折商品的名字、原价和折后价,有两位顾客对此信息很感兴趣,但是一位顾客只关心打折商品的名称,而另一位顾客只关心原价和折后价。
1.主题:
1 package com.observer1; 2 3 public interface Subject { 4 void addObserver(Observer o); 5 void deleteObserver(Observer o); 6 void notifyObservers(); 7 }
2.具体主题:
1 package com.observer1; 2 3 import java.util.ArrayList; 4 5 public class ShopSubject implements Subject { 6 String goodsName; 7 double oldPrice,newPrice; 8 ArrayList<Observer> customerList; 9 public ShopSubject() { 10 customerList = new ArrayList<Observer>(); 11 } 12 public void addObserver(Observer o) { 13 if(!(customerList.contains(o))){ 14 customerList.add(o); 15 } 16 } 17 18 public void deleteObserver(Observer o) { 19 if(customerList.contains(o)){ 20 customerList.remove(o); 21 } 22 } 23 24 public void notifyObservers() { 25 for (int i = 0; i < customerList.size(); i++) { 26 Observer o = customerList.get(i); 27 o.update(); 28 } 29 } 30 //设置打折商品 31 public void setDiscountGoods(String name,double oldP,double newP){ 32 goodsName = name; 33 oldPrice = oldP; 34 newPrice = newP; 35 //设置完通知顾客 36 notifyObservers(); 37 } 38 public String getGoodsName() { 39 return goodsName; 40 } 41 public double getOldPrice() { 42 return oldPrice; 43 } 44 public double getNewPrice() { 45 return newPrice; 46 } 47 48 49 }
3.观察者:
这里不是由具体主题直接更新数据过来,所以update()方法不需要传参数过来。
1 package com.observer1; 2 3 public interface Observer { 4 public void update(); 5 }
4.具体观察者:
CustomerOne对象的顾客只对打折商品的名字感兴趣,对其他信息不感兴趣。
CustomerOne.java
1 package com.observer1; 2 3 public class CustomerOne implements Observer { 4 Subject subject; 5 String goodsName,personName; 6 public CustomerOne(Subject subject,String personName) { 7 this.personName = personName; 8 this.subject = subject; 9 subject.addObserver(this); 10 } 11 public void update() { 12 if(subject instanceof ShopSubject){ 13 goodsName = ((ShopSubject)subject).getGoodsName(); 14 System.out.println(personName+"只对打折商品的名字感兴趣:"); 15 System.out.println("打折商品的名字是:"+goodsName); 16 } 17 } 18 19 }
CustomerTwo的对象的顾客只对商品原价和折后价感兴趣,对商品名称不感兴趣。
CustomerTwo.java
1 package com.observer1; 2 3 public class CustomerTwo implements Observer { 4 Subject subject; 5 String personName; 6 double oldPrice,newPrice; 7 public CustomerTwo(Subject subject,String personName) { 8 this.personName = personName; 9 this.subject = subject; 10 subject.addObserver(this); 11 } 12 public void update() { 13 if(subject instanceof ShopSubject){ 14 oldPrice = ((ShopSubject)subject).getOldPrice(); 15 newPrice = ((ShopSubject)subject).getNewPrice(); 16 System.out.println(personName+"只对打折商品的原价和折后价感兴趣:"); 17 System.out.println("打折商品的原价是:"+oldPrice); 18 System.out.println("打折商品的折后价是:"+newPrice); 19 } 20 } 21 22 }
下面写一个测试程序:
1 package com.observer1; 2 3 public class Application { 4 public static void main(String[] args) { 5 ShopSubject shop = new ShopSubject(); 6 CustomerOne boy = new CustomerOne(shop,"小明"); 7 CustomerTwo girl = new CustomerTwo(shop,"小红"); 8 shop.setDiscountGoods("Photo数码相机", 2345.6, 2020.0); 9 shop.setDiscountGoods("三星手机", 2999.0, 2499.0); 10 } 11 }
运行结果为:
1 小明只对打折商品的名字感兴趣: 2 打折商品的名字是:Photo数码相机 3 小红只对打折商品的原价和折后价感兴趣: 4 打折商品的原价是:2345.6 5 打折商品的折后价是:2020.0 6 小明只对打折商品的名字感兴趣: 7 打折商品的名字是:三星手机 8 小红只对打折商品的原价和折后价感兴趣: 9 打折商品的原价是:2999.0 10 打折商品的折后价是:2499.0
观察者与多主题
上面两个例子所讲的都是一个具体主题多个观察者的情况。还有一种情况就是一个具体观察者观察多个主题,当观察的任何具体主题的信息发生变化时,该观察者都能得到通知。在使用多主题时,主题应该采用拉数据方式,观察者接口可以将更新数据方法的参数类型设置为主题接口类型,比如update(Subject subject),即具体主题数据发生改变时,将自己的引用传递给具体观察者,然后具体观察者让这个具体主题调用相关方法获得信息。
下面是一个简单的多主题例子
问题描述:李先生计划去旅游,那么他需要关注旅行社的信息,同时还要关注旅行地区的天气问题。
根据观察者模式,李先生就是一个具体观察者,而气象站和旅行社就是他观察的两个具体主题。主要代码如下:
1.主题
1 package com.observer2; 2 3 public interface Subject { 4 void addObserver(Observer o); 5 void deleteObserver(Observer o); 6 void notifyObservers(); 7 }
2.观察者
1 package com.observer2; 2 3 public interface Observer { 4 public void update(Subject subject); 5 }
3.具体主题:
TravelAgency.java
1 package com.observer2; 2 3 import java.util.ArrayList; 4 5 public class TravelAgency implements Subject{ 6 String tourStartTime; 7 String tourMess; 8 ArrayList<Observer> personList; 9 public TravelAgency() { 10 personList = new ArrayList<Observer>(); 11 } 12 public void addObserver(Observer o) { 13 if(o == null){ 14 return; 15 }else{ 16 if(!(personList.contains(o))){ 17 personList.add(o); 18 } 19 } 20 } 21 public void deleteObserver(Observer o) { 22 if(personList.contains(o)){ 23 personList.remove(o); 24 } 25 26 } 27 public void notifyObservers() { 28 for (int i = 0; i < personList.size(); i++) { 29 Observer o = personList.get(i); 30 o.update(this); 31 } 32 } 33 public void giveMess(String time,String mess){ 34 tourStartTime = time; 35 tourMess = mess; 36 notifyObservers(); 37 } 38 public String getTourStartTime() { 39 return tourStartTime; 40 } 41 public String getTourMess() { 42 return tourMess; 43 } 44 }
WeaherStation.java
1 package com.observer2; 2 3 import java.util.ArrayList; 4 5 public class WeaherStation implements Subject{ 6 String forecastTime,forcastMess; 7 int maxPemperature,minTemperature; 8 ArrayList<Observer> personList; 9 public WeaherStation() { 10 personList = new ArrayList<Observer>(); 11 } 12 public void addObserver(Observer o) { 13 if(o == null){ 14 return; 15 }else{ 16 if(!(personList.contains(o))){ 17 personList.add(o); 18 } 19 } 20 } 21 public void deleteObserver(Observer o) { 22 if(personList.contains(o)){ 23 personList.remove(o); 24 } 25 26 } 27 public void notifyObservers() { 28 for (int i = 0; i < personList.size(); i++) { 29 Observer o = personList.get(i); 30 o.update(this); 31 } 32 } 33 public void doForeCast(String t,String mess,int max,int min){ 34 forecastTime = t; 35 forcastMess = mess; 36 minTemperature = min; 37 maxPemperature = max; 38 notifyObservers(); 39 } 40 public String getForecastTime() { 41 return forecastTime; 42 } 43 public String getForcastMess() { 44 return forcastMess; 45 } 46 public int getMaxPemperature() { 47 return maxPemperature; 48 } 49 public int getMinTemperature() { 50 return minTemperature; 51 } 52 53 54 }
具体观察者:
1 package com.observer2; 2 3 public class Person implements Observer { 4 Subject subjectOne,subjectTwo; //可依赖的主题 5 String forecastTime,forecastMess; 6 String tourStartTime,tourMess; 7 int maxTemperature,minTemperature; 8 public Person(Subject one,Subject two) { 9 this.subjectOne = one; 10 this.subjectTwo = two; 11 subjectOne.addObserver(this); 12 subjectTwo.addObserver(this); 13 } 14 public void update(Subject subject) { 15 if(subject instanceof WeaherStation){ 16 WeaherStation ws = (WeaherStation)subject; 17 forecastTime = ws.getForecastTime(); 18 forecastMess = ws.getForcastMess(); 19 maxTemperature = ws.getMaxPemperature(); 20 minTemperature = ws.getMinTemperature(); 21 System.out.print("预报日期:"+forecastTime+","); 22 System.out.print("天气状况:"+forecastMess+","); 23 System.out.print("最高温度:"+maxTemperature+","); 24 System.out.println("最低温度:"+minTemperature+"."); 25 }else if(subject instanceof TravelAgency){ 26 TravelAgency ta = (TravelAgency)subject; 27 tourStartTime = ta.getTourStartTime(); 28 tourMess = ta.getTourMess(); 29 System.out.print("旅游开始日期:"+tourStartTime+","); 30 System.out.println("旅游信息:"+tourMess+"."); 31 } 32 } 33 34 }
下面写一个测试程序:
1 package com.observer2; 2 3 public class Application { 4 public static void main(String[] args) { 5 WeaherStation weaherStation = new WeaherStation();//具体主题 6 TravelAgency travelAgency = new TravelAgency(); //具体主题 7 Person boy = new Person(weaherStation,travelAgency); 8 weaherStation.doForeCast("10日", "阴有小雨", 28, 20); 9 travelAgency.giveMess("10日", "黄山2日游"); 10 weaherStation.doForeCast("11日", "晴转多云", 30, 21); 11 travelAgency.giveMess("11日", "丽江1日游"); 12 } 13 14 }
运行结果如下:
1 预报日期:10日,天气状况:阴有小雨,最高温度:28,最低温度:20. 2 旅游开始日期:10日,旅游信息:黄山2日游. 3 预报日期:11日,天气状况:晴转多云,最高温度:30,最低温度:21. 4 旅游开始日期:11日,旅游信息:丽江1日游.
适合使用观察者模式的情况:
①、当一个对象的数据更新时需要通知其他的对象,但这个对象又不希望和被通知的那些对象形成紧耦合。
②、当一个对象的数据更新时,这个对象需要让其他对象也各自更新自己的数据,但这个对象不知道具体有多少个对象要更新数据。