【问题标题】:Can I say that the relation between events and delegates adopts composite pattern?我可以说事件和委托之间的关系采用复合模式吗?
【发布时间】:2020-11-19 05:47:03
【问题描述】:

一个事件可以包含许多使用delegate定义的处理程序,我目前的理解是委托只是函数指针的抽象。由于与delegate 类型相关联的event 可以向其中添加/删除许多委托,并且复合模式将复合对象视为终端对象,因此想法是:

composite.onTriggered();
// Internally:
// foreach(handler in composite)
// {
//     handler.onTriggered();
// }

将依次调用composite 管理的每个处理程序。

但似乎public event EventHandler ThresholdReached 没有定义复合,请参阅下面代码中的注释

class Counter
{
    public event EventHandler ThresholdReached;

    protected virtual void OnThresholdReached(EventArgs e)
    {
        EventHandler handler = ThresholdReached; // So what's the point of this line?
        handler?.Invoke(this, e);
        // Why not just:
     // ThresholdReached?.Invoke(this, e);
    } 

    // provide remaining implementation for the class
}

我对抽象级别的想法是否正确?如果没有,您能否提供任何更正?

【问题讨论】:

  • .NET 事件模型更像是 observer(或发布/订阅)模式。处理程序订阅事件。发布者引发处理程序/订阅者处理程序的事件

标签: c# design-patterns delegates


【解决方案1】:

直接回答您的问题,我会说:不,采用复合模式的事件和代表之间没有关系委托设计是的,它遵循复合模式。 事件不是。 (此外,请注意您不需要事件来利用代表。(请参阅DelegateBased 下文))(我将回答您关于“此行的意义何在? ”作为附注)

尽管如此,委托类型本身遵循复合方法,“复合模式描述了一个对象组,这些对象的处理方式与单个实例是同一类型的对象。”。

反过来,正如@Flydog57 和@mark-seemann 已经提到的那样,.NET 事件模型遵循观察者模式

事件和委托之间的关系涉及可能需要委托类型(TypeSpec)的事件声明,如 部分所述II.18 定义ECMA-335 (CLI) Partitions I to VI 的事件(标准):

在典型用法中,TypeSpec(如果存在)标识其签名与传递给事件的 fire 方法的参数匹配的委托。

为了清楚起见,请检查以下两个等效示例,其中EventBased 使用没有委托字段的事件DelegateBased 使用没有事件的委托字段。请注意,我明确地说 委托字段委托类型。它们是不相同的。这两个例子都需要一个委托类型,在这个例子中声明如下:

delegate void Observer();

您可以使用以下命令运行这两个示例:

var subject = new DelegateBased(); // replace it with: var subject = new EventBased();
Observer foo = () => Console.Write("Foo");
Observer bar = () => Console.Write("Bar");
subject.RegisterObserver(foo); // subject.Caller += foo;
subject.RegisterObserver(bar); // subject.Caller += bar;
subject.Notify(); // prints: FooBar
Console.WriteLine();
subject.UnregisterObserver(foo); // subject.Caller -= foo;
subject.Notify(); // prints: Bar

接下来是EventBasedDelegateBased的两个实现,根据Observer Pattern in Wikipedia的例子使用名字

class EventBased {
  private List<Observer> observers = new List<Observer>();
  public event Observer Caller {
    add { RegisterObserver(value); }
    remove { UnregisterObserver(value); }
  }
  public void Notify() { foreach (var caller in observers) caller(); }
    
  public void RegisterObserver(Observer val) {  observers.Add(val); }
    
  public void UnregisterObserver(Observer val) { observers.Remove(val); }
}
class DelegateBased {
  private Observer observers; // delegate field without events
    
  public void Notify() { observers(); }

  public void RegisterObserver(Observer val) { 
    observers = (Observer) Delegate.Combine(observers, val); // <=> observers += val
  }
  public void UnregisterObserver(Observer val) {
    observers = (Observer) Delegate.Remove(observers, val); // <=> observers -= val
  }
}

关于您的评论:

EventHandler handler = ThresholdReached; // So what's the point of this line?
handler?.Invoke(this, e);

Jeffrey Richter 在其杰作“Clr via C#”中的第 11 章 - “以线程安全方式引发事件”中的事件(参见 NewMail如您的示例中的ThresholdReached)其中声明:

OnNewMail 方法的问题是线程可以看到NewMail 不为空,然后,就在调用NewMail 之前,另一个线程可以从链中删除一个委托NewMail@987654340 @,导致NullReferenceException 被抛出。

【讨论】:

    【解决方案2】:

    正如 Flydog57 所指出的,.NET 事件模型本质上是语言中内置的观察者模式,就像 IEnumerableforeach 实现迭代器模式一样。

    然而,《四人帮》一书中的模式处于不同的抽象层次。我不确定这在 1994 年是否对任何人都清楚,但随着几十年的使用,它变得越来越清楚(至少对我而言)其中一些模式比其他模式更普遍。其中一种模式是适配器模式,您可以将装饰器模式视为一种退化的特化。

    另一种这样的模式是复合模式。您可以将书中的一些其他模式视为 Composite 的专业化。不仅是观察者,还有命令和状态(至少如书中所述);可能还有其他人。

    我认为你的直觉是正确的。虽然事件最具体地按照观察者模式进行了模式化,但您也可以将其视为组合。如果我们consider Reactive Extensions (Rx) and the IObserver&lt;T&gt; interface instead of .NET events,这可能会更清楚。 IIRC,Rx 定义了 .NET 事件和它自己的模型之间的转换。

    更一般地说,every API that gives rise to a monoid can be modelled as a Composite。由于事件不返回任何数据(它们具有void 方法签名),它们形成了一个幺半群。因此,您也可以将它们视为复合设计模式的一个实例。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-07-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多