3.观察者模式

3.行为型模式-观察者模式


3.1 基础知识

学习难度:3

使用频率:5

总分:8

定义

观察者模式(Observer Pattern)定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

说人话

通知者维护一系列观察者,向它们发送通知。

结构图

实现步骤

  • 常规实现
    • 观察者接口:定义更新方法
    • 具体观察者类:实现观察者接口,定义需要的成员,实现更新方法
    • 通知者接口:定义添加观察者、移除观察者、通知观察者方法
    • 具体通知者类:实现通知者接口,定义观察者接口列表,实现添加观察者、移除观察者、通知观察者方法。通知观察者方法中遍历观察者接口列表调用更新方法。当某些状态改变时通知所有观察者。
    • 客户端:实例化具体通知者,实例化多个具体观察者添加到通知者中。改变通知者的某些状态来通知所有观察者。
  • 委托实现
    • 不需要观察者接口
    • 具体观察者类:定义需要的成员和方法
    • 通知者接口:定义通知观察者方法
    • 具体通知者类:实现通知者接口,定义通知委托,实现通知观察者方法中调用通知委托。
    • 客户端:实例化具体通知者,实例化一些具体观察者。把观察者要被观察的方法添加到通知者的通知委托中。某些状态下调用通知委托。

说明

观察者模式更常用的是委托实现。


3.2 模版代码

观察者接口和具体观察者类

// 观察者接口
public interface IObserver
{
    void Update(string message);
}

// 具体观察者类
public class ConcreteObserver : IObserver
{
    private string name;

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

    public void Update(string message)
    {
        Console.WriteLine($"观察者 {name} 收到消息:{message}");
    }
}

通知者接口和具体通知者

// 通知者接口
public interface INotifier
{
    void RegisterObserver(IObserver observer);
    void RemoveObserver(IObserver observer);
    void NotifyObservers();
}

// 具体通知者类
public class ConcreteNotifier : INotifier
{
    private List<IObserver> observers = new List<IObserver>();
    private string state;

    public void RegisterObserver(IObserver observer)
    {
        observers.Add(observer);
    }

    public void RemoveObserver(IObserver observer)
    {
        observers.Remove(observer);
    }

    public void NotifyObservers()
    {
        foreach (var observer in observers)
        {
            observer.Update(state);
        }
    }

    public void SetState(string state)
    {
        this.state = state;
        NotifyObservers();
    }
}

客户端

class Program
{
    static void Main(string[] args)
    {
        ConcreteNotifier notifier = new ConcreteNotifier();

        IObserver observer1 = new ConcreteObserver("观察者1");
        IObserver observer2 = new ConcreteObserver("观察者2");

        notifier.RegisterObserver(observer1);
        notifier.RegisterObserver(observer2);

        notifier.SetState("新状态1");
        // 观察者 观察者1 收到消息:新状态1
        // 观察者 观察者2 收到消息:新状态1

        notifier.SetState("新状态2");
        // 观察者 观察者1 收到消息:新状态2
        // 观察者 观察者2 收到消息:新状态2

        notifier.RemoveObserver(observer1);

        notifier.SetState("新状态3");
        // 观察者 观察者2 收到消息:新状态3
    }
}

3.3 CSharp实践

实践需求

使用观察者模式,模拟足球比赛发生的事件,更新球迷的反应。

球迷接口和球迷类

// 观察者接口 - 足球球迷
public interface IFootballFan
{
    void Update(string status);
}

// 具体观察者类 - 足球球迷
public class FootballFan : IFootballFan
{
    public string Name { get; }

    public FootballFan(string name)
    {
        Name = name;
    }

    public void Update(string status)
    {
        Console.WriteLine($"{Name} 收到比赛状态更新:{status}");
    }
}

比赛接口和比赛类

// 主题接口 - 足球比赛
public interface IFootballMatch
{
    void RegisterObserver(IFootballFan observer);
    void RemoveObserver(IFootballFan observer);
    void NotifyObservers();
    void UpdateMatchStatus(string status);
}


// 具体主题类 - 具体足球比赛
public class FootballMatch : IFootballMatch
{
    private List<IFootballFan> fans = new List<IFootballFan>();
    private string matchStatus;

    public void RegisterObserver(IFootballFan fan)
    {
        fans.Add(fan);
    }

    public void RemoveObserver(IFootballFan fan)
    {
        fans.Remove(fan);
    }

    public void NotifyObservers()
    {
        foreach (var fan in fans)
        {
            fan.Update(matchStatus);
        }
    }

    public void UpdateMatchStatus(string status)
    {
        matchStatus = status;
        NotifyObservers();
        
        if (status.Contains("C罗") && status.Contains("进球"))
        {
            foreach (var fan in fans)
            {
                if (fan is FootballFan && ((FootballFan)fan).Name == "甲亢哥")
                {
                    fan.Update("甲亢哥庆祝C罗进球");
                }
                else if (fan is FootballFan && ((FootballFan)fan).Name == "李老八")
                {
                    fan.Update("李老八庆祝C罗进球");
                }
            }
        }
        else if (status.Contains("梅西") && status.Contains("进球"))
        {
            foreach (var fan in fans)
            {
                if (fan is FootballFan && ((FootballFan)fan).Name == "真实球迷会")
                {
                    fan.Update("真实球迷会庆祝梅西进球");
                }
            }
        }
        else if (status.Contains("梅西") && status.Contains("杀死比赛"))
        {
            foreach (var fan in fans)
            {
                if (fan is FootballFan && ((FootballFan)fan).Name == "真实球迷会")
                {
                    fan.Update("真实球迷会庆祝梅西进球");
                }
                else if (fan is FootballFan && ((FootballFan)fan).Name == "甲亢哥")
                {
                    fan.Update("甲亢哥说不装了,我是梅西球迷,庆祝梅西进球");
                }
            }
        }
    }
}

客户端

class Program
{
    static void Main(string[] args)
    {
        IFootballMatch match = new FootballMatch();

        IFootballFan fan1 = new FootballFan("李老八");
        IFootballFan fan2 = new FootballFan("甲亢哥");
        IFootballFan fan3 = new FootballFan("真实球迷会");

        match.RegisterObserver(fan1);
        match.RegisterObserver(fan2);
        match.RegisterObserver(fan3);

        match.UpdateMatchStatus("比赛开始");

        // C罗进球
        match.UpdateMatchStatus("C罗进球,比分1:0");

        // 梅西进球
        match.UpdateMatchStatus("梅西进球,比分1:1");

        // C罗梅开二度
        match.UpdateMatchStatus("C罗再进球,比分2:1");

        // 梅西梅开二度
        match.UpdateMatchStatus("梅西再进球,比分2:2");
        
        // 梅西帽子戏法
        match.UpdateMatchStatus("梅西杀死比赛,比分3:2");

        // 比赛结束
        match.UpdateMatchStatus("比赛结束,C罗梅开二度,梅西帽子戏法,最终比分3:2");


        // 李老八 收到比赛状态更新:比赛开始
        // 甲亢哥 收到比赛状态更新:比赛开始
        // 真实球迷会 收到比赛状态更新:比赛开始

        // 李老八 收到比赛状态更新:C罗进球,比分1:0
        // 甲亢哥 收到比赛状态更新:C罗进球,比分1:0
        // 真实球迷会 收到比赛状态更新:C罗进球,比分1:0

        // 李老八 收到比赛状态更新:李老八庆祝C罗进球
        // 甲亢哥 收到比赛状态更新:甲亢哥庆祝C罗进球
        
        // 李老八 收到比赛状态更新:梅西进球,比分1:1
        // 甲亢哥 收到比赛状态更新:梅西进球,比分1:1
        // 真实球迷会 收到比赛状态更新:梅西进球,比分1:1

        // 真实球迷会 收到比赛状态更新:真实球迷会庆祝梅西进球

        // 李老八 收到比赛状态更新:C罗再进球,比分2:1
        // 甲亢哥 收到比赛状态更新:C罗再进球,比分2:1
        // 真实球迷会 收到比赛状态更新:C罗再进球,比分2:1

        // 李老八 收到比赛状态更新:李老八庆祝C罗进球
        // 甲亢哥 收到比赛状态更新:甲亢哥庆祝C罗进球

        // 李老八 收到比赛状态更新:梅西再进球,比分2:2
        // 甲亢哥 收到比赛状态更新:梅西再进球,比分2:2
        // 真实球迷会 收到比赛状态更新:梅西再进球,比分2:2

        // 真实球迷会 收到比赛状态更新:真实球迷会庆祝梅西进球

        // 李老八 收到比赛状态更新:梅西杀死比赛,比分3:2
        // 甲亢哥 收到比赛状态更新:梅西杀死比赛,比分3:2
        // 真实球迷会 收到比赛状态更新:梅西杀死比赛,比分3:2

        // 甲亢哥 收到比赛状态更新:甲亢哥说不装了,我是梅西球迷,庆祝梅西进球
        // 真实球迷会 收到比赛状态更新:真实球迷会庆祝梅西进球

        // 李老八 收到比赛状态更新:比赛结束,C罗梅开二度,梅西帽子戏法,最终比分3:2
        // 甲亢哥 收到比赛状态更新:比赛结束,C罗梅开二度,梅西帽子戏法,最终比分3:2
        // 真实球迷会 收到比赛状态更新:比赛结束,C罗梅开二度,梅西帽子戏法,最终比分3:2

    }
}

3.4 Unity实践

实践需求

使用委托实现观察者模式,模拟按下空格更新薇恩的生命值和血条,当生命值小于等于0时薇恩死亡。伤害类型包括物理和魔法。

具体观察者类

public class HealthBarObserver : MonoBehaviour
{
    private float width;  // 血条的初始宽度

    private GameObject healthValue;  // 用于显示血量的对象

    public VayneObserver vayneObserver;  // 引用薇恩的观察者对象

    private void Start()
    {
        // 获取血条的初始宽度
        width = this.gameObject.GetComponent<RectTransform>().rect.width;

        // 获取用于显示血量的对象
        healthValue = transform.GetChild(0).gameObject;
    }

    // 更新血条的宽度,根据薇恩的生命值
    public void UpdateHealthBar(float damage)
    {
        // 计算当前宽度,根据薇恩的生命值和最大生命值来计算
        float nowWidth = (vayneObserver.Health / VayneObserver.HEALTH_MAX) * width;

        // 更新血量显示对象的宽度
        healthValue.GetComponent<RectTransform>().sizeDelta =
            new Vector2(nowWidth, healthValue.GetComponent<RectTransform>().sizeDelta.y);

        // 输出更新后的血条宽度信息
        Debug.Log($"更新血条 当前宽度是{nowWidth} 最大宽度是{width}");
    }
}

public class VayneObserver : MonoBehaviour
{
    // 常量,表示最大生命值
    public const float HEALTH_MAX = 100;

    // 当前生命值
    public float health = HEALTH_MAX;

    // 用于显示生命值的 HealthBarObserver
    public HealthBarObserver healthBarObserver;

    // 属性,用于获取和设置生命值
    public float Health
    {
        get => health;
        private set => health = value;
    }

    // 函数用于改变薇恩的生命值
    public void ChangeVayneHealth(float damage)
    {
        // 减少生命值
        Health -= damage;

        // 限制生命值在0到100之间
        Health = Mathf.Clamp(Health, 0, HEALTH_MAX);

        if (Health != 0)
        {
            // 输出受到伤害后的生命值信息
            Debug.Log($"薇恩受到了{damage}点伤害,当前血量是{Health}");
        }
        else
        {
            // 输出薇恩死亡信息并销毁游戏对象
            Debug.Log("薇恩死亡");
            Destroy(this.gameObject);
        }
    }
}

生命值通知接口和生命值通知者

// 定义了一个名为 IHealthNotifier 的接口
public interface IHealthNotifier
{
    // 声明一个方法,用于通知观察者健康变化
    void NotifyObserverHealthChange(params object[] arr);
}

// 枚举类型,表示伤害类型
public enum DamageType
{
    Physical,
    Magic
}

public class HealthNotifier : IHealthNotifier
{
    // 声明一个 Action 委托来处理物理伤害变化 其实这句没必要声明 只是和自定义委托做对比
    public Action<float> physicalHealthChangedHandler;

    // 声明一个事件,用于通知物理伤害变化
    public event Action<float> OnPhysicalHealthChanged;

    // 声明一个自定义委托来处理魔法伤害变化
    public delegate void MagicHealthChangedHandler(float damage);

    // 声明一个事件,用于通知魔法伤害变化
    public event MagicHealthChangedHandler OnMagicHealthChanged;
    
    // 实现 IHealthNotifier 接口中的通知方法
    public void NotifyObserverHealthChange(params object[] arr)
    {
        // 如果参数为空或参数数量不足2,不执行通知
        if (arr == null || arr.Length < 2) return;
        
        float damage = (float)arr[0];
        DamageType damageType = (DamageType)arr[1];
        
        // 输出伤害类型信息
        Debug.Log($"通知者知道了伤害类型是{damageType}");
        
        // 根据伤害类型触发相应的事件
        if (damageType == DamageType.Physical)
        {
            OnPhysicalHealthChanged?.Invoke(damage);
        }
        else if (damageType == DamageType.Magic)
        {
            OnMagicHealthChanged?.Invoke(damage);
        }
    }
}

其他准备工作

客户端

public class TestObserverPattern : MonoBehaviour
{
    // 可在Unity编辑器中设置的薇恩预制体
    public GameObject vayneGameObject;

    // 健康通知者对象
    private HealthNotifier healthNotifier;

    // 薇恩观察者对象
    private VayneObserver vayneObserver;

    // 健康条观察者对象
    private HealthBarObserver healthBarObserver;

    private void Start()
    {
        // 从预制体创建薇恩对象
        GameObject vayne = Instantiate(vayneGameObject, Vector3.zero, Quaternion.identity);

        // 创建健康通知者对象
        healthNotifier = new HealthNotifier();

        // 获取薇恩观察者和健康条观察者
        vayneObserver = vayne.GetComponent<VayneObserver>();
        healthBarObserver = vayneObserver.healthBarObserver;

        // 绑定通知者的事件到薇恩和健康条的处理方法
        healthNotifier.OnPhysicalHealthChanged += vayneObserver.ChangeVayneHealth;
        healthNotifier.OnMagicHealthChanged += vayneObserver.ChangeVayneHealth;
        healthNotifier.OnPhysicalHealthChanged += healthBarObserver.UpdateHealthBar;
        healthNotifier.OnMagicHealthChanged += healthBarObserver.UpdateHealthBar;
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            if (vayneObserver == null || healthBarObserver == null)
            {
                // 如果薇恩或健康条已被销毁,输出消息并返回
                Debug.Log($"薇恩已被销毁");
                return;
            }

            // 随机生成伤害和伤害类型
            float damage = Random.Range(0f, 20f);
            DamageType damageType = (DamageType)Random.Range(0, 2);

            // 输出随机伤害和伤害类型信息
            Debug.Log($"随机出来的伤害是{damage},伤害类型是{damageType}");

            // 通知观察者关于伤害的变化
            healthNotifier.NotifyObserverHealthChange(damage, damageType);
        }
    }
}

运行结果



转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏