補-設計模式之觀察者模式(九)

image

觀察者模式,顧名思義就是觀察與被觀察的關系,比如你在燒開水得時時看著它開沒開,你就是觀察者,開水就是被觀察者;比如商品缺貨,你會看商品是否來貨,你就是觀察者,商品就是被觀察者。


一、觀察者又叫做發(fā)布訂閱(Publish/Subscribe)模式

??觀察者模式定義一種一對多的依賴關系,讓多個觀察者對象同時監(jiān)聽某一個主題對象,這個主題對象在狀態(tài)發(fā)生變化時,會通知所有觀察者對象,使他們能夠自動更新自己。

  • 觀察者模式結構
image
  • Subject類

??它把所有對觀察者對象的引用保存在一個集合里,每個主題都可以有任何數(shù)量的觀察者,抽象主題提供一個接口,可以增加和刪除觀察者對象。

  • Observer類

??抽象觀察者,為所有的具體觀察者定義一個接口,在得到主題的通知時更新自己。

  • ConcreteSubject類

??具體主題將有關狀態(tài)存入具體觀察者對象,在具體主題的內(nèi)部狀態(tài)改變時,給所有登記過的觀察者發(fā)出通知。

  • ConcreObserver類

??具體觀察者,實現(xiàn)抽象觀察者角色所要求的更新接口,以便使本身的狀態(tài)與主題的狀態(tài)相協(xié)調(diào)。


二、觀察者模式典型代碼

  • Subject類(抽象通知者)
public abstract class Subject {
   // 定義一個觀察者集合用于存儲所有觀察者對象  
   protected List<Observer> list = new ArrayList<Observer>();

   // 增加觀察者
   public void attach (Observer observer) {
       list.add(observer);
   }
   // 移除觀察者
   public void detach (Observer observer) {
      list.remove(observer);
   }
   // 通知者
   public void Notify ();
}
  • Observer 類(抽象觀察者)
public abstract class Observer {

       public abstract void update();
}
  • ConcreteSubject類(具體通知者)
public class ConcreteSubject extends  Subject {

       private String subjectState;

       public String getSubjectState() {
             return subjectState;
       }

       public void setSubjectState(String subjectState) {
             this.subjectState = subjectState;
       }

       @Override
       public void Notify() {
            for (Observer observer: list) {
                  observer.update();
            }
       }
}
  • ConcreteObserver類(具體觀察者)
public class ConcreteObserver extends Observer {

       private String name;

       private String observerState;

       private ConcreteSubject concreteSubject;

       public ConcreteObserver(ConcreteSubject concreteSubject,String name) {
             this.concreteSubject = concreteSubject;
             this.name = name;
       }

       @Override
       public void update() {
             observerState = concreteSubject.getSubjectState();
             System.out.println("name :"+name);
             System.out.println("observerState :"+observerState);
      }

       public ConcreteSubject getConcreteSubject() {
              return concreteSubject;
       } 

       public void setConcreteSubject(ConcreteSubject concreteSubject) {
             this.concreteSubject = concreteSubject;
       }
}
  • 測試方法
  public static void main(String[] args) {
    ConcreteSubject s = new ConcreteSubject();
    s.attach(new ConcreteObserver(s,"X"));
    s.attach(new ConcreteObserver(s,"Y"));
    s.attach(new ConcreteObserver(s,"Z"));
    s.setSubjectState("ABC");
    s.Notify();

}
  • 測試結果如下
 name :X
 observerState :ABC
 name :Y
 observerState :ABC
 name :Z
 observerState :ABC

??通過上邊的例子可以看出增加3個觀察者分別是X、Y、Z進行通知消息ABC。


三、實際應用

??軟件公司欲開發(fā)一款多人聯(lián)機對戰(zhàn)游戲(類似魔獸世界、星際爭霸等游戲),在該游戲中,多個玩家可以加入同一戰(zhàn)隊組成聯(lián)盟,當戰(zhàn)隊中某一成員受到敵人攻擊時將給所有其他盟友發(fā)送通知,盟友收到通知后將作出響應。


  • 抽象觀察類
interface Observer {
    public String getName();
    public void setName(String name);
    public void help(); //聲明支援盟友方法
    public void beAttacked(AllyControlCenter acc); //聲明遭受攻擊方法
}
  • 戰(zhàn)隊成員類:具體觀察者類
class Player implements Observer {
      private String name;

      public Player(String name) {
           this.name = name;
      }

      public void setName(String name) {
           this.name = name;
      }

      public String getName() {
           return this.name;
      }

      //支援盟友方法的實現(xiàn)
      public void help() {
           System.out.println("堅持住," + this.name + "來救你!");
      }

      //遭受攻擊方法的實現(xiàn),當遭受攻擊時將調(diào)用戰(zhàn)隊控制中心類的通知方法notifyObserver()來通知盟友
      public void beAttacked(AllyControlCenter acc) {
           System.out.println(this.name + "被攻擊!");
           acc.notifyObserver(name);       
      }
}
  • 戰(zhàn)隊控制中心類:目標類
abstract class AllyControlCenter {
       protected String allyName; //戰(zhàn)隊名稱
       protected ArrayList<Observer> players = new ArrayList<Observer>(); //定義一個集合用于存儲戰(zhàn)隊成員

       public void setAllyName(String allyName) {
           this.allyName = allyName;
       }

       public String getAllyName() {
           return this.allyName;
       }

        //注冊方法
       public void join(Observer obs) {
           System.out.println(obs.getName() + "加入" + this.allyName + "戰(zhàn)隊!");
           players.add(obs);
       }

       //注銷方法
       public void quit(Observer obs) {
           System.out.println(obs.getName() + "退出" + this.allyName + "戰(zhàn)隊!");
           players.remove(obs);
       }

        //聲明抽象通知方法
       public abstract void notifyObserver(String name);
}
  • 具體戰(zhàn)隊控制中心類:具體目標類
class ConcreteAllyControlCenter extends AllyControlCenter {
public ConcreteAllyControlCenter(String allyName) {
    System.out.println(allyName + "戰(zhàn)隊組建成功!");
    System.out.println("----------------------------");
    this.allyName = allyName;
}

//實現(xiàn)通知方法
public void notifyObserver(String name) {
    System.out.println(this.allyName + "戰(zhàn)隊緊急通知,盟友" + name + "遭受敵人攻擊!");
    //遍歷觀察者集合,調(diào)用每一個盟友(自己除外)的支援方法
    for(Object obs : players) {
        if (!((Observer)obs).getName().equalsIgnoreCase(name)) {
            ((Observer)obs).help();
        }
    }       
}
}
  • 測試類
class Client {  
public static void main(String args[]) {  
    //定義觀察目標對象  
    AllyControlCenter acc;  
    acc = new ConcreteAllyControlCenter("金庸群俠");  

    //定義四個觀察者對象  
    Observer player1,player2,player3,player4;  

    player1 = new Player("楊過");  
    acc.join(player1);  

    player2 = new Player("令狐沖");  
    acc.join(player2);  

    player3 = new Player("張無忌");  
    acc.join(player3);  

    player4 = new Player("段譽");  
    acc.join(player4);  

    //某成員遭受攻擊  
    player1.beAttacked(acc);  
}  
}
  • 測試結果
金庸群俠戰(zhàn)隊組建成功!
----------------------------
楊過加入金庸群俠戰(zhàn)隊!
令狐沖加入金庸群俠戰(zhàn)隊!
張無忌加入金庸群俠戰(zhàn)隊!
段譽加入金庸群俠戰(zhàn)隊!
楊過被攻擊!
金庸群俠戰(zhàn)隊緊急通知,盟友楊過遭受敵人攻擊!
堅持住,令狐沖來救你!
堅持住,張無忌來救你!
堅持住,段譽來救你!
  • 適用場景

??當一個對象需要同時改變其他對象的時候,而且它不知道具體有多少個對象有待改變時,應考慮使用觀察者模式。
??當一個模型有2個方面,其中一個方面依賴于另一個方面,這時使用觀察者模式可以將這兩者封裝在獨立的對象中使它們各自可以獨立的改變和使用。

  • 應用場景

??觀察者模式還可以用于網(wǎng)絡中的客戶端和服務器,比如手機中的各種App的消息推送,服務端是觀察者,各個手機App是被觀察者,一旦服務器上的數(shù)據(jù)(如App升級信息)有更新,就會被推送到手機客戶端。

  • 觀察模式與MVC

??在當前流行的MVC(Model-View-Controller)架構中也應用了觀察者模式,MVC是一種架構模式,它包含三個角色:模型(Model),視圖(View)和控制器(Controller)。其中模型可對應于觀察者模式中的觀察目標,而視圖對應于觀察者,控制器可充當兩者之間的中介者。當模型層的數(shù)據(jù)發(fā)生改變時,視圖層將自動改變其顯示內(nèi)容。



??模型層提供的數(shù)據(jù)是視圖層所觀察的對象,在視圖層中包含兩個用于顯示數(shù)據(jù)的圖表對象,一個是柱狀圖,一個是餅狀圖,相同的數(shù)據(jù)擁有不同的圖表顯示方式,如果模型層的數(shù)據(jù)發(fā)生改變,兩個圖表對象將隨之發(fā)生變化,這意味著圖表對象依賴模型層提供的數(shù)據(jù)對象,因此數(shù)據(jù)對象的任何狀態(tài)改變都應立即通知它們。同時,這兩個圖表之間相互獨立,不存在任何聯(lián)系,而且圖表對象的個數(shù)沒有任何限制,用戶可以根據(jù)需要再增加新的圖表對象,如折線圖。在增加新的圖表對象時,無須修改原有類庫,滿足“開閉原則”。、


四、觀察模式優(yōu)缺點

  • 觀察模式的優(yōu)點

??1、觀察者模式可以實現(xiàn)表示層和數(shù)據(jù)邏輯層的分離,定義了穩(wěn)定的消息更新傳遞機制,并抽象了更新接口,使得可以有各種各樣不同的表示層充當具體觀察者角色。

??2、觀察者模式在觀察目標和觀察者之間建立一個抽象的耦合。觀察目標只需要維持一個抽象觀察者的集合,無須了解其具體觀察者。由于觀察目標和觀察者沒有緊密地耦合在一起,因此它們可以屬于不同的抽象化層次。

??3、觀察者模式支持廣播通信,觀察目標會向所有已注冊的觀察者對象發(fā)送通知,簡化了一對多系統(tǒng)設計的難度。

??4、觀察者模式滿足“開閉原則”的要求,增加新的具體觀察者無須修改原有系統(tǒng)代碼,在具體觀察者與觀察目標之間不存在關聯(lián)關系的情況下,增加新的觀察目標也很方便。

  • 觀察模式的缺點

??1、如果一個觀察目標對象有很多直接和間接觀察者,將所有的觀察者都通知到會花費很多時間。

??2、如果在觀察者和觀察目標之間存在循環(huán)依賴,觀察目標會觸發(fā)它們之間進行循環(huán)調(diào)用,可能導致系統(tǒng)崩潰。

??3、觀察者模式?jīng)]有相應的機制讓觀察者知道所觀察的目標對象是怎么發(fā)生變化的,而僅僅只是知道觀察目標發(fā)生了變化。

  • 總結

??觀察者模式所做的工作其實就是在解耦,讓解耦的雙方依賴,依賴于抽象,而不是依賴于具體,從而使各自的變化都不會影響另一邊的變化。凡事涉及到一對一或者一對多的對象交互的場景都可以使用觀察者模式。


?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容