天天看點

C# 設計模式(九)觀察者模式(unity示範)

      • 1、引言
      • 2、觀察者模式詳細介紹
        • 2.1、定義
        • 2.2、解決的問題
        • 2.3、模式的原理
        • 2.4、類圖的實作
        • 2.5、C#舉例(委托實作)
      • 3、觀察者模式優缺點
      • 4、觀察者模式适用場景
      • 5、應用舉例(unity)
      • 6、總結
      • 7、unity工程下載下傳

1、引言

  在我們平時的軟體開發中,觀察者模式是我們常用的一種模式。在現實生活中,觀察者模式也是處處可見。例如:微信中的訂閱号,部落格的訂閱和微網誌中的關注好友,這些都屬于觀察者模式的應用。下面是一些我對觀察者模式的了解。

2、觀察者模式詳細介紹

2.1、定義

  • 觀察者模式(Observer Pattern)

      觀察者模式又叫做釋出-訂閱(Publoish/Subscribe)模式。觀察者模式定義了一種一對多的依賴關系,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀态發生變化時,會通知所有觀察者對象,使他們能夠自動更新自己。

2.2、解決的問題

  一個對象狀态改變通知其他對象的問題,而且要考慮到易用和低耦合,保證高度的協作。

2.3、模式的原理

下面時該模式的UML類圖:

C# 設計模式(九)觀察者模式(unity示範)

通過上圖我們不難發現,觀察者模式中的角色如下:

  • 抽象主題角色(Subject):抽象主題把所有觀察者對象的引用儲存在一個清單中,并提供增加和删除觀察者對象的操作,抽象主題角色又叫做抽象被觀察者角色,一般由抽象類或接口實作。
  • 抽象觀察者角色(Observer):為所有具體觀察者定義一個接口,在得到主題通知時更新自己,一般由抽象類或接口實作。
  • 具體主題角色(ConcreteSubject):實作抽象主題接口,具體主題角色又叫做具體被觀察者角色。
  • 具體觀察者角色(ConcreteObserver):實作抽象觀察者角色所要求的接口,以便使自身狀态與主題的狀态相協調。

2.4、類圖的實作

下面我們來實作類圖中的代碼:

觀察者抽象類:

/// <summary>
/// 抽象觀察者類
/// </summary>
abstract class Observer
{
    /// <summary>
    /// 為所有觀察者定義接口(更新接口,接到通知時更新自己)
    /// </summary>
    public abstract void Update();
}
           

抽象主題類:

/// <summary>
/// 抽象主題(抽象通知者)類
/// </summary>
abstract class Subject
{
    private IList<Observer> observers = new List<Observer>();

    /// <summary>
    /// 增加觀察者
    /// </summary>
    /// <param name="observer"></param>
    public void Attach(Observer observer)
    {
        observers.Add(observer);
    }

    /// <summary>
    /// 移除觀察者
    /// </summary>
    /// <param name="observer"></param>
    public void Detach(Observer observer)
    {
        observers.Remove(observer);
    }

    /// <summary>
    /// 通知所有的觀察者
    /// </summary>
    public void Notify()
    {
        foreach (Observer observer in observers)
        {
            observer.Update();
        }
    }
}
           

具體觀察者:

/// <summary>
/// 具體主題(具體通知者)
/// 該類中存在一個狀态,改狀态變化時通知所有的觀察者
/// </summary>
class ConcreteSubject : Subject
{
    private string subjectState;
    public string SubjectState
    {
        get { return subjectState; }
        set { subjectState = value; }
    }
}
           

具體觀察者類:

/// <summary>
/// 具體觀察者
/// 實作抽象觀察者的所有接口,以便于本省狀态和通知者狀态協調
/// </summary>
class ConcreteObserver : Observer
{
    private string name;
    private string observerState;
    private ConcreteSubject subject;
    public ConcreteSubject Subject
    {
        get { return subject; }
        set { subject = value; }
    }

    public ConcreteObserver(ConcreteSubject subject,string name)
    {
        this.subject = subject;
        this.name = name;
    }

    public override void Update()
    {
        observerState = subject.SubjectState;
        Console.WriteLine("觀察者{0}的新狀态是:{1}", name, observerState);
    }        
}
           

測試一下:

//具體通知者
ConcreteSubject s = new ConcreteSubject();

s.Attach(new ConcreteObserver(s, "XX"));
s.Attach(new ConcreteObserver(s, "YY"));
s.Attach(new ConcreteObserver(s, "ZZ"));

s.SubjectState = "ABC";
s.Notify();
           

測試結果如下:

觀察者XX的新狀态是:ABC
觀察者YY的新狀态是:ABC
觀察者ZZ的新狀态是:ABC
           

  通過對類圖的實作我想你已經對觀察模式的原理已經了解了,下面我們再來舉例說明一下,來加深了解。

2.5、C#舉例(委托實作)

  • 背景:老闆回到公司,同僚們都在努力工作,看NBA的張三、聊天的李四發現情況後立即停下與工作無關的事,繼續上班。

  我們平時在寫一個觀察者模式時,可能我們的類中也許并不是這個更新方法滿足上面的接口。也就是說抽象通知者依賴抽象觀察者這個接口不存在,或者具體觀察者沒有實作這個更新方法,那麼這個通知就無法完成了。此時我們可以使用事件委托實作。下面我們看具體代碼:

通知者接口(可能我們這個類隻是要具有通知的功能,可能不需要實作這個類,此時用接口比較合适):

/// <summary>
/// 通知者
/// </summary>
interface Notify
{
    /// <summary>
    /// 通知方法
    /// </summary>
    void Notify();

    /// <summary>
    /// 屬性
    /// <summary>
    string SubjectState
    {
        get;
        set;
    }
}
           

這裡先聲明一個委托:

/// <summary>
/// 定義一個委托,用來通知更新
/// </summary>
delegate void EventHandler();
           

老闆類:

class Boss : Notify
{
    /// <summary>
    /// 聲明一個類型為委托EventHandler的事件,名為Update,
    /// </summary>
    public event EventHandler Update;

    private string action;
    public string SubjectState
    {
        get { return action; }
        set { action = value; }
    }

    public void Notify()
    {
        Update();
    }
}
           

看NBA的同僚:

/// <summary>
/// (看NBA的)觀察者
/// </summary>
class ObserverNBA
{
    private string name;
    private Notify notify;
    public ObserverNBA(string name, Notify notify)
    {
        this.name = name;
        this.notify = notify;
    }

    public void CloseNBAView()
    {
        Console.WriteLine("{0},{1}關閉NBA直播,繼續工作!", notify.SubjectState, name);
    }
}
           

聊天的同僚:

/// <summary>
/// (聊天的)觀察者
/// </summary>
class ObserverChat
{
    private string name;
    private Notify notify;
    public ObserverChat(Notify notify,string name)
    {
        this.name = name;
        this.notify = notify;
    }

    public void StopChat()
    {
        Console.WriteLine("{0},{1}停止聊天,開始工作!",notify.SubjectState,name);
    }

}
           

測試一下:

//通知者
Boss huHanSan = new Boss();

//觀察者
ObserverNBA tsNBA = new ObserverNBA("張三", huHanSan);
ObserverChat tsChat = new ObserverChat(huHanSan, "李四");

//委托事件(更新方法)
huHanSan.Update += new EventHandler(tsNBA.CloseNBAView);
huHanSan.Update += new EventHandler(tsChat.StopChat);

huHanSan.SubjectState = "我胡漢三又回來了";

//通知者發起通知
huHanSan.Notify();
           

測試結果:

我胡漢三又回來了,張三關閉NBA直播,繼續工作!
我胡漢三又回來了,李四停止聊天,開始工作!
           

  通過以上代碼,我想,您已對觀察者模式已經有了更深入的了解。下面我們來看看觀察者的優缺點。

3、觀察者模式優缺點

  • 優點:
    1. 觀察者模式實作了表示層和資料邏輯層的分離,并定義了穩定的更新消息傳遞機制,并抽象了更新接口,使得可以有各種各樣不同的表示層,即觀察者。
    2. 觀察者模式在被觀察者和觀察者之間建立了一個抽象的耦合,被觀察者并不知道任何一個具體的觀察者,隻是儲存着抽象觀察者的清單,每個具體觀察者都符合一個抽象觀察者的接口。
    3. 觀察者模式支援廣播通信。被觀察者會向所有的注冊過的觀察者發出通知。
  • 缺點:
    1. 如果一個被觀察者有很多直接和間接的觀察者時,将所有的觀察者都通知到會花費很多時間。
    2. 雖然觀察者模式可以随時使觀察者知道所觀察的對象發生了變化,但是觀察者模式沒有相應的機制使觀察者知道所觀察的對象是怎樣發生變化的。
    3. 如果在被觀察者之間有循環依賴的話,被觀察者會觸發它們之間進行循環調用,導緻系統崩潰,在使用觀察者模式應特别注意這點。

4、觀察者模式适用場景

在下面的情況下可以考慮使用觀察者模式:

  1. 當一個抽象模型有兩個方面,其中一個方面依賴于另一個方面,将這兩者封裝在獨立的對象中以使它們可以各自獨立地改變和複用的情況下。
  2. 當對一個對象的改變需要同時改變其他對象,而又不知道具體有多少對象有待改變的情況下。
  3. 當一個對象必須通知其他對象,而又不能假定其他對象是誰的情況下。

5、應用舉例(unity)

  在unity應用中,使用C#中的事件委托Delegate來徹底解除通知者和觀察者之間的耦合。

  • 關于委托:

      委托是一種函數指針。一旦為委托配置設定了方法,委托将與該方法有相同的行為。委托方法可以像其它任何方法一樣,具有參數和傳回值。委托可以看作是對函數(方法)的的抽象,是特殊的“函數類”,委托的執行個體代表一個(或多個)具體的函數,它可以是多路廣播的。

  • 關于事件:

      事件基于委托,為委托提供了一種釋出/訂閱機制。事件的訂閱與取消與我們剛才講的觀察者模式中的訂閱與取消類似,隻是表現形式有所不同。在觀察者模式中,訂閱使用方法Attach()來進行;在事件的訂閱中使用“+=”。類似地,取消訂閱在觀察者模式中用Dettach(),而事件的取消用“-=”。

下面是一個小栗子,僅用來抛磚引玉,不足之處還請見諒。

首先準備UI:

C# 設計模式(九)觀察者模式(unity示範)

下面是通知者:

using UnityEngine;
using System.Collections;
using System;
using UnityEngine.UI;

/// <summary>
/// 委托通知的方法類型
/// </summary>
public delegate void ObserverTest();

/// <summary>
/// 這個腳本是事件的派發類,
/// 所有發生的事件都由這個類來派發
/// </summary>
public class Subject : MonoBehaviour
{
    public static event ObserverTest ObserverTestEvent;

    void Start()
    {
        Button btn = transform.Find("Button").GetComponent<Button>();
        btn.onClick.AddListener(onClick);
    }

    public void onClick()
    {
        if (ObserverTestEvent != null)
            ObserverTestEvent();
    }
}
           

接着寫幾個觀察者:

觀察者1:

using UnityEngine;
using System.Collections;
using UnityEngine.UI;

/// <summary>
/// (按鈕)觀察者
/// </summary>
public class ButtonObserver : MonoBehaviour
{
    void Start()
    {
        Subject.ObserverTestEvent += ChangeButton;
    }

    public void ChangeButton()
    {
        Button btn = transform.Find("Button (1)").GetComponent<Button>();
        Image image = btn.GetComponent<Image>();
        Text txt = btn.transform.Find("Text").GetComponent<Text>();

        image.color = Color.red;
        txt.text = "On Change Button Text";
    }

    void OnDestroy()
    {
        Subject.ObserverTestEvent -= ChangeButton;
    }
}
           

觀察者2:

using UnityEngine;
using System.Collections;
using UnityEngine.UI;

/// <summary>
/// (文本)觀察者
/// </summary>
public class TextObserver_1 : MonoBehaviour
{
    // Use this for initialization
    void Start()
    {
        Subject.ObserverTestEvent += ChangeText;
    }

    private void ChangeText()
    {
        Text txt = transform.Find("Text1").GetComponent<Text>();
        txt.text = "hi, Eagle1";
    }

    void OnDestroy()
    {
        Subject.ObserverTestEvent -= ChangeText;
    }
}
           

觀察者3:

using System.Collections;
using UnityEngine.UI;
using UnityEngine;

public class TextObserver_2 : MonoBehaviour
{

    // Use this for initialization
    void Start()
    {
        Subject.ObserverTestEvent += ChangeText;
    }

    private void ChangeText()
    {
        Text txt = transform.Find("Text2").GetComponent<Text>();
        txt.text = "hi, Eagle2";
    }

    void OnDestroy()
    {
        Subject.ObserverTestEvent -= ChangeText;
    }
}
           

如上圖所示把三個觀察者都挂在Canvas上,點選左邊按鈕,效果如下:

C# 設計模式(九)觀察者模式(unity示範)

6、總結

  到這裡,觀察者模式的分享就介紹了。觀察者模式定義了一種一對多的依賴關系,讓多個觀察者對象可以同時監聽某一個主題對象,這個主題對象在發生狀态變化時,會通知所有觀察者對象,使它們能夠自動更新自己,解決的是“當一個對象的改變需要同時改變多個其他對象”的問題。大家可以以微信訂閱号的例子來了解觀察者模式。

7、unity工程下載下傳

  在文章的最後我們給出上述例子的工程下載下傳連結,友善朋友們探讨交流!本次示範版本Unity5.6.3f1(64-bit),VS2015需要下載下傳的朋友,請點選這裡下載下傳。

The End

  好了,今天的分享就到這裡,如有不足之處,還望大家及時指正,随時歡迎探讨交流!!!

喜歡的朋友們,請幫頂、點贊、評論!您的肯定是我寫作的不竭動力!

相關閱讀

C# 23種設計模式(unity示範)

繼續閱讀