.NET 委托 AutoCooker
.NET 委托 之 AutoCooker
YY一下,有一种自动化厨具AutoCooker,根据放入的原料,烹饪出一桌美味大餐。
它可以根据当前状态自动发出通知,不用时时关注它的运行状态。
当然再先进也需要有人配料,由老妈负责。儿子呢,在外面Happy 也希望接收到“饭已好,回来米西”的短信通知。
下面是AutoCooker的实现。
一 紧耦合
{
private Person _person;
public AutoCooker(Person ps)
{
_person = ps;
}
public void DoCook()
{
Console.WriteLine("Start to Cook");
if (_person != null) _person.CookStarted();
Console.WriteLine("Cooking");
if (_person != null) _person.CookProgressing();
Console.WriteLine("Finish to Cook");
if (_person != null)
{
int grade = _person.CookCompleted();
Console.WriteLine("My appetite is {0} %",grade);
}
}
}
通知发给谁?就是俗称的 “监听人员”,mother和Son
{
public virtual void CookStarted() { }
public virtual void CookProgressing() { }
public virtual int CookCompleted() { return 0; }
}
public class Mother:Person
{
public override void CookStarted() { Console.WriteLine("Mother: Ah,I must go to check the ingredient again"); }
public override void CookProgressing() { Console.WriteLine("Mother: I know,continute to play cards"); }
public override int CookCompleted() { Console.WriteLine("Mother: I will go home to prepare dinner"); return 80; }
}
可以看出,AutoCooker和通知列表Person是紧耦合的关系,而且不符合设计原则中的单一职责原则。
客户端:
{
static void Main(string[] args)
{
Mother mother = new Mother();
AutoCooker easyCooker = new AutoCooker(mother);
easyCooker.DoCook();
Console.ReadLine();
}
}
执行结果:
二 接口
儿子也想收到AutoCooker发出的短信通知,如何实现呢?答案:接口,而且接口可以将通知列表和方法分离开。
{
int CookCompleted();
void CookProgressing();
void CookStarted();
}
public class AutoCooker
{
private ICookerEvents _events;
public AutoCooker(ICookerEvents ps)
{
_events = ps;
}
public void DoCook()
{
Console.WriteLine("Cooker:Start to Cook");
if (_events != null) _events.CookStarted();
Console.WriteLine("Cooker:Cooking");
if (_events != null) _events.CookProgressing();
Console.WriteLine("Cooker:Finish to Cook");
if (_events != null)
{
int grade = _events.CookCompleted();
Console.WriteLine("My appetite is {0} %",grade);
}
}
}
public abstract class Person
{
}
public class Mother:Person, ICookerEvents
{
public void CookStarted() { Console.WriteLine("Mother: Ah,I must go to check the ingredient again"); }
public void CookProgressing() { Console.WriteLine("Mother: I know,continute to play cards"); }
public int CookCompleted() { Console.WriteLine("Mother: I will go home to prepare dinner"); return 80; }
}
public class Son: Person,ICookerEvents
{
public void CookStarted() { Console.WriteLine("Son: Ah,what's for dinner?"); }
public void CookProgressing() { Console.WriteLine("Son: Wow,It smells good!"); }
public int CookCompleted()
{
Console.WriteLine("Son:I'm hungry, wait supper for me!");
return 90;
}
}
class Program
{
static void Main(string[] args)
{
Mother mother = new Mother();
AutoCooker easyCooker = new AutoCooker(mother);
easyCooker.DoCook();
Son son = new Son();
AutoCooker easyCooker2 = new AutoCooker(son);
easyCooker2.DoCook();
Console.ReadLine();
}
}
相比紧耦合,接口的方式更加合理了一些。
三、委托
上面的实现怎么看,怎么别扭,要两个AutoCooker,才能通知mother和son,不合逻辑,能不能让AutoCooker同时通知所有的监控者呢?
还有,就是接口用作事件的时候,粒度不够好,比如Son其实不怎么关心CookStarted和CookProgressing的,但是作为接口也必须全部实现。
将上面接口的方法分离为单独的委托,每个委托都像一个小的接口方法:
public delegate void CookProgressing();
public delegate int CookCompleted();
public class AutoCooker
{
public CookStarted started;
public CookProgressing progressing;
public CookCompleted completed;
public void DoCook()
{
Console.WriteLine("Cooker:Start to Cook");
if (started != null) started();
Console.WriteLine("Cooker:Cooking");
if (progressing != null) progressing();
Console.WriteLine("Cooker:Finish to Cook");
if (completed != null)
{
int grade = completed();
Console.WriteLine("My appetite is {0} %",grade);
}
}
}
public abstract class Person
{
}
public class Mother:Person
{
public void CookStarted() { Console.WriteLine("Mother: Ah,I must go to check the ingredient again"); }
public void CookProgressing() { Console.WriteLine("Mother: I know,continute to play cards"); }
public int CookCompleted() { Console.WriteLine("Mother: I will go home to prepare dinner"); return 80; }
}
public class Son: Person
{
public int CookCompleted()
{
Console.WriteLine("Son:I'm hungry, wait supper for me!");
return 90;
}
}
class Program
{
static void Main(string[] args)
{
AutoCooker easyCooker = new AutoCooker();
Mother mother = new Mother();
Son son = new Son();
easyCooker.started += new CookStarted(mother.CookStarted);
easyCooker.completed = new CookCompleted(son.CookCompleted);
easyCooker.DoCook();
Console.ReadLine();
}
}
执行结果:
四 静态监听者
采用实例方法的委托,比较占用资源,可以用静态方法改进。
{
public static int CookCompleted()
{
Console.WriteLine("Son:I'm hungry, wait supper for me!");
return 90;
}
}
class Program
{
static void Main(string[] args)
{
AutoCooker easyCooker = new AutoCooker();
easyCooker.started += new CookStarted(Mother.CookStarted);
easyCooker.completed = new CookCompleted(Son.CookCompleted);
easyCooker.DoCook();
Console.ReadLine();
}
}
五 事件
如果这样会发生什么情况:
easyCooker.started += new CookStarted(Mother.CookStarted);
easyCooker.completed += new CookCompleted(Mother.CookCompleted);
easyCooker.completed = new CookCompleted(Son.CookCompleted); // 会替换掉Mother.CookCompleted
easyCooker.DoCook();
easyCooker.completed();
由此可以看出,客户端可以随意添加委托,激发事件。 新添加的委托会替换掉原来的委托链。
改进措施:event
enent关键字在委托的外边包装了一个 property,仅让 C#客户通过 += 和 -=操作符来添加和移除,不能直接激发事件。
public delegate void CookProgressing();
public delegate int CookCompleted();
public class AutoCooker
{
public event CookStarted started;
public event CookProgressing progressing;
public event CookCompleted completed;
public void DoCook()
{
Console.WriteLine("Cooker:Start to Cook");
if (started != null) started();
Console.WriteLine("Cooker:Cooking");
if (progressing != null) progressing();
Console.WriteLine("Cooker:Finish to Cook");
if (completed != null)
{
int grade = completed();
Console.WriteLine("My appetite is {0} %",grade);
}
}
}
六 收到所有反馈
注意到,我们现在只收到了Son的My appetite is 90%, 并没有Mother的;
这就是委托函数为什么一般为void的原因,因为委托链调用结束,只返回最后一个调用结果。
深入到代理里面,轮询监听者列表,手工一个个调用:
if (completed != null)
{
foreach (CookCompleted item in completed.GetInvocationList())
{
int grade = item();
Console.WriteLine("My appetite is {0} %", grade);
}
}
执行结果:
七 异步通知:激发 & 忘掉
到这里,我们几乎得到我们想要的效果了。
更加苛刻的情况是这样的,Mother和Son有自己的事情要做,也就是说,委托调用的时间比较长,AutoCooker要什么也不能做,在那里等待么?
{
public static int CookCompleted()
{
System.Threading.Thread.Sleep(3000);
Console.WriteLine("Son:I'm hungry, wait supper for me!");
return 90;
}
}
我们来做个实验:
假设Son的CookCompleted动作,sleep 3秒,Mother的CookCompleted动作Sleep8秒;DoWork()的CookCompleted动作Sleep1秒。
a. 同步委托调用:
{
![](https://www.cnblogs.com/Images/dot.gif)
if (completed != null)
{
System.Threading.Thread.Sleep(1000);
int grade;
grade = completed();
Console.WriteLine("My appetite is {0} %", grade);
}
}
static void Main(string[] args)
{
AutoCooker easyCooker = new AutoCooker();
easyCooker.started += new CookStarted(Mother.CookStarted);
easyCooker.completed += new CookCompleted(Son.CookCompleted);
easyCooker.completed += new CookCompleted(Mother.CookCompleted);
DateTime preTime = DateTime.Now;
easyCooker.DoCook();
DateTime afterTime = DateTime.Now;
TimeSpan issueTime = afterTime - preTime;
Console.WriteLine("Time interval is: {0}", issueTime);
}
执行结果:
b. 如果是:同步委托轮询
{
System.Threading.Thread.Sleep(1000);
int grade;
foreach (CookCompleted item in completed.GetInvocationList())
{
grade = item();
Console.WriteLine("My appetite is {0} %", grade);
}
}
执行结果:因为有循环和GetInvocationList()的调用,所以用时大于12秒:
上面都是同步委托调用,主线程被block,当委托调用完成,才回到主线程,输出Time interva is:
c. 如果用异步委托调用:
{
System.Threading.Thread.Sleep(1000);
int grade;
foreach (CookCompleted item in completed.GetInvocationList())
{
item.BeginInvoke(null, null);
}
}
执行结果:
可以看出,主线程和委托调用分别进行(进程的线程池在调用这些委托),所以Time interval is才能第一个输出。
好处就是,AutoCooker通知了Son和Mother后,可以立即返回工作,例如在DoWork()后调用AutoCooker的其他方法。
缺陷是得不到反馈,改进:
d. 异步委托轮询,
异步激发事件,但是周期性地轮询,
{
System.Threading.Thread.Sleep(1000);
int grade;
foreach (CookCompleted item in completed.GetInvocationList())
{
IAsyncResult res = item.BeginInvoke(null, null);
while (!res.IsCompleted)
System.Threading.Thread.Sleep(1);
grade = item.EndInvoke(res);
}
}
执行结果:
反馈是得到的,但是执行起来和同步调用的效果一样。异步轮询 = 同步轮询?
e. 异步委托轮询
能不能既异步委托调用,又做到轮询调用结果呢?答案是可以,用异步委托轮询。
{
System.Threading.Thread.Sleep(1000);
foreach (CookCompleted item in completed.GetInvocationList())
{
item.BeginInvoke(new AsyncCallback(PersonAppetite), item);
}
}
private void PersonAppetite(IAsyncResult res)
{
CookCompleted cc = (CookCompleted)res.AsyncState;
int grade = cc.EndInvoke(res);
Console.WriteLine("My appetite is {0} %", grade);
}
执行结果:
皆大欢喜,既实现了异步调用,又能得到每个委托调用的返回结果。
AutoCooker通知Son和Mother,不用管多久从目的方法中返回,通知后立马做AutoCooker的其他事情;又可以异步的得到反馈结果。
出处:http://www.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。