C#中利用delegate减少代码重复

摘要:利用delegate,可以在一个类的内部提取公共方法,减少代码重复。有时候,也会让代码变得更酷。

一、缘起,以及例子

最近参与一个项目的开发工作,项目的开发者中,有的经验丰富,有的经验比较丰富,也有新手。因此在开发的成果中,有不少质量可以提高的地方。在这里零星记下来,希望对项目有帮助。——这些博客的预期读者,主要是本项目的成员。

代码重复是最令人难以忍受的编码坏习惯之一。有很多手段可以减少代码重复,例如,提取公共方法,提取公共父类或共享的helper类等。在C#中,delegate也是一种可以考虑的手段。

看这样一个例子。我们要访问外部接口,获取系统所需的数据。为了提高效率,将从外部接口获取的数据缓存起来。我们先不考虑用memcached一类的技术,而仅仅是将其存放在自己的字典中。

一开始我们只获取数据A:

 

public class ServiceDataCacher
{
    private Dictionary<string, object> cachedData = new Dictionary<string, object>();
 
    public A GetA()
    {
        string key = "A";
        if (!cachedData.ContainsKey(key))
        {
            A a = SeriviceClient.GetA();
            cachedData.Add(key, a);
        }
 
        return (A)cachedData[key];
    }
}

 

后来又需要获取数据B,这一次是根据参数获取:

 
public class ServiceDataCacher
{
    private Dictionary<string, object> cachedData = new Dictionary<string, object>();
 
    public A GetA()
    {
        ...
    }

 
    public B GetB(long id)
    {
        string key = "B-" + id;
        if (!cachedData.ContainsKey(key))
        {
            B b = SeriviceClient.GetB(id);
            cachedData.Add(key, b);
        }
 
        return (B)cachedData[key];
    }

}

 

事情看起来有点不妙了,因为出现了重复的代码:先判断是否已经缓存,如果没有缓存,就从外部服务中获取内容,将其缓存起来。

再后来,又要读取数据C,这一次是根据两个参数:

 
public class ServiceDataCacher
{
    private Dictionary<string, object> cachedData = new Dictionary<string, object>();
 
    public A GetA()
    {
        ...
    }
 
    public B GetB(long id)
    {
        ...
    }

 
    public C GetC(string type, string name)
    {
        string key = "C-" + type + "-" + name;
        if (!cachedData.ContainsKey(key))
        {
            C c = ServiceClient.GetC(type, name);
            cachedData.Add(key, c);
        }
 
        return (C)cachedData[key];
    }
}

事情到了这个地步,就必须进行重构了,将“判断是否已经缓存-如果没有,就读取并缓存起来”这样的逻辑提取出来。实际上,在我们的项目中,这样的函数有几十甚至上百个。但怎样提取?提取公共方法有点难度,因为每个函数的框架是类似的,只是要调用的服务不同,而且参数也可能不同。另一种思路是提取公共父类,但为每一个方法定义一个子类的成本太高了:要定义上百个子类,谁受得了啊?

二、利用delegate

解决的思路是利用delegate。先考虑基本的做法:将公共的框架提取出来,而将服务调用定义为delegate。由于每个方法的参数不同,需要在delegate中使用动态参数。

    private delegate object Caller(params object[] args);

 提取公共方法,调用这个delegate来获取数据:

    private object GetData(string key, Caller caller, params object[] args)
    {
        if (!cachedData.ContainsKey(key))
        {
            object a = caller(args);
            cachedData.Add(key, a);
        }

        return cachedData[key];
    }

 

这样就把“判断是否已经缓存-如果没有,就读取并缓存起来”这个逻辑提取出来了,每个方法定义好自己的delegate,调用这个方法即可。

 

重构后的类定义如下:

class ServiceDataCacher
{
    private Dictionary<string, object> cachedData = new Dictionary<string, object>();
 
    private delegate object Caller(params object[] args);
 
    private object GetData(string key, Caller caller, params object[] args)
    {
        if (!cachedData.ContainsKey(key))
        {
            object a = caller(args);
            cachedData.Add(key, a);
        }
 
        return cachedData[key];
    }
 
    public A GetA()
    {
        string key = "A";
        return (A) GetData(key, ServiceGetA);
    }
 
    private object ServiceGetA(object[] args)
    {
        return ServiceClient.GetA();
    }
 
    public B GetB(long id)
    {
        string key = "B-" + id;
        return (B) GetData(key, ServiceGetB, id);
    }
 
    private object ServiceGetB(object[] args)
    {
        return ServiceClient.GetB((long)args[0]);
    }
 
    public C GetC(string type, string name)
    {
        string key = "C-" + type + "-" + name;
        return (C) GetData(type, ServiceGetC, name);
    }
 
    private object ServiceGetC(object[] args)
    {
        return ServiceClient.GetC((string)args[0], (string)args[1]);
    }
}

三、利用匿名方法

 经过上面的重构,相同的逻辑被提取出来并复用,减少了代码重复。但每一个方法在定义delegate时,都要搭配一个独立的方法。这样显得不够简练:原来的一个方法被分成了两个。另外,使用动态参数,也让人很闹心。

为此,考虑使用匿名方法来实现delegate。顾名思义,匿名方法只需要给出方法的内容,而不需要定义带有名字的、独立的具体方法。以GetC为例,使用匿名方法后,就变成了:

    public C GetC(string type, string name)
    {
        string key = "C-" + type + "-" + name;
        return (C) GetData(type, delegate
            {
                return ServiceClient.GetC(type, name);
            });
    }

 

其中delegate引导的部分就是一个匿名方法,我们定义了方法的内容后,就直接使用了。匿名方法有一个优势:能够感知当前上下文,可以使用其中的变量。这样,就不需要再把参数传递给匿名方法了。原来delegate定义中的参数也就不需要了。

delegate定义修改为:

    private delegate object Caller();

 公共方法修改为:

    private object GetData(string key, Caller caller)
    {
        if (!cachedData.ContainsKey(key))
        {
            object a = caller();
            cachedData.Add(key, a);
        }

        return cachedData[key];
    }

 

经过这样的重构,类的定义变成了下面的样子:

class ServiceDataCacher
{
    private Dictionary<string, object> cachedData = new Dictionary<string, object>();
 
    private delegate object Caller();
 
    private object GetData(string key, Caller caller)
    {
        if (!cachedData.ContainsKey(key))
        {
            object a = caller();
            cachedData.Add(key, a);
        }
 
        return cachedData[key];
    }
 
    public A GetA()
    {
        string key = "A";
        return (A) GetData(key, delegate
            {
                return ServiceClient.GetA();
            });
    }
 
    public B GetB(long id)
    {
        string key = "B-" + id;
        return (B) GetData(key, delegate
            {
                return ServiceClient.GetB(id);
            });
    }
 
    public C GetC(string type, string name)
    {
        string key = "C-" + type + "-" + name;
        return (C) GetData(key, delegate
            {
                return ServiceClient.GetC(type, name);
            });
    }
}

 

简单总结一下:缓存逻辑的集中,为调整带来了很大便利。例如,如果要考虑并发问题,需要对cachedData加锁,只需要修改一处即可。如果要考虑缓存的持久化或共享,例如使用memcached管理缓存,也只需要改造一处。

 

----------我是分隔线:主要内容介绍到此结束,以下的内容纯属耍酷 ---------- 

四、利用lambda表达式

C#中,可以用lambda运算符表示delegate,这样可以使代码显得更酷。

例如,GetC可以变成这个样子:

     public C GetC(string type, string name)
    {
        string key = "C-" + type + "-" + name;
        return (C) GetData(type, () => 
            {
                return ServiceClient.GetC(type, name);
            });
    }	

 更进一步,如果匿名方法中仅有一个return语句,这意味着整个方法体实际上就是一个表达式,可以用lambda表达式来表示这个方法。

例如,GetC可以变成:

    public C GetC(string type, string name)
    {
        string key = "C-" + type + "-" + name;
        return (C) GetData(key, () => ServiceClient.GetC(type, name); );
    }	 

再进一步,如果lambda表达式中的方法调用没有参数,则可以用方法直接表示,省去lambda表达式。

例如,利用lambda表达式,GetA可以改造为:

    public A GetA()
    {
        string key = "A";
        return (A) GetData(key, () => ServiceClient.GetA());
    }

利用方法,更可以进一步改造为:

    public A GetA()
    {
        string key = "A";
        return (A) GetData(key, ServiceClient.GetA);
    }

整个类定义变为:

class ServiceDataCacher
{
    private Dictionary<string, object> cachedData = new Dictionary<string, object>();
 
    private delegate object Caller();
 
    private object GetData(string key, Caller caller)
    {
        if (!cachedData.ContainsKey(key))
        {
            object a = caller();
            cachedData.Add(key, a);
        }
 
        return cachedData[key];
    }
 
    public A GetA()
    {
        string key = "A";
        return (A) GetData(key, ServiceClient.GetA);
    }
 
    public B GetB(long id)
    {
        string key = "B-" + id;
        return (B) GetData(key, () => ServiceClient.GetB(id));
    }
 
    public C GetC(string type, string name)
    {
        string key = "C-" + type + "-" + name;
        return (C) GetData(key, () => ServiceClient.GetC(type, name));
    }
}

很酷,不是么?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值