Laden...

[erledigt] Design? Eventempfänger registrieren über Interfaces

Erstellt von Luth vor 17 Jahren Letzter Beitrag vor 17 Jahren 1.286 Views
L
Luth Themenstarter:in
36 Beiträge seit 2006
vor 17 Jahren
[erledigt] Design? Eventempfänger registrieren über Interfaces

Hallo zusammen!

Nachdem die Foren- und Google-Suche wenig zu Tage gefördert hat, versuche ich es mal wieder hier und hoffe, das Forum ist das richtige für diese Art von Fragen....

Prinzipiell geht es mir darum, das eine Reihe von Klassen für den Empfang von Ereignissen registriert werden soll. Dies soll in Abhängigkeit von den implementierten Interfaces geschehen. Ich habe eine Idee wie das umzusetzen wäre, allerdings keine Ahnung ob an der Sache ein Haken ist oder ob es überhaupt funktioniert. Daher wäre es nett, wenn mich jemand aufklärt ob das so funktionieren kann...oder ob es bessere Lösungen gibt, bevor ich das alles implementiere und hinterher nichts geht.

Hintergrund ist der folgende:

Ich habe eine Klasse "Manager", die eine ganze Reihe von Ereignissen anbietet. Diese lassen sich in 3 Gruppen unterteilen, Dialog-, System- und Datenändungsereignisse. Der "Manager" initialisiert und verwaltet die Klassen und löst die Ereignisse aus. Einige der Klassen sollen nun nur auf Dialog- bzw. Systemereignisse reagieren, andere auf Datenänderungs- und Dialogereignisse und wiederum andere auf alle Ereignisse. Angedacht war, dies über Interfaces zu regeln. Eine Klasse die das Interface "IDialogConsumer" implementiert, wird für den Empfang von Dialogereignissen, mit dem Interface "ISystemStateConsumer" für Systemereignisse registriert. Und so weiter ...

Ein Beispiel:
Der Manager bietet das Dialog-Ereignis ValueChange an das mit den entsprechenden Eventargs ausgelöst wird.


public event ValueChangeHandler ValueChange;
public delegate void ValueChangeHandler(ValueChangeEventArgs e);

public void OnValueChange(ValueChangeEventArgs e)
        {
            ValueChange(e);
        }

Eine Klasse "Receiver" implementiert das Interface "IDialogConsumer", welches eine Methode handleValueChange spezifiziert.


void handleValueChange(ValueChangeEventArgs e);

Beim Programmstart wird vom Manager überprüft, welche Klassen das Interface "IDialogConsumer" implementieren und die Klassen für die Ereignisbehandlung registriert.


public void registerListeners()
{
    foreach(Receiver r in receiverList)
    {
         if(r is IDialogConsumer)
         {
             ValueChange+=new ValueChangeHandler(r.handleValueChange);
         }
         
         if(r is ISystemStateConsumer)
         {
            ......
         }
         
         .....
    }
}


Nachdem jetzt wahrscheinlich genug Verwirrung gestifetet wurde, noch einmal die Frage:
Kann das so funktionieren oder lässt sich das schlauer machen? 🤔

Ich danke schonmal für etwaige Antworten,
der Lutz

B
1.529 Beiträge seit 2006
vor 17 Jahren

Ich glaube, du hast einen kleinen Denkfehler.
Ereignisse dienen dazu, einer anderen Klasse irgendetwas mitzuteilen.
Wenn der Manager die Ereignisse auslöst, muss er sie auch anbieten. Jede Klasse abonniert dann nur die Events, die sie benötigt.
Und wenn der Manager die Bearbeitung in den Klassen anstoßen soll, müssen diese entsprechende Methoden bereitstellen (über ein Interface), welche der Manager dann aufruft.
Du vermischst in deiner Implementation Manager und abhängige Klassen. Deklariere lieber ein Interfaces IManageable mit zwei Methoden RegisterEvents() und UnregisterEvents(), welche dann klassenspezifisch die benötigten Events des Managers registrieren bzw. deregistrieren.
RegisterEvents wird dann beim Hinzufügen zu der Verwaltungsstruktur des Managers aufgerufen (im Allgemeinen List<IManageable>), UnregisterEvents beim Entfernen aus dem Manager.

Also etwa:

public interface IManageable
{
   // benötigte Events des Manager hier registrieren
   public void RegisterEvents( Manager manage );
   // und hier wieder deregistrieren
   public void UnregisterEvents( Manager manage );
}

public class Manager
{
   // ...
   private List<IManageable> myManagedObjects = new List<IManageable>();
   public void Add( IManageable myClass )
   {
      if (myClass != null)
      {
         myManagedObjects.Add( myClass );
         myClass.RegisterEvents( this );
      }
   }
   public void Remove( IManageable myClass )
   {
      if ((myClass != null) && (myManagedObjects.Contains( myClass ))
      {
         myClass.UnregisterEvents( this );
         myManagedObjects.Remove( myClass );
      }
   }
}

Zusätzliche Interfaces können deine Klassen nach Belieben implementieren, um jedoch verwaltet werden zu können, müssen sie IManageable implementieren.

EDIT: Code verbessert

L
Luth Themenstarter:in
36 Beiträge seit 2006
vor 17 Jahren

Guten (fast noch) Morgen...

Danke erstmal für die Antwort! Ich bin mir nur nicht sicher, dass ich diese richtig verstanden habe.

Ereignisse dienen dazu, einer anderen Klasse irgendetwas mitzuteilen.
Wenn der Manager die Ereignisse auslöst, muss er sie auch anbieten. Jede Klasse abonniert dann nur die Events, die sie benötigt.

Das Ereignisse anderen Klassen etwas mitteilen sollen ist mir schon klar. Ich denke das tut mein erstes Vorschlag auch. Die Events sind schließlich im Manager deklariert und die Ereignisdaten werden an die registrierten Empfänger zur Verarbeitung gesendet. Ich will dabei aber vermeiden, dass eine Klasse sich aussuchen kann, welche Ereignisse sie verarbeitet. Eine Klasse die IDialogConsumer implementiert, muss auch alle Dialogereignisse verarbeiten können. Daher die Vorgabe der Methoden zum Handling der Ereignisse durch das Interface...

Vielleicht meinst du aber auch, das man dann gleich Methodenaufrufe mit Parameterübergabe verwenden könnte und auf die Ereignisse ganz verzichten?

Du vermischst in deiner Implementation Manager und abhängige Klassen.

Das verstehe ich nicht. Abhängige Klassen? Abhängig von was?


Vielleicht noch mal was zum Hintergrund. Der Manager ist zur Ansteuerung von (virtuellen) Ausgabegeräten vorgesehen. Ich habe eine Konfigurationsdatei vorliegen in der steht, welche Ausgabegeräte benutzt werden sollen und welche Sorte von Informationen (Dialog-, System- und/oder Dateninformationen) sie ausgeben sollen. Da steht zum Beispiel, benutze ein Gerät "Screen" zur Ausgabe von Dialog- und Systeminformationen, aber nicht für Daten. Benutze ein Gerät "Printer" zur Ausgabe von System- und Dateninfos, aber nicht für Dialoge usw...

Momentan weis ich nicht, welche Geräte und wie diese später einmal implementiert werden. Was ich aber weis ist, dass alle Geräte von einer abstrakten Klasse "OutputDevice" erben. Daher benutze ich den in der Konfiguration angegebenen Namen, um in der Assembly nach einer gleichnamigen Klasse zu suchen und diese zu initialisieren. Das Interface IManagable ist damit meines Erachtens nach unnötig, da explizit angegeben wird welche Klassen zu benutzen sind. Diese sind also auf jeden Fall "Managable", da sie von OutputDevice erben.

Dann prüfe ich (nach dem Ansatz im ersten Post), welche Interfaces diese implementieren. Ein "Screen" ist vielleicht in der Lage, alle Informationen auszugeben, besitzt also alle 3 Interfaces. Die Konfiguration sagt mir, dass er aber nur Dialog- und Systeminformationen ausgeben soll. Daher registriert der Manager den Empfänger Screen entsprechend. Da (nach meinem Ansatz) die Methoden zur Ereignisbehandlung durch das Interface vorgegeben sind, kann der Manager die Geräte nun selbst als Empfänger für bestimmte Ereignisse registrieren, in Abhängigkeit von den implementierten Interfaces und der gegebenen Konfiguration.

Damit ist der wesentliche Unterschied, dass die Geräte sich nicht selbst für bestimmte Ereignisse registrieren, sondern der Manager dies für sie tut. Wenn ich dich richtig verstanden habe meinst du, dass sich damit die Ereignisse auch erübrigt haben weil man ja gleich entsprechende Methoden aufrufen könnte. Stimmt das so in etwa?

B
1.529 Beiträge seit 2006
vor 17 Jahren

Ich will dabei aber vermeiden, dass eine Klasse sich aussuchen kann, welche Ereignisse sie verarbeitet.

Die Klassen sollten die benötigten Handler selbst registrieren. Alles andere wäre ein Verstoß gegen das OO-Paradigma der Kapselung und auch unnötig. Denn selbst wenn du die Handler zwangsweise für die Klasse registriert, stellt das ja noch keine Garantie dar, dass diese auch sinnvoll implementiert sind. Ein fauler Programmierer (bzw. einer, der nicht alle Events braucht) schreibt dann einfach leere Methodenrümpfe, um das Interface zu erfüllen. Das ist nicht sehr sinnvoll

Vielleicht meinst du aber auch, das man dann gleich Methodenaufrufe mit Parameterübergabe verwenden könnte und auf die Ereignisse ganz verzichten?

Das wäre dann angebrachter.

Du vermischst in deiner Implementation Manager und abhängige Klassen.
Das verstehe ich nicht. Abhängige Klassen? Abhängig von was?

Du triffst im Manager Aussagen und Entscheidungen über die zu verwaltenden Klassen, die einfach unnötig sind. Ein Interface stellt ja bloß eine formale Schnittstellenbeschreibung zur Verfügung. Es gibt keine Möglichkeit zu entscheiden, ob die Klasse auch die Funktionalität des Interface erfüllt. So könnte ich das Interface IComparer implementieren und in der Methode Compare den Hash (o.ä.) der Objekte berechnen. Ich erfülle damit formal das Interface, aber ignoriere den Sinn. Das kannst du nun mal nicht verhindern, wenn du Interfaces zur Verfügung stellst.

Damit ist der wesentliche Unterschied, dass die Geräte sich nicht selbst für bestimmte Ereignisse registrieren, sondern der Manager dies für sie tut. Wenn ich dich richtig verstanden habe meinst du, dass sich damit die Ereignisse auch erübrigt haben weil man ja gleich entsprechende Methoden aufrufen könnte. Stimmt das so in etwa?

Wie gesagt: da du nur die Struktur, nicht aber die Funktionalität überprüfen kannst, musst du einfach darauf vertrauen, dass sich die Klassen korrekt verhalten.
Und damit ist es prinzipiell egal, ob du im Manager Events zur Verfügung stellst (wäre mein bevorzugter Weg) oder direkt Methoden der verschiedenen Interfaces aufrufst.
Aus den oben genannten Gründen halte ich es jedoch für besser, wenn die Klassen ihre Events selbst registrieren. Schließlich wirst du in einer WinForms-Anwendung auch nicht gezwungen, alle Events zu registrieren, ob du sie brauchst oder nicht. Das würde nur dazu führen, dass alle Programme aus sehr vielen leeren Methodenrümpfen bestünden...

L
Luth Themenstarter:in
36 Beiträge seit 2006
vor 17 Jahren

Tja...um es kurz zu machen, du hast natürlich recht mit deinen Ausführungen. Diesmal ist angekommen was du gemeint hast und die Erklärung war einleuchtend. Ich werde es so machen empfohlen. Die Events im Manager anbieten und die Geräte die Registrierung und das Abmelden übernehmen lassen.

Vielen Dank nochmal für's auf die Sprünge helfen! 🙂

Lutz