本地控制反转






4.75/5 (20投票s)
有点像控制反转,但没有 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 一样。 它使代码更易读、更易于维护,也更易于重构。 ▪