Hallo,
ich bin gerade dabei, mir für meine Anwendung eine saubere Plugin-Architektur auszudenken und teste da ein wenig rum.
Ich habe mir mal ein System nach diesem Vorbild aufgebaut: PluginsInCSharp auf Codeprojekt
Das klappt auch wunderbar, nur eine Sache will mir daran nicht so recht gefallen und dazu würde mich eure Meinung interessieren bzw. ob ihr andere Wege kennt:
Jedes Plugin besitzt dort eine Klasse, welches das Plugininterface implementiert. Wenn Forms verwendet werden, wird in dieser Klasse das Form-Objekt deklariert.
Und hier ist mein Problem: Normalerweise gehe ich aufgrund einer sauberen Trennung von Gui und Businessklassen genau den umgekehrten Weg: Die Form kennt die Businessklasse aber die Businessklasse kennt die Form eben nicht!
Im Konkreten Fall soll ein Plugin bei mir aus zwei Forms / oder Usercontrols (ist ja egal) und mindestens einer Businessklasse bestehen. Ein Form stellt die Oberfläche des Plugins dar, ein anderes Form ein Konfigurationsmenü des Plugins und die Businessschicht eben die Funktionalität (hier ist auch Datenaustausch zwischen Plugin und Hauptanwendung erforderlich)
ich hoffe ich habe meine Bedenken gegenüber der Vorgehensweise auf Codeprojekt verständlich rübergebracht!
Gruß
Thomas
Hallo ThomasR,
du hast ganz Recht. Gui und Businessklassen sollten sauber getrennt sein.
In Generic Manipulator Tool findest du so eine Trennung realisiert. Daran kannst du dich orientieren.
herbivore
Hallo herbivore,
natürlich habe ich mich (vorbildlich wie ich bin 😄) hier im Forum schon eine lange Zeit nach dem Thema umgeschaut, daher kenne ich auch das Generic Manipulator Tool schon. Leider hilft mir das in diesem Fall nicht weiter, da die Plugins dort keine eigene GUI/Forms mitbringen sondern nur aus jeweils einer einzigen Klasse bestehen.
Gruß
Thomas
Hrm, der einfachste Weg um das zu realisieren wäre eine Trennung auch zwischen den Schnittstellen bzw. Interfaces gekoppelt mit Factory.
Das ganze habe ich damals bei meinen ersten Pluginprojekt wie folgt gelöst um auch später ein InternetLive Update zu realisieren:
Soo und nun gehts los, die Hauptanwendung also das dritte Projekt dient dazu Plugins mit Informationen wie deren Schnittstellenimplementation bereitzustellen. So wie Codeprojekt es anbietet nur etwas aufgedröselter. Dafür braucht man eine Klasse zum Laden und entladen der Module, quasi fast das selbe wie im zweiten Projekt. Um die Plugins zu Identifizieren benötigt es verschiedene Schnittstellen: IPlugin - beinhaltet Properties wie Version aber vorallem Dependencies, eine Liste von abhängigkeiten - den das Plugin kann ja andere Plugings als vorraussetzung benötigen, ebenso kann man noch andere wichtige implementationen in die Shcnittstelle mitreinbringen, eine davon wäre die bereitstellung der vorhandenen Liste von IForm's. IForm ist weiderum eine Schnittstelle welche Grundlegende Methoden von System.Windows.Form implementiert - also Methoden wie Show Hide und Properties wie Visible usw. Die nächste Schnittstelle ist für Plugins der Sorte IBusinessObject - diese besonderen Objecte bilden die BusinessLayer ab. Somit kann ein Plugin IForm's und IBusinessObjecte beinhalten, oder nur IBusinessObjecte oder nur IForm's. Das ist wichtig falls jemand externes irgendwann mal anbindungen an dein Modul schreiben möchte.
Soo das war der leichte Teil, die Komunikationsschicht ist ja das A und O eines Pluginsystems. Das wichtigste ist eine efiziente Fehlerfindung bei Komunikationsfehlern. Hierzu implementiere eine Singletonklasse in das dritte Projekt. In der Singletonklasse stellst du ein Delegate das z.B. über ein Proeprtie ansprichst bereit - sprich du zeigst auf den Instanzierten Handler nicht aber auf das Event. Intern stellst du eine private event mit dem Scope auf das delegate bereit. Soo was will man damit ereichen? Ganz einfach, man nutzt eine Intiligente Komposition als Informationsträger, diese realisiert man über das Command Patern. Hierzu kannst du eine Schnittstelle schreiben die zu den selben Schnittstellen gepackt werden wo sich auch IForm und IBusinessObject und IPlugin befindet, diese nennst du ICommand. ICommand beinhaltet nur ein Property Type und eine Methode void Execute, später kannst noch Undo, Redo hinzufügen aber ist erstmal nicht wichtig. Jetzt erstellst du eine Basisklasse (abstract) welche die Schnittstelle ICommand beinhaltet. Davon abgeleitet erstellst du z.B. folgende Klassen: FormAction, MenuAction, PluginAction und später evtl noch nen paar mehr. Die Singletonklasse mit dem delegate feuert und bearbeitet diese Klassen und zwar über die Schnittstelle ICommand. Was bedeutet das? - Ganz einfach, die ePlugins referenzieren auf das dritte Projekt diese Hauptanwendung und wenn etwas z.B. bei deiner Form passieren soll das diese angezeigt werdne soll oder einen Menüeintrag hinzufügen oder löschen soll so erstellst du eine diese Actions wie ForAction und oder MenuAction und übergibst diese an das Delegate der Singletonklasse. Und voila, du hast eine getrennte Schicht. Was ist nun mit den BusinessObjecten in den Forms? Diese Ladest du ja in die generische Liste im PluginLoader und stellst diesen Loader ebenso über die Singletonklasse zur verfügung, somit kannst du über die Schnittstelle IBusinessObject mit angabe des entsprechenden Plugins z.B. über dessen festen Namen als string bereit.
So, das Protokolieren und Fehlerfinden von Komunikationsfehlern zwische den Plugins ist dann das einfachste der wellt in dem Event der Singletonklasse verarbeitet und vershcickst du ja die Nachrichten von Plugin A auch an Plugin B ohne das diese sich überhaupt kennen, hier kannst du auch ein try catch einbauen und Fehler die erzeugt werdne abfangen und Protokollieren. Und besser noch, du kannst sogar alle Nachrichten mitloggen selbst jene die keinen Fehler erzeugten.
Hoffe das dir das weiterhilft .-)
Stichworte: Plugin, CommandPattern, Singleton, BusinessObjects, Actions
Wie vernichtet stand Andreas unter den flammenden Augen seiner Kunden.
Ihm war's, als stünde des Schicksals dunkle Wetterwolke über seinem Haupte X(
Hallo ThomasR,
Leider hilft mir das in diesem Fall nicht weiter, da die Plugins dort keine eigene GUI/Forms mitbringen sondern nur aus jeweils einer einzigen Klasse bestehen.
Das ist doch aber genau was du willst. Das Plugin soll ja wegen der Trennung von GUI und Modell gerade kein GUI enthalten. Es muss aber natürlich stattdessen alle Informationen anbieten, die das GUI braucht, um das Plugin grafisch darzustellen. Beim Generic Manipulator Tool wird das inbesondere bei der Konfiguration klar. Das Plugin bringt zwar keinen Konfigurationsdialog mit, bestimmt aber trotzdem, was man konfigurieren kann. Es bietet also alle Informationen an, damit das Host-Programm daraus einen Konfigurationsdialog aufbauen kann. Insofern hilft dir das Generic Manipulator Tool auch in deinem Fall weiter.
Dass die Plugins dort keine eigene GUI/Forms mitbringen, sondern nur aus jeweils einer einzigen Klasse bestehen ist gerade die Lösung deines Problems!
herbivore
der einfachste Weg...uiuiui 8o
danke Andreas.May für deine Antwort. Bis ich das durchschaut habe werde ich deinen Text allerdings noch 5 - 100 mal lesen müssen 😉
Das ist doch aber genau was du willst. Das Plugin soll ja wegen der Trennung von GUI und Modell gerade kein GUI enthalten
Doch soll es. Das Plugin soll beides beinhalten, aber eben so dass beides auch im Plugin sauber getrennt ist. Das Konzept vom GM hilft mir nichts, weil ich in der Forms-Gestaltung der Plugins flexibler sein muss.
Hallo ThomasR,
und was machst du, wenn das Host-Programm später ein anderes GUI bedienen soll (WPF, Web-Anwendung, ...)? Dann nützt dir die saubere Trennung innerhalb des Plugins gar nichts. Gewonnen hast du erst, wenn das Plugin gar kein Windows.Forms mehr enthält.
Ich sage mal so: Wenn klar ist, dass die Host-Anwendung immer Windows.Forms ist, kannst du dir die Trennung von GUI und Modell (etwas überspitzt formuliert) gleich sparen. Wenn jedoch klar ist, dass die Host-Anwendung nicht immer Windows.Forms sein wird, dann nützt dir eine reine Trennung nichts, sondern nur der Verzicht auf Windows-Forms im Plugin. Genau das macht das Generic Manipulator Tool.
Wie flexibel die "Forms"-Gestaltung ist, hängt davon ab, was du im Host-Programm bzw. im Interface des Plugins an Gestaltungsmöglichkeiten vorsiehst.
herbivore
ok, also mein zweck ist es nicht die Trennung bis in den Exzess zu betreiben. Es ist auch definitiv nicht zu erwarten, dass die Anwendung mal als Web oder WPF laufen soll. (Und wenn, dann müssen eben auch die Plugins (bzw. eben nach möglichkeit nur deren GUI) geändert werden.
Mein Ziel ist es eigentlich nur, "sauberen" Code zu schreiben. Und da ist mir eben die beidseitige Navigierbarkeit zwischen Form und Logikklasse ein bisschen ein Dorn im Auge...
Hallo ThomasR,
wenn du also sowieso vorhast, Forms zu schreiben, was hindert dich dann daran die Zugriffsrichtung umzudrehen? Also so dass die Forms auf das Modell zugreifen und das Modell das Form nicht kennt?
herbivore
wenn du also sowieso vorhast, Forms zu schreiben, was hindert dich dann daran die Zugriffsrichtung umzudrehen? Also so dass die Forms auf das Modell zugreifen und das Modell das Form nicht kennt?
daran hindert mich, dass dann ja die ganze Kommunikation zur Host-Anwendung über die Form läuft... ich hoffe ich habe dich jetzt richtig verstanden...du meinst dann also dass die Form das IPlugin implementiert?
Hallo ThomasR,
du meinst dann also dass die Form das IPlugin implementiert?
ja!
Oder von mir aus auch zwei bzw. drei Plugin-Interfaces für Form, Konfiguration und Modell.
herbivore
Hallo herbivore,
3 Interfaces waren auch mein erster Gedanke, allerdings weiß ich noch nicht so recht wie ich die dann unter einen Hut bringe...könntest du bitte kurz erläutern, wie du dir das vorstellst?
Danke
ThomasR
Hallo ThomasR,
da gibt es verschiedene Möglichkeiten.
Es kommt z.B. darauf an, wer die Business-Objekte erzeugen soll bzw. kann. Wenn das Form.Objekt dazu in der Lage ist, dann würde u.U. ein Interface reichen. Das Form-Objekt würde dann das Business-Objekt und den Konfigurationsdialog erzeugen und selbst verwenden. Alle Zugriffe würde über das Form-Objekt gehen.
Natürlich könnte könnte das Form-Objekt (respektive das Plugin-Interface) auch zwei Methoden haben, damit man direkten Zugriff auf Business-Objekt und Konfigurationsdialog bekommt.
public IBusinessObject GetBusinessObject ()
public IConfigurationDialog GetConfigurationDialog ()
herbivore
dankeschön!
ich werde die Sache nochmal überdenken und mich dann für die in meinen Augen sauberste Lösung entscheiden.
Oder mal CAB/SCSF anschauen.
Hier ist das alles schon vorexerziert.
Pluginsystem, MVP/MVC pattern, Eventmachanismus, IOC usw.
Und kostenlos, im Source und mit einer immer grösser werdenden gemeinde.