今天放假,又有時間繼續啃《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日游.
適合使用觀察者模式的情況:
①、當一個對象的數據更新時需要通知其他的對象,但這個對象又不希望和被通知的那些對象形成緊耦合。
②、當一個對象的數據更新時,這個對象需要讓其他對象也各自更新自己的數據,但這個對象不知道具體有多少個對象要更新數據。