DotNet-Advanced-Series-1-7-Event


主要内容概要

一只猫的故事

这里有个例子,一只猫叫了一声,会有一系列的动作,如下:

public void Miao()
{
  Console.WriteLine("{0} Miao", this.GetType().Name);
  new Dog().Wang(); //狗叫了
  new Mouse().Run();//老鼠跑了
  new Baby().Cry(); // 小孩哭了
  new Mother().Wispher();
  new Father().Roar();
  new Neighbor().Awake();
  new Stealer().Hide();
}

看一下上面这个过程,有什么问题?
依赖太重,依赖太多的类型
职责不单,耦合太重,本身猫只应该做自己的事
如果任何一个环节发生改变,这个方法就得修改,导致这方法非常不稳定
其实也不应该这样,猫就是猫,只能Miao一声,剩下的动作,本来不是这个猫的事儿;
但是我们代码给全部限定了,还指定了顺序。

回归需求:只能Miao一声,剩下的动作来自于哪里,或者怎么执行,我们不管,只负责触发,调用。
把一堆动作按顺序执行,应该使用什么?多播委托

现在来改进一下:

public Action CatMaioAction;
public void MiaoDelegate()
{
  Console.WriteLine("{0} Miao", this.GetType().Name);
  CatMaioAction?.Invoke();
}
//这样就可以在委托上添加方法了
CatMaioAction+=new Dog().Wang;
CatMaioAction+=new Mouse().Run;
CatMaioAction+=new Mother().Wispher;

这其实是一种观察者模式,我们可以用面向接口编程的思路来看:

private List<IObject> _Observer = new List<IObject>();
public void AddObserver(IObject obserer)
{
  _Observer.Add(obserer);
}
public void MiaoAbserver()
{
  Console.WriteLine("{0} Miao", this.GetType().Name);//固定动作
  foreach (var item in _Observer)
  {
    item.DoAction();
  }
}

这里的IObject其实就是定义了一个接口,让Dog等类去实现其中的方法DoAction;然后在遍历所有observe中的DoAction方法,本质上和多播委托是一个用法:

public interface IObject
{
  void DoAction();
}

事件

事件Event:就是一个委托的实例 加一个event关键字
事件也实现上面这个猫叫了以后发生的事儿;
event:可以限定权限,限定只能在事件所在类的内部Invoke;在外面是不行,包括子类也不行

public event Action CatMaioActionHandler;
public void MiaoEvent()
{
  Console.WriteLine("{0} MiaoEvent", this.GetType().Name);
  this.CatMaioActionHandler?.Invoke();
}
public class CatChild : Cat
{
  public void Show()
  {
    //base.CatMaioActionHandler.
    //base.CatMaioActionHandler = null; //在子类 也不行
  }
}

事件和委托的区别

委托是一种类型,事件是委托类型的一个实例,加上了event的权限控制。
Student是一种类型,Tony是Student类型的一个实例。

事件的应用

e.g. winform控件无处不在,WPF WebForm服务端控件 请求级事件
事件(观察者模式)能把固定动作和可变动作分开,完成固定动作,把可变动作分离出去,由外部来控制。例如上例中,猫自己做的动作和别的动作。
搭建框架时。恰好就需要这个特点,可以通过实践去分离可变动作,支持扩展。

实例:winform
启动Form—初始化控件Button—Click事件—+=一个动作
点击按钮—鼠标操作—操作系统受到信号—发送给程序—程序接受信号,判断控件—(事件只能类的内部发生)Button类自己调用Click—肯定是出发了Click事件—动作就会执行

对于登陆和支付,2次按钮操作,大部分东西是一样的,就是具体业务不一样的;
封装的空间就完成了固定动作—接受信号和默认动作
可变的部分就是事件—是一个开放的接口,想扩展什么就添加什么。
event限制权限,避免外部乱来。

Event
下面定义一个标准事件:
首先有个Phone的类:

//事件的发布者,发布事件并且在满足条件的情况下,触发事件
public class Phone
{
  public int Id { get; set; }
  public string Name { get; set; }
  private int _price;
  public int Price
  {
    get
    {
      return _price;
    }
    set
    {
      if (value < _price) //降价了
      {
        this.DiscountHandler?.Invoke(this, new XEventArgs()
        {
          OldPrice = _price,
          NewPrice = value
        });
      }
    this._price = value;
    }
  }
  public event EventHandler DiscountHandler;//打折事件
}

这里面有个自定义的事件,自定的参数 EventArgs是个空的,一般会为特定事件封装个参数:

public class XEventArgs : EventArgs
{
  public int OldPrice { get; set; }
  public int NewPrice { get; set; }
}

下面是学生和老师会关注这个事件,如果降价了,就会买,那么买这个方法的参数就要和事件的一致:

//下面是事件的订户 关注事件,事件发生之后,自己做出相应的动作
public class Teacher
{
  public void Buy(object sender, EventArgs e)
  {
    Phone phone = (Phone)sender;
    Console.WriteLine($"this is {phone.Name}");
    XEventArgs xEventArgs = (XEventArgs)e;
    Console.WriteLine($"之前的价格{xEventArgs.OldPrice}");
    Console.WriteLine($"现在的价格{xEventArgs.NewPrice}");
    Console.WriteLine("买下来");
  }
}

public class Student
{
  public void Buy(object sender, EventArgs e)
  {
    Phone phone = (Phone)sender;
    Console.WriteLine($"this is {phone.Name}");
    XEventArgs xEventArgs = (XEventArgs)e;
    Console.WriteLine($"之前的价格{xEventArgs.OldPrice}");
    Console.WriteLine($"现在的价格{xEventArgs.NewPrice}");
    Console.WriteLine("买下来");
  }
}

最后,还需要将订户和事件发布者关联起来:

public static void Show()
{
  Phone phone = new Phone()
  {
    Id = 123,
    Name = "华为P9",
    Price = 2499
  };

  //订阅:就是把订户和事件发布者关联起来
  phone.DiscountHandler += new Teacher().Buy;
  phone.DiscountHandler += new Student().Buy;
  phone.Price = 500;
}

文章作者: Chaoqiang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Chaoqiang !
评论
 上一篇
DotNet-Advanced-Series-1-8-LambdaLinq DotNet-Advanced-Series-1-8-LambdaLinq
主要内容概要1 匿名方法 lambda表达式2 匿名类 var 扩展方法3 linq to object 匿名方法lambda演变历史首先我们定义了如下这么些委托,还有DoNothing和Study方法: public delegate
下一篇 
DotNet-Advanced-Series-1-6-Delegate DotNet-Advanced-Series-1-6-Delegate
主要内容概要 委托的声明、实例化和调用 泛型委托–Func Action 委托的意义:解耦 委托的意义:异步多线程 委托的意义:多播委托 事件 观察者模式 委托委托的声明委托在IL 中就是一个类,继承自父类(特殊类)MulticastDe
  目录