Code Copied

重载方法选择

从重载方法说起

在coding一些框架的时候,一个类的公开方法里面通常有很多重载方法,写重载方法通常的方式是:参数数量和参数类型重载
重载方法带来的好处不言而喻,它能够给调用者多种选择,让调用者在不用场合都能调用它,总归是“总有一种适合你”。
当然在框架中写出恰当的重载方法,是仍然需要一些功力的,作为框架的编写者,对重载方法应该有较深的理解:

  1. 考虑很多的调用场景
    也许你提供的框架供很多开发者使用,但这些开发者针对不同的场景编码,要满足这些“众口”,你得尽可能的考虑到这些场景。
  2. 考虑调用的可行性
    很多场景你确实考虑到了,对于调用者来说你提供的方法由于他的调用条件不满足,可能导致无法调用,这种方法我称之为”僵尸方法“,即”有的方法虽然活着,但是它已经死了“,因为无人可调用。
  3. 考虑调用的舒适性
    至此,你已经完成了很多重载方法,调用者也都能够调用了。
    现在你以为你做得”菜“大家都能”吃”了,但你应该去了解一下他们对“菜”的评价。
    也许有的开发者会抱怨:这道”菜“确实能吃,但是太难吃了,为了能吃上你这道”菜“,我绕了很多弯路,真是经历千辛万苦。
    这种比喻可能有点夸张,但确实有这种情况发生:开发者为了调用你的方法,仍然得绕很多弯路,有些得不偿失。

    我们应该始终保持1条戒规:让开发者能够简便调用,不要让开发者为难。

如何能够考虑做到这3点呢?
答案是你多年来的项目经历和经验,并且以开发者和框架编写者的2种角色去切实体会。
这里没有一个公式和模范可以套用,代码是千变万化的,优秀的coder的编码风格、性格也都是不一样的,导致每个人的想法是不一样的。

那些优秀的coder可以根据相同的功能要求编写出风格迥异的框架出来。

下面我将以4简单的重载方法来讨论这些话题。

场景说明

假设类库里面有一个叫做GenericTypedHelper的类,它提供了一个方法SetAttributes()。
这个方法的作用是根据名称创建1个Entity对象,然后给这个Entity对象的各个属性赋值。
(这段代码,你完全可以忽略Entity在这里的作用,本文中的Entity对象来源于Dynamics CRM)

public void SetAttributes(string entityName, IDictionary<string, object> attributes)
{
    var entity = new Entity(entityName);
    foreach (var attr in attributes)
    {
        entity[attr.Key] = attr.Value;
    }
}

当其他开发者调用这个方法时,可能会这样写:

IDictionary<string,object> attributes = new Dictionary<string, object>();
attributes.Add("name","Peng Sunny");
attributes.Add("emailaddress1","sunnyweb2.0@hotmail.com");
...
...
SetAttributes("account", attributes);

从逻辑和代码风格上看,逻辑完全没有问题,代码也还算简洁。
但它限定了调用者只能在一个场景调用:固定传入2个参数string entityName, IDictionary<string, object> attributes。
也许我们应该考虑到更多的调用场景:

  • 传入一个空的Entity对象和属性键值对调用
  • 传入一个entityName和一个object对象调用(object对象存储了属性的键值对)。

场景实现

现在我们逐一实现这些场景

Hide Code | Copy Code

/// <summary>
/// 重载方法1
/// </summary>
public void SetAttributes(string entityName, IDictionary<string, object> attributes)
{
    var entity = new Entity(entityName);
    foreach (var attr in attributes)
    {
        entity[attr.Key] = attr.Value;
    }
}

/// <summary>
/// 重载方法2
/// </summary>
public void SetArrtibutes(string entityName, object attributes)
{
    SetAttributes(entityName, attributes.ToDictionary());
}

/// <summary>
/// 重载方法3
/// </summary>
public void SetAttributes(Entity entity, IDictionary<string, object> attributes)
{
    foreach (var attr in attributes)
    {
        entity[attr.Key] = attr.Value;
    }
}

/// <summary>
/// 重载方法4
/// </summary>
public void SetAttributes(Entity entity, object attributes)
{
    SetAttributes(entity, attributes.ToDictionary());
}

下面的代码展示了不同重载方法的调用,我们可以将其视作不同场景的调用。

// 调用重载方法1
IDictionary<string, object> attributes1 = new Dictionary<string, object>();
attributes1.Add("name", "Peng Sunny");
attributes1.Add("emailaddress1", "sunnyweb2.0@hotmail.com");
SetAttributes("account", attributes1);

// 调用重载方法2
var attributes2 = new { name = "Peng Sunny", emailaddress1 = "sunnyweb2.0@hotmail.com" };
SetAttributes("account", attributes2);

// 调用重载方法3
var entity3 = new Entity("account");
IDictionary<string, object> attributes3 = new Dictionary<string, object>();
attributes3.Add("name", "Peng Sunny");
attributes3.Add("emailaddress1", "sunnyweb2.0@hotmail.com");
SetAttributes(entity3, attributes3);

// 调用重载方法4
var entity4 = new Entity("account");
var attributes4 = new { name = "Peng Sunny", emailaddress1 = "sunnyweb2.0@hotmail.com" };
SetAttributes(entity4, attributes4);

精简重载方法

现在大家可以看到,这4个重载方法的是通过参数个数和类型的排列组合产生的,其中1和2是一组关联重载方法,3和4是一组关联重载方法。
但我并不推荐大家这样做,理由是相同参数个数的重载方法不宜过多,这会让调用者难以选择,产生“选择恐惧症”。
甚至同一个调用者在不同的地方调用这些方法时,由于可选择的调用方式太多,可能导致相同的场景调用了不同的方法。

对上面这个例子进行精简,只该只保留重载方法1和2,祛除3和4。

/// <summary>
/// 重载方法1
/// </summary>
public void SetAttributes(string entityName, IDictionary<string, object> attributes)
{
    var entity = new Entity(entityName);
    foreach (var attr in attributes)
    {
        entity[attr.Key] = attr.Value;
    }
}

/// <summary>
/// 重载方法2
/// </summary>
public void SetAttributes(string entityName, object attributes)
{
    SetAttributes(entityName, attributes.ToDictionary());
}

上面用到的ToDictionary()方法是object对象的一个扩展方法,用于将Object对象的所有属性转换为IDictionary<string, object>。

public static class ObjectHelper
{
    public static IDictionary<string, object> ToDictionary(this object o)
    {
        return o.GetType().GetProperties().ToDictionary(info => info.Name,info =>(info.GetValue(o, null)));
    }
}

总结

前面以1个简单的例子说明了重载方法该如何去写,这些内容可以总结为如下3点:

  1. 重载方法的参数应当尽量选择简单类型,并且易于构造。
  2. 重载方法依赖于实际场景,但相同参数数量的重载方法不宜过多。
  3. 重载方法之间可以互相调用。