65.9K
CodeProject 正在变化。 阅读更多。
Home

本地控制反转

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (20投票s)

2013年2月20日

CPOL

3分钟阅读

viewsIcon

25786

有点像控制反转,但没有 DI 容器

术语“控制反转”通常与 DI/IoC 框架相关。然而,同样的想法也适用于微观层面。我个人经常遇到我可以称之为局部控制反转 (LIoC) 的情况,这就是这篇短文的全部内容。

示例

让我们从最简单的例子开始。假设您想向集合中添加一个项目:这通常写成

myCollection.Add(x)

如果您暂停片刻并思考上面的语句,并用英语表达它,您将得到myCollection 应该将项目 x 添加到自身,这是非常难以理解的。 这正是 IoC 有用的情况,这是方法:让我们定义一个 AddTo() 扩展方法

public static T AddTo(this T self, ICollection collection)
{
  collection.Add(self);
  return self;
}

现在,不要在集合上调用 Add() 方法,而是控制反转,因此在集合上调用 AddTo() 方法

var aList = new List();
2.AddTo(aList);

翻译成英语,上面现在读作2 应该添加到 aList。 更有意义,不是吗? 此外,作为额外的好处,AddTo() 操作是流畅的,因为它返回原始参数。 这意味着一个值可以一次添加到多个列表中,例如

2.AddTo(someList).AddTo(someOtherList);

让我们尝试一些其他的东西。 假设你想检查一个集合是否为空。 通常,你会写

if (myClass.Fields.Count == 0) { ... }
// or
if (!myClass.Fields.Any()) { ... }

我想这两种方法都可以,但代码读起来很奇怪。 第一个例子说如果 myClass 的字段计数等于零,而第二个例子读作如果 myClass 的字段不是任何数字,这甚至更糟糕。

所以这里有一对扩展方法

public static bool HasSome(this TSubject subject, 
  Func> propertyToCheck)
{
  return propertyToCheck(subject).Any();
}
public static bool HasNo(this TSubject subject, 
  Func> propertyToCheck)
{
  return !HasSome(subject, propertyToCheck);
}

现在,检查字段的存在变成

if (myClass.HasNo(c => c.Fields)) {}

读作myClass 没有字段,这正是我们在这里检查的。

好的,再举一个例子。 假设你正在检查一个字符串值,并且想将它与一组值进行比较。 这通常表示为

if (myOp == "AND" || myOp == "OR" || myOp == "XOR") { ... }

这以一种还可以,但零碎的方式阅读。 一个更好的方法是将变体分组到一个数组中,但是在 C# 中,这看起来很丑陋

if (new[]{"AND", "OR", "XOR"}.Contains(myOp)) { ... }

这甚至更缺乏语义。 如果有一个 IsOneOf() 函数呢?

public static bool IsOneOf(this T self, params T[] variants)
{
  return variants.Contains(self);
}

现在,上面的检查将变成

if (myOp.IsOneOf("AND", "OR", "XOR")) { ... }

上述方法的好处在于,参数被声明为 params T[],因此我们不必在使用它们作为测试之前用 new[] 初始化任何数组。

摘要

因此,这是 LIoC 的基本前提:给定一个函数 f(x,y...),从语义和可用性的角度来看,在某些情况下,在 x 上定义一个扩展方法来使用参数 y 应用函数 f 可能更有益。

以下事实提供了额外的好处

  • 扩展方法可以接受泛型参数,从而让您在类似对象组上定义反转操作。

  • params 关键字让您的 API 以“可变参数”的方式运行,从而使您摆脱显式集合初始化。

  • 可以通过传入 lambdas 或表达式来获得额外的表达能力。

这种方法的缺点是

  • 必须管理包含扩展方法的单独类。

  • API 污染——任何时候你在泛型类型上定义一个扩展方法,所有对象都会获得一个额外的 IntelliSense 弹出成员。

  • 可能的性能开销——例如,在您使用 lambda 而不是直接成员访问的情况下。

我到处都在使用 LIoC,就像我使用 Maybe monad 一样。 它使代码更易读、更易于维护,也更易于重构。 ▪

© . All rights reserved.