stand on the shoulders of giants

.NET 委托 AutoCooker

.NET 委托 之 AutoCooker

YY一下,有一种自动化厨具AutoCooker,根据放入的原料,烹饪出一桌美味大餐。
它可以根据当前状态自动发出通知,不用时时关注它的运行状态。
当然再先进也需要有人配料,由老妈负责。儿子呢,在外面Happy 也希望接收到“饭已好,回来米西”的短信通知。

下面是AutoCooker的实现。

一  紧耦合 

public class 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 class Person
    {
        
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是紧耦合的关系,而且不符合设计原则中的单一职责原则。

客户端:

class Program
    {
        
static void Main(string[] args)
        {
            Mother mother = new Mother();
            AutoCooker easyCooker = new AutoCooker(mother);
            easyCooker.DoCook();

            Console.ReadLine();
        }
    }

 执行结果:

二 接口

儿子也想收到AutoCooker发出的短信通知,如何实现呢?答案:接口,而且接口可以将通知列表和方法分离开。 

public interface ICookerEvents
    {
        
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 CookStarted();
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 class Son: Person
{
        
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();
        }
    }

 

五 事件

如果这样会发生什么情况:

AutoCooker easyCooker = new AutoCooker();         

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 CookStarted();
    
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的原因,因为委托链调用结束,只返回最后一个调用结果
深入到代理里面,轮询监听者列表,手工一个个调用: 

 Console.WriteLine("Cooker:Finish to Cook");
            
if (completed != null)
            {
                
foreach (CookCompleted item in completed.GetInvocationList())
                {
                    
int grade = item();
                    Console.WriteLine(
"My appetite is {0} %", grade);
                }
            }


执行结果:

 

七 异步通知:激发 & 忘掉  

到这里,我们几乎得到我们想要的效果了。
更加苛刻的情况是这样的,Mother和Son有自己的事情要做,也就是说,委托调用的时间比较长,AutoCooker要什么也不能做,在那里等待么? 

public class Son: Person
    {
        
public static int CookCompleted()
        {
            System.Threading.Thread.Sleep(3
000);
            Console.WriteLine(
"Son:I'm hungry, wait supper for me!");
            
return 90;
        }
    }

 

我们来做个实验:
假设Son的CookCompleted动作,sleep 3秒,Mother的CookCompleted动作Sleep8秒;DoWork()的CookCompleted动作Sleep1秒。

a. 同步委托调用:

public void DoCook()
{  
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. 如果是:同步委托轮询

if (completed != null)
            {
                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. 如果用异步委托调用:

if (completed != null)
            {
                System.Threading.Thread.Sleep(
1000);
                
int grade;
                
foreach (CookCompleted item in completed.GetInvocationList())
                {
                    item.BeginInvoke(
nullnull);
                 
}                
            }

执行结果:

可以看出,主线程和委托调用分别进行(进程的线程池在调用这些委托),所以Time interval is才能第一个输出。
好处就是,AutoCooker通知了Son和Mother后,可以立即返回工作,例如在DoWork()后调用AutoCooker的其他方法。

缺陷是得不到反馈,改进:
d. 异步委托轮询,
异步激发事件,但是周期性地轮询, 

            if (completed != null)
            {
                System.Threading.Thread.Sleep(
1000);
                
int grade;
                
foreach (CookCompleted item in completed.GetInvocationList())
                {
                    IAsyncResult res 
= item.BeginInvoke(nullnull);                    
                    
while (!res.IsCompleted)
                        System.Threading.Thread.Sleep(
1);
                    grade 
= item.EndInvoke(res);
                }                
            }

执行结果:

反馈是得到的,但是执行起来和同步调用的效果一样。异步轮询 = 同步轮询?

e. 异步委托轮询

能不能既异步委托调用,又做到轮询调用结果呢?答案是可以,用异步委托轮询。

if (completed != null)
            {
                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的其他事情;又可以异步的得到反馈结果。

posted @ 2009-05-19 01:38  DylanWind  阅读(275)  评论(0编辑  收藏  举报