C# 設計模式——觀察者模式

觀察者模式簡介

定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態時,它的所有依賴者都會收到通知並自動更新。
在這裏插入圖片描述
何時使用:
一個對象(目標對象)的狀態發生改變,所有的依賴對象(觀察者對象)都將得到通知,進行廣播通知。

優點 缺點
1、觀察者和被觀察者是抽象耦合的。 1、如果一個被觀察者對象有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。
2、建立一套觸發機制。 2、如果在觀察者和觀察目標之間有循環依賴的話,觀察目標會觸發它們之間進行循環調用,可能導致系統崩潰。
/ 3、觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎麼發生變化的,而僅僅只是知道觀察目標發生了變化。

事件的應用

事件模型的五大組成部分

  1. 事件的擁有着(event source,對象);
  2. 事件成員(event,成員)
  3. 事件的響應者(event subscriber,對象);
  4. 事件處理器(event handler,成員)——一個回調方法;
  5. 事件訂閱——把事件處理器與事件關聯在一起,本質上是一種以委託類型爲基礎的“約定”。

最爲常用的事件模型
在這裏插入圖片描述
說明:事件擁有者是事件響應者的一個字段成員,事件響應者用自己的方法訂閱者自己的成員的某個事件。

上述模型C# Demo

using System;
using System.Windows.Forms;
/// <summary>
/// 實現功能,在一個窗口裏有一個文本框和按鈕,點擊按鈕
/// 文本框則顯示“Hello World”字符串
/// </summary>
namespace EventExample
{
    class Program
    {
        static void Main(string[] args)
        {
            MyForm form = new MyForm();
            form.ShowDialog();
        }
    }

    class MyForm : Form
    {
        private TextBox textBox;
        private Button button;
        public MyForm()
        {
            this.textBox = new TextBox(); // 事件響應者textBox
            this.button = new Button();   // 事件擁有者button  事件:Click
            // 讓這兩個控件顯示在窗口裏
            this.Controls.Add(this.textBox);
            this.Controls.Add(this.button);
            this.button.Click += this.ButtonClicked;//事件訂閱
            // 設置button控鍵
            this.button.Text = "Say Hello";
            this.button.Top = 50;
        }

        /// <summary>
        /// 事件處理器是ButtonClicked方法
        /// </summary>
        private void ButtonClicked(object sender, EventArgs e)
        {
            this.textBox.Text = "Hello World";
        }
    }
}

觀察者模式C# Unity_Demo

目標:定義事件的觀察者,實現觀察者模式。
unity處設置不再贅述

C#代碼部分
BaseUnit.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public enum DamageType
{
    Normal,
    Critical
}

public enum HpShowType
{
    Null,
    Damage,
    Miss
}

public class BaseUnit : MonoBehaviour
{
     
    public delegate void SubHpHandler(BaseUnit source, float subHp, DamageType damageType, HpShowType showType);
    public event SubHpHandler OnSubHp;

    protected virtual void OnBeAttacked(float harmNumber, bool isCritical, bool isMiss)
    {
        DamageType damageType = DamageType.Normal;
        HpShowType showType = HpShowType.Damage;
        if (isCritical)
            damageType = DamageType.Critical;
        if (isMiss)
            showType = HpShowType.Miss;
        // 首先判斷是否有方法訂閱了該事件,如果有則通知它們
        if (OnSubHp != null)
            OnSubHp(this, harmNumber, damageType, showType);

    }

    public bool IsHero
    {
        get { return true; }
    }

    public void BeAttacked()
    {
        float possibility = UnityEngine.Random.value;
        bool isCritical = UnityEngine.Random.value > 0.5f;
        bool isMiss = UnityEngine.Random.value > 0.5f;
        float harmNumber = 10000f;
        OnBeAttacked(harmNumber, isCritical, isMiss);
    }
}

BattleInformationComponent.cs

using System;
using UnityEngine;

public class BattleInformationComponent : MonoBehaviour
{
    public BaseUnit unit;

    private void Start()
    {
        this.unit = gameObject.GetComponent<BaseUnit>();
        this.AddListener();

    }

    // 訂閱BaseUnit定義的事件OnSubHp
    private void AddListener()
    {
        this.unit.OnSubHp += this.OnSubHp;
    }

    // 取消對BaseUnit定義的事件OnSubHp的訂閱
    private void RemoveListener()
    {
        // 註銷關注
        this.unit.OnSubHp -= this.OnSubHp;
    }

    // 當BaseUnit 被攻擊時,會調用該回調事件
    private void OnSubHp( BaseUnit source, float subHp, DamageType damageType, HpShowType showType)
    {
        string unitName = string.Empty;
        string missStr = "閃避";
        string damageTypeStr = string.Empty;
        string damageHp = string.Empty;
        if(showType == HpShowType.Miss)
        {
            Debug.Log(missStr);
            return;
        }
        if (source.IsHero)
        {
            unitName = "英雄";
        }
        else
        {
            unitName = "士兵";
        }

        damageTypeStr = damageType == DamageType.Critical ? "暴擊" : "普通攻擊";
        damageHp = subHp.ToString();
        Debug.Log(unitName + damageTypeStr + damageHp);
    }
}

Controller.cs

using System;
using UnityEngine;


public class Controller : MonoBehaviour
{
    public BaseUnit unit;

    private void OnGUI()
    {
        if( GUI.Button(new Rect(10, 10, 150, 100), "攻擊測試" ))
        {
            this.unit.BeAttacked();
        }
    }
}


測試結果:
在這裏插入圖片描述
說明:

  1. 定義委託類型,確定回調方法原型;
    C#的事件模塊是以委託作爲基礎的
public delegate void SubHpHandler(BaseUnit source, float subHp, DamageType damageType, HpShowType showType);
  1. 定義事件的成員
 public event SubHpHandler OnSubHp;

使用event關鍵字來定義一個事件成員,每個事件成員需要指定以下3項內容:

  • 可訪問型標識符,爲了能讓其他的類型對象能夠訂閱該事件,因此事件的成員的可訪問性標識符是public;
  • 基礎委託類型,以及委託類型確定的回調方法的基本形式
  • 事件的名稱
  1. 定義觸發事件的方法
 private void OnSubHp( BaseUnit source, float subHp, DamageType damageType, HpShowType showType)
 {
           //DoSomething
 }
  1. 通知訂閱事件的對象
if (OnSubHp != null)
   OnSubHp(this, harmNumber, damageType, showType);

大家能從上例中找出事件的五大組成部分嗎,另外觀察者模式也通過C#的事件模型實現啦。

參考資料

https://www.bilibili.com/video/av1422127?p=21

書籍:Unity3D腳本編程 陳嘉棟著

更多:

23種設計模式C#

發佈了27 篇原創文章 · 獲贊 25 · 訪問量 3302
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章