我在一些示例中看到了作为参数传递的方法。如果我可以从另一种方法调用一个方法,为什么我应该将方法作为参数传递?这种设计背后的目的是什么?
- 从另一个方法调用一个方法
- 使用委托或传递方法作为参数
Action
将方法作为参数传递可用于防止依赖和耦合。让我们看一下如何将其用于策略模式:
假设我们有一个方法PrintReport
,它根据参数打印给定的项目列表,这些项目可能按名称或类型排序。这是天真的方法:
public void PrintReport (List<Item> data, SortOrder sortBy)
{
List<Item> sortedItems;
switch (sortBy)
{
case SortOrder.Name: sortedItems = SortByName(data); break;
case SortOrder.Type: sortedItems = SortByType(data); break;
}
Print(sortedItems);
}
这很简单,但很有效。但是当我们想要添加一个新的排序顺序时会发生什么呢?我们需要更新 SortOrder 枚举,进入PrintReport
并添加一个新的case
并调用新的SortByWhatever
方法。
但是如果我们传入一个方法作为参数,我们PrintReport
可以更简单,不用关心排序实现:
public void PrintReport (List<Item> data, Func<List<Item>, List<Item>> sorter)
{
List<Item> sortedItems = sorter(data);
Print(sortedItems);
}
现在无论如何都可以定义排序函数,甚至可能在一个PrintReport
甚至不知道的不同程序集中。它可以是 lambda 函数或临时定义的匿名方法。但在所有情况下,我们的方法都会接收委托,使用它进行排序,然后打印报告。
这是一个使用示例。起初看起来我们只是将 switch/case 移到函数之外,这很重要,因为它允许不同的调用者有不同的逻辑。但请注意第三种情况。
public void HandleData()
{
switch (ReportItemOrder)
{
case SortOrder.Name: PrintReport(data, SortByName); break;
case SortOrder.Type: PrintReport(data, SortByType); break;
case SortOrder.Whatever:
Func<List<Item>, List<Item>> customSort = (items) => /* do something */;
PrintReport(data, customSort);
}
}
委托通常用于将类和接口彼此分离。
这是一个具体的例子。假设您有一个负责绘制日历的 UI 类,但您不希望它确切知道如何将 DateTime 值格式化为字符串。
您可以像这样定义类:
public sealed class MyCalendarDrawer
{
private readonly Func<DateTime, string> _dateFormatter;
public MyCalendarDrawer(Func<DateTime, string> dateFormatter)
{
_dateFormatter = dateFormatter;
}
public void Draw()
{
// Do some work that involves displaying dates...
DateTime date = DateTime.Now;
string dateString = _dateFormatter(date);
// Display dateString somehow.
}
}
这样,MyCalendarDrawer
不需要知道如何格式化日期 - 通过传递一个Func<DateTime, string>
可以调用的委托来告诉它如何做。
事实是,函数传递给其他函数的每个示例都可以用实现传递给函数的给定接口的对象来表示。
换句话说,委托比接口更好没有明显的原因。Java 中即将推出的 lambda 就是一个示例,您实际上并不需要能够将函数传递给另一个函数来获得简洁的语法。
换句话说,将一个函数传递给另一个函数的能力只是程序员工具包中的一个工具,就像将对象传递给函数一样。虽然这是有争议的,但可以使用一种完全不支持将函数传递给函数的语言 - Java - 并且仍然能够具有相同的表现力。
将函数视为一等类型有其优势。它为您提供了函数式编程的可能性。
以“事件处理”的经典案例为例,您肯定会将函数指针发送到另一个函数作为事件发生时的回调。
同样,这是另一个假设的例子
private void CallMeBack(out int type, Func<int> action)
{
type = action();
}
现在我可以为此提供任何功能,例如CallMeBack(a, ()=>return 1);
和CallMeBack(a, ()=>return 2);
您应该阅读有关代表的内容。例如,委托对于在给定方法完成上定义动态回调很有用。
伪代码示例:
doSomething(); //your code
updateInterface(continueDoingSomething); //a generic method, passing a delegate
...
doAnythingElse();
updateInterface(continueDoingAnythingElse);
在此示例中,您可以定义一个通用方法“updateInterface”,它作为回调调用作为委托传入的动态方法。
如果不使用委托,则必须实现两种(或更多)不同的方法:
void updateInterfaceAndContinueDoingSomething(){}
void updateInterfaceAndContinueDoingAnythingElse(){}