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