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脚本编程 陈嘉栋著

发布了18 篇原创文章 · 获赞 17 · 访问量 2698

猜你喜欢

转载自blog.csdn.net/chasinghope/article/details/104178408