我能说事件和委托之间的关系采用复合模式吗?
Can I say that the relation between events and delegates adopts composite pattern?
一个事件可以包含许多使用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
}
我在抽象层面上的想法是否正确?如果不是,您能否提供任何更正?
正如 Flydog57 指出的那样,.NET 事件模型本质上是语言内置的观察者模式,就像 IEnumerable
和 foreach
实现迭代器模式一样。
然而,四人帮书中的模式处于不同的抽象层次。我不确定 1994 年的任何人都清楚这一点,但经过数十年的使用,(至少对我而言)越来越清楚,其中一些模式比其他模式更通用。一种这样的模式是适配器模式,您可以在其中将装饰器模式视为退化的专业化。
另一个这样的模式是 Composite。您可以将书中的一些其他模式视为 Composite 的特化。不仅是 Observer,还有 Command 和 State(至少是书中描述的);可能还有其他人。
我认为你的直觉是正确的。虽然事件是在观察者模式之后最具体地模式化的,但您也可以将其视为复合模式。如果我们consider Reactive Extensions (Rx) and the IObserver<T>
interface instead of .NET events,这可能会更清楚。 IIRC,Rx 定义了 .NET 事件和它自己的模型之间的转换。
更一般地说,every API that gives rise to a monoid can be modelled as a Composite。由于事件 return 没有数据(它们具有 void
方法签名),因此它们形成了一个幺半群。因此,您也可以将它们视为 Composite 设计模式的一个实例。
直接回答你的问题,我会说:不,事件和采用复合模式的代表之间没有关系。 委托设计是,它遵循复合模式。 事件不。 (此外,请注意 你不需要事件来利用委托。(请参阅下面的 DelegateBased
))(我将回答你关于“所以这行的重点是什么?”在最后作为旁注)
然而,委托类型本身遵循复合方法,即“复合模式描述了一组对象 的处理方式与相同类型对象的 单个实例 相同。”.
反过来,正如@Flydog57 和@mark-seemann 已经提到的那样,.NET 事件模型遵循观察者模式。
事件和委托之间的关系关于事件声明,可能需要委托类型(TypeSpec
),正如 II.18 定义事件 ECMA-335 (CLI) Partitions I to VI(标准)部分所述:
In typical usage, the TypeSpec (if present) identifies a delegate whose signature matches the arguments passed to the event’s fire method.
为了清楚起见,请检查以下两个等效示例,其中 EventBased
使用 没有委托字段的事件 和 DelegateBased
使用 没有事件的委托字段。请注意,我明确地说 delegate field 或 delegate type。它们不一样。这两个示例都需要一个 delegate 类型,在本示例中声明如下:
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
接下来EventBased
和DelegateBased
的两个实现根据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
),其中指出:
The problem with the OnNewMail
method is that the thread could see that NewMail
is not null, and then, just before invoking NewMail
, another thread could remove a delegate from the chain making NewMail
null
, resulting in a NullReferenceException
being thrown.
一个事件可以包含许多使用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
}
我在抽象层面上的想法是否正确?如果不是,您能否提供任何更正?
正如 Flydog57 指出的那样,.NET 事件模型本质上是语言内置的观察者模式,就像 IEnumerable
和 foreach
实现迭代器模式一样。
然而,四人帮书中的模式处于不同的抽象层次。我不确定 1994 年的任何人都清楚这一点,但经过数十年的使用,(至少对我而言)越来越清楚,其中一些模式比其他模式更通用。一种这样的模式是适配器模式,您可以在其中将装饰器模式视为退化的专业化。
另一个这样的模式是 Composite。您可以将书中的一些其他模式视为 Composite 的特化。不仅是 Observer,还有 Command 和 State(至少是书中描述的);可能还有其他人。
我认为你的直觉是正确的。虽然事件是在观察者模式之后最具体地模式化的,但您也可以将其视为复合模式。如果我们consider Reactive Extensions (Rx) and the IObserver<T>
interface instead of .NET events,这可能会更清楚。 IIRC,Rx 定义了 .NET 事件和它自己的模型之间的转换。
更一般地说,every API that gives rise to a monoid can be modelled as a Composite。由于事件 return 没有数据(它们具有 void
方法签名),因此它们形成了一个幺半群。因此,您也可以将它们视为 Composite 设计模式的一个实例。
直接回答你的问题,我会说:不,事件和采用复合模式的代表之间没有关系。 委托设计是,它遵循复合模式。 事件不。 (此外,请注意 你不需要事件来利用委托。(请参阅下面的 DelegateBased
))(我将回答你关于“所以这行的重点是什么?”在最后作为旁注)
然而,委托类型本身遵循复合方法,即“复合模式描述了一组对象 的处理方式与相同类型对象的 单个实例 相同。”.
反过来,正如@Flydog57 和@mark-seemann 已经提到的那样,.NET 事件模型遵循观察者模式。
事件和委托之间的关系关于事件声明,可能需要委托类型(TypeSpec
),正如 II.18 定义事件 ECMA-335 (CLI) Partitions I to VI(标准)部分所述:
In typical usage, the TypeSpec (if present) identifies a delegate whose signature matches the arguments passed to the event’s fire method.
为了清楚起见,请检查以下两个等效示例,其中 EventBased
使用 没有委托字段的事件 和 DelegateBased
使用 没有事件的委托字段。请注意,我明确地说 delegate field 或 delegate type。它们不一样。这两个示例都需要一个 delegate 类型,在本示例中声明如下:
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
接下来EventBased
和DelegateBased
的两个实现根据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
),其中指出:
The problem with the
OnNewMail
method is that the thread could see thatNewMail
is not null, and then, just before invokingNewMail
, another thread could remove a delegate from the chain makingNewMail
null
, resulting in aNullReferenceException
being thrown.