In tegenstelling tot Marcel zie ik persoonlijk geen redenen om Events (geïmplementeerd met Delegates) en Interfaces niet door elkaar te gebruiken. Bij een interface heb je inderdaad geen weet van de implementor (het object), daarvoor dienen interfaces tenslotte. Dat de ontvanger van een event zou moeten weten wie de implementor is, daar ben ik het dan weer niet mee eens: het is eigenlijk voldoende dat de interface gekend is. De oplossing die Marcel aandraagt (Observer Design Pattern, eigenlijk) is altijd mogelijk maar Delegation via Events (het Event Pattern bij wijze van spreken) heeft een aantal voordelen. En één van de voordelen is dat de zetter van de delegate zelf geen implementor moet zijn van een door de klasse met event gekende interface. (Zie ook Exploring the Observer Pattern )
Het echte probleem ligt 'm in de combinatie van een aantal keuzes die ze bij Borland gemaakt hebben om
1) Delegates te implementeren als een soort Procedural types, en met ongeveer dezelfde syntax: op zich misschien niet zo onlogisch, maar het zijn klassen dus moet je er ook voor zorgen dat je ze op die manier kan benaderen. De nodige code wordt door de compiler gegenereerd op de achtergrond. Hier zijn trouwens nog andere problemen mee: op MultiCastDelegates kan je normaal BeginInvoke en EndInvoke oproepen (Asynchroon), de nodige code (methods) wordt wel gegenereerd, maar je kan er niet aan zonder reflection te gebruiken.
2) de min-of-meer Delphi Win32 compatibele vorm van syntax voor properties in Delphi for .NET die vereist dat je de getter en/of setter opgeeft (of Fields, maar dat kan dan weer niet in interfaces, natuurlijk)
3) De min-of-meer Delphi Win32 compatibele vorm van syntax voor Interfaces (en de noodzaak om ook voor properties in Interfaces de getter/setter te declareren).
4) De speciale property (syntax) vereist voor een MultiCastDelegate: add en remove op een Field!, nodig voor de Include en Exclude methods. Op de achtergrond wordt aan de klasse met deze Event op de achtergrond twee methods toegevoegd: add_MijnEventProperty en remove_MijnEventPropery (zie ook verder)
1 + 2 + 3 + 4 samen zorgen voor syntactische problemen wat betreft de intentie van de TS.
Dus, wat eigenlijk zo zou moeten kunnen (hier VB.NET), kan voor zover ik weet niet in Delphi 8, om louter syntactische redenen!
Code:
Public Delegate Sub MijnDelegate(ByVal Sender As Object, ByVal e As EventArgs)
Public Interface IMijnInterface
Event Boodschap As MijnDelegate
Sub Send()
End Interface
Public Class MijnKlasse
Implements IMijnInterface
// Event mag protected, is dan enkel via interface beschikbaar
// maar voor deze test: public
Public Event Boodschap As MijnDelegate Implements IMijnInterface.Boodschap
Sub Send() Implements IMijnInterface.Send
RaiseEvent Boodschap(Me, System.EventArgs.Empty)
End Sub
End Class
// En dan b.v.:
Private Sub Ontvang(ByVal Sender As Object, ByVal e As System.EventArgs)
ListBox1.Items.Add(Sender.GetType.ToString)
End Sub
Private Sub Ontvang2(ByVal Sender As Object, ByVal e As System.EventArgs)
ListBox2.Items.Add(Sender.GetType.ToString)
End Sub
Private Sub Test()
// met klasse, multicast
Dim obj As New MijnKlasse
AddHandler obj.Boodschap, AddressOf Ontvang
AddHandler obj.Boodschap, AddressOf Ontvang2
// met interface, multicast
Dim objI As IMijnInterface = New MijnKlasse
AddHandler objI.Boodschap, AddressOf Ontvang
AddHandler objI.Boodschap, AddressOf Ontvang2
obj.Send()
objI.Send()
End Sub
In Delphi 8 ben ik nooit verder gekomen dan volgende code.
Noteer alvast het verschil tussen de read/write syntax en de add/remove syntax voor event properties. In het eerste laatste geval is het multicast, in het eerste geval niet (zoals events in Delphi Win32).
Code:
MyEvent = procedure(Sender: System.Object; e: System.EventArgs);
IMyInterface = Interface
procedure add_MyEvent(AnEvent: MyEvent);
procedure remove_MyEvent(AnEvent: MyEvent);
procedure Fire;
end;
MyClass = class(System.Object, IMyInterface )
strict private
FMyEvent: MyEvent;
public
// IMyInterface
// Noteer dat add_MyEvent en remove_MyEvent
// door de Multicast Event property MyEvent op de achtergrond
// worden gegenereerd
procedure Fire;
property MyEvent:MyEvent add FMyEvent remove FMyEvent;
end;
//...
procedure TWinForm.Test;
var
AMyClass: MyClass;
AMyInterface: IMyInterface;
begin
AMyClass := MyClass.Create;
Include(AMyClass.MyEvent, Ontvang);
Include(AMyClass.MyEvent, Ontvang2);
AMyInterface := MyClass.Create;
AMyInterface.add_MyEvent(Ontvang);
AMyInterface.add_MyEvent(Ontvang2);
AMyClass.Fire;
AMyInterface.Fire;
end;
procedure TWinForm.Ontvang(Sender: TObject; e: System.EventArgs);
begin
ListBox1.Items.Add(Sender.ClassType.ClassName)
end;
procedure TWinForm.Ontvang2(Sender: TObject; e: System.EventArgs);
begin
ListBox2.Items.Add(Sender.ClassType.ClassName)
end;
// MyClass
procedure MyClass.Fire;
begin
if Assigned(FMyEvent) then FMyEvent(Self,nil);
end;
Bookmarks