Java - 觀察者模式(Observer)範例

偶然間讀到歐萊禮的Java網路程式設計一書,在講解Thread時有提到當下的thread執行run

method時產生一編碼資料並存進陣列內,但是在run還沒有執行完的當下,另一區塊的程式

卻因為該陣列內尚沒有字串的資料而讀取使用導致NullPointerException,因此有應用到

Observer的技巧來巧妙的避開這個問題。

後來發現原來Java API的util有相關Observer、Observable可使用,因此在這邊也撰寫了一範例

來套用看看相關功能。

首先觀察者模式的建構,會定義被觀察者觀察者,為一對多的關係,即被觀察者可能被多

個觀察者給觀察,當被觀察者呼叫setChanged()及notifyObservers(Object obj)後,各觀察者即

可以透過override的update(Observable o, Object arg) method來取得被觀察者當下的成員變數

資料(get)及傳送過來的Object obj的參數實體。

由此可以知道被觀察者並不知道有哪些觀察者的存在,他只要通知所有的觀察者及把他要

pass的訊息給發送出去,觀察者即可以利用這份資料來做下一步的處理。


緊接著,在這邊小弟將撰寫一個抽獎的範例來套用看看觀察者設計模式

一、定義被觀察者(Observable)

  1. public class LotteryObservable extends Observable{
  2. private List<Participant> personList;
  3. private StringBuilder awardMsg;
  4. public LotteryObservable(File f){
  5. personList = LoadParticipantList(f);
  6. }
  7. public List<Participant> LoadParticipantList(File f){
  8. List<Participant> plist = new ArrayList<Participant>();
  9. try (BufferedReader br = new BufferedReader(new FileReader(f))){
  10. String data = null;
  11. System.out.println("參加抽獎的名單如下:");
  12. while((data = br.readLine()) != null){
  13. Participant p = new Participant(data);
  14. System.out.println(p);
  15. plist.add(p);
  16. }
  17. System.out.println("==============公布得獎名單==============");
  18. } catch (FileNotFoundException e) {
  19. // TODO Auto-generated catch block
  20. System.out.println("指定檔案不存在!!");
  21. } catch(IOException e){
  22. System.out.println("IOException");
  23. }
  24. return plist;
  25. }
  26. public void processLottery(int p_num, AwardType atype){
  27. Random rmd = new Random();
  28. List<Participant> awardMenu = new ArrayList<Participant>();
  29. awardMsg = new StringBuilder();
  30. if(AwardType.THIRD_AWARD == atype)awardMsg.append("抽出三獎得主:\n");
  31. else if(AwardType.SPECIAL_AWAED == atype)awardMsg.append("抽出二獎得主:\n");
  32. for(int i = 0 ; i < p_num ; i++){
  33. int n = rmd.nextInt(personList.size()-1);
  34. awardMsg.append(personList.get(n)+"\n");
  35. awardMenu.add(personList.get(n));
  36. personList.remove(n);
  37. }
  38. awardMsg.deleteCharAt(awardMsg.length()-1);
  39. this.setChanged();
  40. this.notifyObservers(awardMenu);
  41. }
  42. public StringBuilder getAwardMsg() {
  43. return awardMsg;
  44. }
  45. }

首先這個類別LotteryObservable需繼承Observable,並且定義LoadParticipantList方法來載入

抽獎者名單建立List<Participant> personList (Participant類別自行定義)

接著,processLottery方法為進行抽獎的過程,判斷當下的抽獎型態(定義enum)來顯示當下

要抽哪個獎項。再來透過亂數來取得index決定要抽出list下的Participant,並加入至awardMenu

內或轉換成字串assign至awardMsg內。

最後,呼叫setChanged及notifyObservers來知會觀察者!!

二、定義觀察者(多個)

觀察者需實作Observer並自行定義您的update方法!

1. AwardObserver 觀察者單純只是get被觀察者當下的得獎訊息來做發佈!

  1. public class AwardObserver implements Observer{
  2. @Override
  3. public void update(Observable o, Object arg) {
  4. // TODO Auto-generated method stub
  5. LotteryObservable lobs = (LotteryObservable)o;
  6. System.out.println(this.getClass().getName()+"\n"+lobs.getAwardMsg().toString());
  7. }
  8. }

2. AwardStatisticsObserver 觀察者將取得被觀察者pass過來的argument來做進一步的分析得獎

名單。區分出各獎項各有男、女各有多少人等簡單分析。

  1. public class AwardStatisticsObserver implements Observer{
  2. @Override
  3. public void update(Observable o, Object arg) {
  4. // TODO Auto-generated method stub
  5. @SuppressWarnings("unchecked")
  6. List<Participant> awardMenu = (List<Participant>) arg;
  7. Map<String, Integer> sex2num = new HashMap<String, Integer>();
  8. int count;
  9. for(Participant p : awardMenu){
  10. if(!sex2num.containsKey(p.getSex())){
  11. count = 1;
  12. }else{
  13. count = sex2num.get(p.getSex()) + 1;
  14. }
  15. sex2num.put(p.getSex(), count);
  16. }
  17. System.out.println(this.getClass().getName());
  18. for(String sex : sex2num.keySet()){
  19. System.out.println(sex + "性\t人數 => "+ sex2num.get(sex));
  20. }
  21. }
  22. }

三、定義主要類別(開辦活動)

這個類別會定義被觀察者將被哪些觀察者觀察,並且assign抽獎名單及該活動要抽出哪些

獎項及人數。

  1. public class Activity {
  2. /**
  3. * @param args
  4. */
  5. public static void main(String[] args) {
  6. // TODO Auto-generated method stub
  7. LotteryObservable lo = new LotteryObservable(new File("C:/menu.txt"));
  8. lo.addObserver(new AwardObserver());
  9. lo.addObserver(new AwardStatisticsObserver());
  10. lo.processLottery(4, AwardType.THIRD_AWARD);
  11. System.out.println("============================");
  12. lo.processLottery(3, AwardType.SECOND_AWARD);
  13. }
  14. }

四、其他程式

定義獎項的enum及抽獎者

  1. public enum AwardType {
  2. SPECIAL_AWAED, FIRST_AWARD, SECOND_AWARD, THIRD_AWARD
  3. }
  4. public class Participant {
  5. private String name;
  6. private String unit;
  7. private String sex;
  8. private int age;
  9. public Participant(String resource) {
  10. StringTokenizer st = new StringTokenizer(resource, ",");
  11. this.unit = st.nextToken();
  12. this.sex = st.nextToken();
  13. this.age = Integer.parseInt(st.nextToken());
  14. this.name = st.nextToken();
  15. }
  16. public String getName() {
  17. return name;
  18. }
  19. public void setName(String name) {
  20. this.name = name;
  21. }
  22. public String getUnit() {
  23. return unit;
  24. }
  25. public void setUnit(String unit) {
  26. this.unit = unit;
  27. }
  28. public String getSex() {
  29. return sex;
  30. }
  31. public void setSex(String sex) {
  32. this.sex = sex;
  33. }
  34. public int getAge() {
  35. return age;
  36. }
  37. public void setAge(int age) {
  38. this.age = age;
  39. }
  40. @Override
  41. public String toString() {
  42. return "["+unit+"]"+"=>"+sex+"\t"+age+"(age)\t"+name;
  43. }
  44. }

Demo畫面



最後,小弟在此只是簡單講述Observer模式的概念及藉由抽獎的議題來自行發揮Observer

模式,若概念講述有誤或範例舉的不貼切請不吝指教囉!

留言