学习自『观察者模式』来钓鱼

定义

定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。

类图

事件之观察者模式 - 图1

总的来说,发布订阅模式中有两个关键字,通知和更新。

  • 被观察者状态改变通知观察者做出相应更新。
  • 解决的是当对象改变时需要通知其他对象做出相应改变的问题。

    简单方式实现

    ```csharp

namespace Observer.Pattern.SimpleDemo;

///

/// 鱼的品类枚举 /// public enum FishType { 鲫鱼, 鲤鱼, 黑鱼, 青鱼, 草鱼, 鲈鱼 }

  1. 一个钓鱼工具的抽象类,维护订阅者列表,并负责循环通知订阅者。
  2. ```csharp
  3. namespace Observer.Pattern.SimpleDemo;
  4. /// <summary>
  5. /// 钓鱼工具抽象类
  6. /// 用来维护订阅者列表,并通知订阅者
  7. /// </summary>
  8. public abstract class FishingTool
  9. {
  10. private readonly List<ISubscriber> _subscribers;
  11. protected FishingTool()
  12. {
  13. _subscribers = new List<ISubscriber>();
  14. }
  15. public void AddSubscriber(ISubscriber subscriber)
  16. {
  17. if (!_subscribers.Contains(subscriber))
  18. _subscribers.Add(subscriber);
  19. }
  20. public void RemoveSubscriber(ISubscriber subscriber)
  21. {
  22. if (_subscribers.Contains(subscriber))
  23. _subscribers.Remove(subscriber);
  24. }
  25. public void Notify(FishType type)
  26. {
  27. foreach (var subscriber in _subscribers)
  28. subscriber.Update(type);
  29. }
  30. }

鱼竿的实现,这里用随机数模拟鱼儿咬钩

  1. namespace Observer.Pattern.SimpleDemo;
  2. /// <summary>
  3. /// 鱼竿
  4. /// </summary>
  5. public class FishingRod : FishingTool
  6. {
  7. /// <summary>
  8. /// 鱼儿咬钩
  9. /// </summary>
  10. public void Fishing()
  11. {
  12. Console.WriteLine("开始下钩!");
  13. //用随机数模拟鱼咬钩,若随机数为偶数,则为鱼咬钩
  14. if (new Random().Next() % 2 == 0)
  15. {
  16. var type = (FishType)new Random().Next(0, 5);
  17. Console.WriteLine("铃铛:叮叮叮,鱼儿咬钩了");
  18. Notify(type);
  19. }
  20. }
  21. }

定义简单的观察者接口

  1. namespace Observer.Pattern.SimpleDemo;
  2. /// <summary>
  3. /// 订阅者(观察者)接口
  4. /// 由具体的订阅者实现Update()方法
  5. /// </summary>
  6. public interface ISubscriber
  7. {
  8. void Update(FishType type);
  9. }

垂钓者实现观察者接口

  1. namespace Observer.Pattern.SimpleDemo;
  2. /// <summary>
  3. /// 垂钓者实现观察者接口
  4. /// </summary>
  5. public class FishingMan : ISubscriber
  6. {
  7. public FishingMan(string name)
  8. {
  9. Name = name;
  10. }
  11. public string Name { get; set; }
  12. public int FishCount { get; set; }
  13. /// <summary>
  14. /// 垂钓者收钩
  15. /// </summary>
  16. /// <param name="type"></param>
  17. public void Update(FishType type)
  18. {
  19. FishCount++;
  20. Console.WriteLine("{0}:钓到一条[{2}],已经钓到{1}条鱼了!", Name, FishCount, type);
  21. }
  22. }

场景类实现

  1. //观察者模式(简单的实现方式)--钓鱼:鱼儿咬钩,鱼竿通过铃铛通知垂钓者收钩。
  2. using Observer.Pattern.SimpleDemo;
  3. SimpleObserverTest();
  4. /// <summary>
  5. /// 测试简单实现的观察者模式
  6. /// </summary>
  7. void SimpleObserverTest()
  8. {
  9. Console.WriteLine("简单实现的观察者模式:");
  10. Console.WriteLine("=======================");
  11. //1、初始化鱼竿
  12. var fishingRod = new FishingRod();
  13. //2、声明垂钓者
  14. var jeff = new FishingMan("圣杰");
  15. //3、将垂钓者观察鱼竿
  16. fishingRod.AddSubscriber(jeff);
  17. //4、循环钓鱼
  18. while (jeff.FishCount < 5)
  19. {
  20. fishingRod.Fishing();
  21. Console.WriteLine("-------------------");
  22. //睡眠5s
  23. Thread.Sleep(5000);
  24. }
  25. }

事件之观察者模式 - 图2

委托方式实现

委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else(Switch)语句,同时使得程序具有更好的可扩展性。

  1. namespace Observer.Pattern.DelegateDemo;
  2. /// <summary>
  3. /// 鱼的品类枚举
  4. /// </summary>
  5. public enum FishType
  6. {
  7. 鲫鱼,
  8. 鲤鱼,
  9. 黑鱼,
  10. 青鱼,
  11. 草鱼,
  12. 鲈鱼
  13. }

有了委托,我们就不再需要定义专门的抽象被观察者对象了,直接实现鱼竿

  1. namespace Observer.Pattern.DelegateDemo;
  2. /// <summary>
  3. /// 鱼竿(被观察者)
  4. /// </summary>
  5. public class FishingRod
  6. {
  7. public delegate void FishingHandler(FishType type); //声明委托
  8. public event FishingHandler FishingEvent; //声明通知事件
  9. public void Fishing()
  10. {
  11. Console.WriteLine("开始下钩!");
  12. //用随机数模拟鱼咬钩,若随机数为偶数,则为鱼咬钩
  13. if (new Random().Next() % 2 == 0)
  14. {
  15. var a = new Random(10).Next();
  16. var type = (FishType)new Random().Next(0, 5);
  17. //鱼儿咬钩,鱼竿通过铃铛通知垂钓者收钩
  18. Console.WriteLine("铃铛:叮叮叮,鱼儿咬钩了");
  19. if (FishingEvent != null)
  20. FishingEvent(type);
  21. }
  22. }
  23. }

因为被观察者定义了委托,我们也没必要定义专门的观察者接口,只需要在具体的观察者中实现对应的委托即可。

  1. namespace Observer.Pattern.DelegateDemo;
  2. /// <summary>
  3. /// 垂钓者(观察者)
  4. /// </summary>
  5. public class FishingMan
  6. {
  7. public FishingMan(string name)
  8. {
  9. Name = name;
  10. }
  11. public string Name { get; set; }
  12. public int FishCount { get; set; }
  13. /// <summary>
  14. /// //鱼儿咬钩,鱼竿通过铃铛通知垂钓者收钩
  15. /// </summary>
  16. /// <param name="type"></param>
  17. public void Update(FishType type)
  18. {
  19. FishCount++;
  20. Console.WriteLine("{0}:钓到一条[{2}],已经钓到{1}条鱼了!", Name, FishCount, type);
  21. }
  22. }

场景类

  1. //观察者模式(委托实现方式)--钓鱼:鱼儿咬钩,鱼竿通过铃铛通知垂钓者收钩。
  2. using Observer.Pattern.DelegateDemo;
  3. DelegateObserverTest();
  4. /// <summary>
  5. /// 测试委托实现的观察者模式
  6. /// </summary>
  7. void DelegateObserverTest()
  8. {
  9. Console.WriteLine("委托实现的观察者模式:");
  10. Console.WriteLine("=======================");
  11. //1、初始化鱼竿
  12. var fishingRod = new FishingRod();
  13. //2、声明垂钓者
  14. var jeff = new FishingMan("圣杰");
  15. //3、注册观察者(绑定通知事件)
  16. fishingRod.FishingEvent += jeff.Update;
  17. //4、循环钓鱼
  18. while (jeff.FishCount < 5)
  19. {
  20. fishingRod.Fishing();
  21. Console.WriteLine("-------------------");
  22. //睡眠5s
  23. Thread.Sleep(5000);
  24. }
  25. }

总结

观察者模式中有两个关键字,通知和更新。
被观察者状态改变通知观察者做出相应更新。
解决的是当对象改变时需要通知其他对象做出相应改变的问题。

优缺点

优点
观察者和被观察者之间是抽象耦合,易于扩展;
可构造一套触发机制,形成广播链,支持广播通信;
缺点
若一个对象存在较多的观察者,则通知观察者存在效率问题;
观察者和被观察者间可能会导致循环依赖,导致系统奔溃。

应用场景

  • 关联行为场景。需要注意的是,关联行为是可拆分的,而不是“组合”关系。
  • 事件多级触发场景。
  • 跨系统的消息交换场景,如消息队列的处理机制。