主要内容概要
一只猫的故事
这里有个例子,一只猫叫了一声,会有一系列的动作,如下:
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限制权限,避免外部乱来。
下面定义一个标准事件:
首先有个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;
}