我希望在 VB.NET 或 C# 或其他一流的 .NET 语言中实现观察者模式。我听说委托可以用于此目的,但无法弄清楚为什么它们比在观察者上实现的普通旧接口更受欢迎。所以,
- 为什么我应该使用委托而不是定义我自己的接口并传递对实现它们的对象的引用?
- 为什么我要避免使用委托,而使用良好的老式接口?
我希望在 VB.NET 或 C# 或其他一流的 .NET 语言中实现观察者模式。我听说委托可以用于此目的,但无法弄清楚为什么它们比在观察者上实现的普通旧接口更受欢迎。所以,
当您可以直接调用方法时,您不需要委托。
当调用方法的代码不知道/不关心它正在调用的方法是什么时,委托很有用——例如,您可能会调用一个长时间运行的任务并将其传递给该任务可以使用的回调方法发送有关其状态的通知。
这是一个(非常愚蠢的)代码示例:
enum TaskStatus
{
Started,
StillProcessing,
Finished
}
delegate void CallbackDelegate(Task t, TaskStatus status);
class Task
{
public void Start(CallbackDelegate callback)
{
callback(this, TaskStatus.Started);
// calculate PI to 1 billion digits
for (...)
{
callback(this, TaskStatus.StillProcessing);
}
callback(this, TaskStatus.Finished);
}
}
class Program
{
static void Main(string[] args)
{
Task t = new Task();
t.Start(new CallbackDelegate(MyCallbackMethod));
}
static void MyCallbackMethod(Task t, TaskStatus status)
{
Console.WriteLine("The task status is {0}", status);
}
}
如您所见,Task
该类不知道也不关心——在这种情况下——委托是一个将任务状态打印到控制台的方法。该方法同样可以通过网络连接将状态发送到另一台计算机。等等。
你是操作系统,我是应用程序。我想告诉您,当您检测到发生某些事情时,调用我的一种方法。为此,我将一个委托传递给我希望你调用的我的方法。我自己不会调用我的那个方法,因为我希望你在检测到某些东西时调用它。您不直接调用我的方法,因为您不知道(在编译时)该方法存在(我什至在构建时都没有编写);相反,您调用您在运行时收到的委托指定的任何方法。
从技术上讲,您不必使用委托(除非使用事件处理程序,否则它是必需的)。没有他们你也能过得去。实际上,它们只是工具箱中的另一个工具。
使用它们首先想到的是Inversion Of Control。任何时候您想从外部控制函数的行为方式,最简单的方法是将委托作为参数放置,并让它执行委托。
你不像程序员那样思考。
问题是,当您可以调用委托时,为什么要直接调用函数?
David Wheeler 的一句著名格言是:计算机科学中的所有问题都可以通过另一个层次的间接性来解决。
我有点半开玩笑。显然,大多数时候你会直接调用函数,尤其是在模块中。但是,当需要在包含对象不可用(或相关)的上下文中调用函数时,委托很有用,例如事件回调。
在观察者模式中有两个地方可以使用委托。由于我不确定您指的是哪一个,因此我将尝试回答两者。
第一种是在主题中使用委托而不是 IObserver 列表。这种方法在处理多播方面似乎更干净,因为你基本上有
private delegate void UpdateHandler(string message);
private UpdateHandler Update;
public void Register(IObserver observer)
{
Update+=observer.Update;
}
public void Unregister(IObserver observer)
{
Update-=observer.Update;
}
public void Notify(string message)
{
Update(message);
}
代替
public Subject()
{
observers = new List<IObserver>();
}
public void Register(IObserver observer)
{
observers.Add(observer);
}
public void Unregister(IObserver observer)
{
observers.Remove(observer);
}
public void Notify(string message)
{
// call update method for every observer
foreach (IObserver observer in observers)
{
observer.Update(message);
}
}
除非您需要做一些特别的事情并需要对整个 IObserver 对象的引用,否则我认为代表会更干净。
第二种情况是例如使用传递委托而不是 IObervers
public delegate void UpdateHandler(string message);
private UpdateHandler Update;
public void Register(UpdateHandler observerRoutine)
{
Update+=observerRoutine;
}
public void Unregister(UpdateHandler observerRoutine)
{
Update-=observerRoutine;
}
public void Notify(string message)
{
Update(message);
}
有了这个,观察者不需要实现接口。你甚至可以传入一个 lambda 表达式。这种控制水平的变化几乎是不同的。这是好是坏取决于你。
我在重复我对这个问题的回答。
我一直喜欢广播电台的比喻。
当一个广播电台想要广播一些东西时,它只是将它发送出去。它不需要知道是否真的有人在听。您的收音机能够向广播电台注册(通过拨盘调谐),所有广播电台的广播(我们的小比喻中的事件)都被收音机接收,并将它们翻译成声音。
没有这种注册(或事件)机制。广播电台必须轮流联系每一个电台,询问它是否想要广播,如果你的电台说是,然后直接向它发送信号。
您的代码可能遵循非常相似的范例,其中一个类执行一项操作,但该类可能不知道,或者可能不想知道谁会关心或对发生的该操作采取行动。因此,它为任何对象提供了一种注册或注销自身的方法,以通知该操作已发生。
委托是函数/方法接口的强类型。
如果您的语言认为应该有强类型,并且它具有一流的功能(C# 都有),那么没有委托将是不一致的。
考虑任何接受委托的方法。如果你没有委托,你将如何传递一些东西给它?被调用者如何对其类型有任何保证?
实际上,委托是传递对方法的引用,而不是对象……接口是对对象实现的方法子集的引用……
如果在您的应用程序的某些组件中,您需要访问一个对象的多个方法,则定义一个表示该对象方法子集的接口,并在您可能需要传递给它的所有类上分配和实现该接口组件...然后通过该接口而不是通过它们的具体类传递这些类的实例。
如果,otoh,在某些方法或组件中,您只需要几个方法之一,这些方法可以在任意数量的不同类中,但都具有相同的签名,那么您需要使用委托。
我听过一些“事件布道者”谈论这个,他们说越多的解耦事件越好。
最好,事件源永远不应该知道事件侦听器,而事件侦听器永远不应该关心谁发起了事件。这不是今天的情况,因为在事件侦听器中,您通常会收到事件的源对象。
话虽如此,代表是这项工作的完美工具。它们允许事件源和事件观察者之间的解耦,因为事件源不需要保留所有观察者对象的列表。它只保留观察者的“函数指针”(委托)列表。正因为如此,我认为这比接口有很大的优势。
换个角度看。与使用该语言在语法和库中都支持的标准方式相比,使用自定义接口有什么优势?
当然,在某些情况下,定制的解决方案可能具有优势,在这种情况下,您应该使用它。在所有其他情况下,请使用可用的最规范的解决方案。它的工作量更少,更直观(因为这是用户所期望的),得到了更多工具(包括 IDE)的支持,而且很有可能,编译器会以不同的方式处理它们,从而产生更高效的代码。
不要重新发明轮子(除非当前版本已损坏)。
实际上,Sun 和 Microsoft 之间在代表问题上发生了有趣的争论。虽然 Sun 对代表采取了相当强硬的立场,但我认为 Microsoft 在使用代表方面提出了更强有力的观点。以下是帖子:
http://java.sun.com/docs/white/delegates.html
http://msdn.microsoft.com/en-us/vjsharp/bb188664.aspx
我想你会发现这些有趣的阅读...
这是我可以写下的使用委托的原因。以下代码是用 C# 编写的,请关注评论。
public delegate string TestDelegate();
protected void Page_Load(object sender, EventArgs e)
{
TestDelegate TD1 = new TestDelegate(DiaplayMethodD1);
TestDelegate TD2 = new TestDelegate(DiaplayMethodD2);
TD2 = TD1 + TD2; // Make TD2 as multi-cast delegate
lblDisplay.Text = TD1(); // invoke delegate
lblAnotherDisplay.Text = TD2();
// Note: Using a delegate allows the programmer to encapsulate a reference
// to a method inside a delegate object. Its like the function pointer
// in C or C++.
}
//the Signature has to be same.
public string DiaplayMethodD1()
{
//lblDisplay.Text = "Multi-Cast Delegate on EXECUTION"; // Enable on multi-cast
return "This is returned from the first method of delegate explanation";
}
// The Method can be static also
public static string DiaplayMethodD2()
{
return " Extra words from second method";
}
最好的问候, Pritom Nandy,孟加拉国
这是一个可能有帮助的例子。
有一个使用大量数据的应用程序。需要一个允许过滤数据的功能。可以指定 6 种不同的过滤器。
立即的想法是创建 6 个不同的方法,每个方法都返回过滤后的数据。例如
公共数据 FilterByAge(int age)
公共数据 FilterBySize(int size)
.... 等等。
这很好,但是非常有限,并且会产生垃圾代码,因为它已关闭以进行扩展。
更好的方法是使用单个 Filter 方法并传递有关如何过滤数据的信息。这是可以使用委托的地方。委托是一个可以应用于数据以过滤数据的函数。
公共数据过滤器(动作过滤器)
然后使用它的代码变成
过滤器(数据 => data.age > 30);
过滤器(数据 => 数据大小 = 19);
代码 data => blah blah 成为委托。代码变得更加灵活并保持开放。
我认为它与语法糖和组织代码的方式更相关,一个很好的用途是处理与公共上下文相关的几种方法,这些方法属于对象或静态类。
并不是您被迫使用它们,您可以使用或不使用它们进行编程,但也许使用或不使用它们可能会影响代码的组织性、可读性以及为什么不酷,可能会破坏代码中的某些行。
正如有人所说,这里给出的每个示例都是一个很好的示例,您可以在其中实现它们,这只是您可以使用的语言的另一个功能。
问候