×

『观察者模式』来钓鱼

96
圣杰
2017.01.25 16:41* 字数 1133

目录:设计模式之小试牛刀
源码路径:Github-Design Pattern


一人多竿来钓鱼

定义:(Observer Pattern)

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

类图:

观察者模式通用类图

启示:

最近工作好不繁忙,累的是腰酸脖子疼,这不周末了决定忙里偷闲,去公园散散步,换换心情。
逃离了键盘鼠标,整个世界都清净了。
漫步在公园小径,吹着小风晒着太阳补着钙,好不自在。
走到池塘边,一阵铃铛声吸引了我的注意力。定睛一看,原来是鱼儿咬钩了。只见垂钓者赶紧从旁边的草坪上跑过来收杆拉线,好家伙,钓了个够猫尝个腥塞个牙缝的鱼儿。取下鱼,重新放上鱼饵,把钩一摔,垂钓者就撒手去旁边草坪和朋友打牌去了。和旁边其他一心垂钓的钓客相比,这货明显是更会享受啊,即钓了鱼又晒了太阳打了牌,简直开了多线程啊。

我这人就爱灵光一闪,这不就是观察者模式吗?!

鱼竿,铃铛,垂钓者。
鱼竿是被观察者,
铃铛是通知工具,
垂钓者是观察者。

鱼儿咬钩,鱼竿通过铃铛通知垂钓者收钩。

多么经典的观察者模式生活实例啊。

这灵光一闪,可是要立马记录啊。打开手机滴答清单记下【应用观察者模式实现钓鱼示例】。

回到家,打开电脑,一会噼里啪啦,就完成了以下观察者模式示例。

代码:(简单实现)

先来定义鱼的品类枚举:

public enum FishType
{
    鲫鱼,
    鲤鱼,
    黑鱼,
    青鱼,
    草鱼,
    鲈鱼
}

接下来申明一个钓鱼工具的抽象类,维护订阅者列表,并负责循环通知订阅者。

  /// <summary>
 ///     钓鱼工具抽象类
 ///     用来维护订阅者列表,并通知订阅者
 /// </summary>
 public abstract class FishingTool
 {
     private readonly List<ISubscriber> _subscribers;

     protected FishingTool()
     {
         _subscribers = new List<ISubscriber>();
     }

     public void AddSubscriber(ISubscriber subscriber)
     {
         if (!_subscribers.Contains(subscriber))
             _subscribers.Add(subscriber);
     }

     public void RemoveSubscriber(ISubscriber subscriber)
     {
         if (_subscribers.Contains(subscriber))
             _subscribers.Remove(subscriber);
     }

     public void Notify(FishType type)
     {
         foreach (var subscriber in _subscribers)
             subscriber.Update(type);
     }
 }

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

 /// <summary>
///     鱼竿
/// </summary>
public class FishingRod : FishingTool
{
    public void Fishing()
    {
        Console.WriteLine("开始下钩!");

        //用随机数模拟鱼咬钩,若随机数为偶数,则为鱼咬钩
        if (new Random().Next() % 2 == 0)
        {
            var type = (FishType) new Random().Next(0, 5);
            Console.WriteLine("铃铛:叮叮叮,鱼儿咬钩了");
            Notify(type);
        }
    }
}

定义简单的观察者接口:

/// <summary>
///     订阅者(观察者)接口
///     由具体的订阅者实现Update()方法
/// </summary>
public interface ISubscriber
{
    void Update(FishType type);
}

垂钓者实现观察者接口,并定义了Name,FishCount属性:

/// <summary>
///     垂钓者实现观察者接口
/// </summary>
public class FishingMan : ISubscriber
{
    public FishingMan(string name)
    {
        Name = name;
    }

    public string Name { get; set; }
    public int FishCount { get; set; }

    public void Update(FishType type)
    {
        FishCount++;
        Console.WriteLine("{0}:钓到一条[{2}],已经钓到{1}条鱼了!", Name, FishCount, type);
    }
}

来看看场景类:

 /// <summary>
 ///     测试简单实现的观察者模式
 /// </summary>
 private static void SimpleObserverTest()
 {
     Console.WriteLine("简单实现的观察者模式:");
     Console.WriteLine("=======================");
     //1、初始化鱼竿
     var fishingRod = new FishingRod();

     //2、声明垂钓者
     var jeff = new FishingMan("圣杰");

     //3、将垂钓者观察鱼竿
     fishingRod.AddSubscriber(jeff);

     //4、循环钓鱼
     while (jeff.FishCount < 5)
     {
         fishingRod.Fishing();
         Console.WriteLine("-------------------");
         //睡眠5s
         Thread.Sleep(5000);
     }
 }
简单实现测试结果

以上就是观察者模式的简单实现,通过此例,是不是发现观察者模式也不过如此。

代码:(委托实现)

.Net中有一个好东西,就是委托,对它不了解的可以看看这篇博文C# 中的委托和事件

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

那委托在观察者模式中如何应用呢,让我们来以且听我娓娓道来:

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

 /// <summary>
 ///     鱼竿
 /// </summary>
 public class FishingRod
 {
     public delegate void FishingHandler(FishType type); //声明委托
     public event FishingHandler FishingEvent; //声明事件

     public void Fishing()
     {
         Console.WriteLine("开始下钩!");

         //用随机数模拟鱼咬钩,若随机数为偶数,则为鱼咬钩
         if (new Random().Next() % 2 == 0)
         {
             var a = new Random(10).Next();
             var type = (FishType) new Random().Next(0, 5);
             Console.WriteLine("铃铛:叮叮叮,鱼儿咬钩了");
             if (FishingEvent != null)
                 FishingEvent(type);
         }
     }
 }

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

/// <summary>
///     垂钓者(观察者)
/// </summary>
public class FishingMan
{
    public FishingMan(string name)
    {
        Name = name;
    }

    public string Name { get; set; }
    public int FishCount { get; set; }

    public void Update(FishType type)
    {
        FishCount++;
        Console.WriteLine("{0}:钓到一条[{2}],已经钓到{1}条鱼了!", Name, FishCount, type);
    }
}

看看场景类:

/// <summary>
///     测试委托实现的观察者模式
/// </summary>
private static void DelegateObserverTest()
{
    Console.WriteLine("委托实现的观察者模式:");
    Console.WriteLine("=======================");
    //1、初始化鱼竿
    var fishingRod = new DelegateImplement.FishingRod();

    //2、声明垂钓者
    var jeff = new DelegateImplement.FishingMan("圣杰");

    //3、注册观察者
    fishingRod.FishingEvent += jeff.Update;

    //4、循环钓鱼
    while (jeff.FishCount < 5)
    {
        fishingRod.Fishing();
        Console.WriteLine("-------------------");
        //睡眠5s
        Thread.Sleep(5000);
    }
}
委托实现观察者模式测试结果

总结:

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

优缺点:

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

应用场景:

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