Design von Plugins

Alles rund um die Programmierung mit Qt
Antworten
shaka
Beiträge: 27
Registriert: 8. November 2006 10:36
Wohnort: München

Design von Plugins

Beitrag von shaka »

Hallo!

Wir schreiben an einer Anwendung, die zahlreiche Plugins verwenden soll, von denen wiederum jedes mehrere Interfaces exportiert. Falls wir die Dokumentation richtig verstehen, hat jedes Plugin ein "root object", das exportiert wird und alle Interfaces anbietet, die es im Plugin gibt.
Da wir die Implementierung der Interfaces über mehrere Klassen verteilen wollen (um ein unmögliches großes root object zu verweiden), landen wir bei einer Architektur, bei der das root object alle Interfaces anbietet, aber nichts tut, als Funktionsaufrufe an Kindklassen weiter zu leiten, die dann für die Implementierung einzelner Interfaces zuständig sind. Etwa so:

Code: Alles auswählen

//###################################
struct I1  //Interface1
{
	virtual ~I1() {};
	
	virtual void f1() = 0;
	virtual void f2() = 0;
	
	signals:
	virtual void aSignal() = 0;
};
Q_DECLARE_INTERFACE(...)

//###################################
struct I2  //Interface2
{
	virtual ~I2() {};

	virtual void g1() = 0;
	virtual void g2() = 0;
};
Q_DECLARE_INTERFACE(...)

//###################################
class Impl1 : public QObject, public I1   // Child class 1
{
	//...
};

//###################################
class Impl2 : public QObject, public I2   // Child class 2
{
	//...
};

//###################################
class PluginRoot : public QObject, public I1, public I2   // Root class
{
	Q_OBJECT
	Q_INTERFACES(I1 I2)
	
	public:
		PluginRoot()
		{
			pImpl1 = new Impl1(this);
			pImpl2 = new Impl2(this);
			
			connect( pImpl1, SIGNAL(aSignal()), this, SIGNAL(aSignal()) );
		};
		
		virtual ~PluginRoot();
		
		// I1
		virtual void f1() { return pImpl1->f1(); };
		virtual void f1() { return pImpl1->f2(); };

		// I2
		virtual void g1() { return pImpl2->g1(); };
		virtual void g2() { return pImpl2->g2(); };

	signals:
		virtual void aSignal();

	private:
		QObject* pImpl1;
		QObject* pImpl2;
};
Q_EXPORT_PLUGIN2(..., PluginRoot)
Das Problem damit ist, das wir wieder und wieder die gleichen Funktionen schreiben (etwa in I1.h, Impl1.h, Impl1.cpp, PluginRoot.h and PluginRoot.cpp), und meist nur mit trivialem Code (z.B. Weiterleiten des Funktionsaufrufs an eine Kind-Klasse).
Gibt es einen vernünftigeren Weg, das Ganze anzugehen?

Danke für Eure Hilfe!
Shaka

P.S: Da es mannigfaltige Kommunikation zwischen den verschiedenen Kind-Klassen gibt, ist es keine Option, das Ganze auf mehrere Plugins aufzuteilen...
AuE
Beiträge: 918
Registriert: 5. August 2008 10:58

Beitrag von AuE »

Ich habe dafür bei mir eine Basisklasse implementiert die ja nicht unbedingt ein Interface sein muss.
Von dererben bei mir alle plugins.
RHBaum
Beiträge: 1436
Registriert: 17. Juni 2005 09:58

Beitrag von RHBaum »

Da wir die Implementierung der Interfaces über mehrere Klassen verteilen wollen (um ein unmögliches großes root object zu verweiden)
Klingt bei mir eher nach schlechtem klassendesign ... ein Klasse sollte eigentlich fuer eine (abstracte) taetigkeit zustaendig sein, und nicht die eierlegendewollmichsau. Deshalb sollten die meisten klassen auch ne ueberschaubare anzahl an methoden haben, die sie brauchen.
class PluginRoot : public QObject, public I1, public I2 // Root class
warum muss root alle interfaces ableiten ??? da gibts "bessere" alternativen !
warum den root keine cast funktion verpassen, die den namen der schnittstelle als parameter uebernimmt, und nen void * zurueckgibt, den man dann auf die richtige schnittstelle casten muss ??? das ist viel flexiebler, und man vermeidet auch die mehrfachvererbung !

Ciao ....
AuE
Beiträge: 918
Registriert: 5. August 2008 10:58

Beitrag von AuE »

Ich habe das so ähnlich mit dem root.
Hat den Vorteil für mich das ich alle Objekte in der MainGui behandlen kann ohne zu wissen was es ist.
Ich habe intern eine Liste meiner root klasse (wobei die rootklasse gewisse basisfunktionen implementieren muss) somit kann ich einfach

Code: Alles auswählen

  
QPluginLoader loader(path.absoluteFilePath(pname), this);
QObject *plugin = loader.instance();
Plugin_Interface *plug = qobject_cast<AV_Plugin_Interface*>(plugin);
if (plug && !qobject_cast<Updater_plugin_interface*>(plugin))
			 {
				ui->mainStackedWidget->addWidget(plug->createWidget(this));
				qDebug() << "Loaded plugin:" <<plug->getName();
				 plug->internalizer.setPluginPath(path.absoluteFilePath(pname));
				 plug->internalizer.setPluginName(plug->getName() );
				ui->mainToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
				 if ( plug->getNavigationForPlugin()->actions().count() > 1 )
shaka
Beiträge: 27
Registriert: 8. November 2006 10:36
Wohnort: München

Mißverständnis

Beitrag von shaka »

@RHBaum

Aber gerade weil wir keine eierlegende Wollmilchsau wollen, haben wir ja das Problem - also: PluginRoot soll eben nicht alle Interfaces selbst implementieren, dafür haben wir ja die Klassen Impl1, Impl2 etc. Die Kapselung ist schon in Ordnung, die Implementierung in PluginRoot besteht ja nur aus trivialem Weiterreichen der Funktionsaufrufe, insofern ist auch die Mehrfachvererbung unproblematisch - bis auf das CodeDoubling (siehe Originalpost)

Natürlich könnte PluginRoot nur ein Interface anbieten, das dann Funktion wie

Code: Alles auswählen

I1* GetInterfaceI1();
I2* GetInterfaceI2();
enthält. (Eine Funktion, die void*-Pointer liefert, halte ich übrigens nicht mehr für zeitgemäß, und auch nicht für gutes Design.) Wir haben allerdings auch noch das Problem, das Interfaces in verschiedenen Versionen vorkommen können, und wenn alle Interfaces eines Plugins direkt von PluginRoot angeboten werden, ist es einfacher zu entscheiden, ob es Versionskonflikte gibt, als wenn man erst "in das Plugin reinsteppen" muß.

@AuE
Ist ja ähnlich wie bei uns.
RHBaum
Beiträge: 1436
Registriert: 17. Juni 2005 09:58

Beitrag von RHBaum »

Die Kapselung ist schon in Ordnung, die Implementierung in PluginRoot besteht ja nur aus trivialem Weiterreichen der Funktionsaufrufe, insofern ist auch die Mehrfachvererbung unproblematisch
Ja, wie wahrscheinlich ist es denn, das da neue funktionalitaet hinzukommt. quasi also neue Schnittstellen viel spaeter definiert werden und in das konzept einfliessen ? Dann bewerte im Hinblick daraufhin die aussage mit der multiplen vererbung nochmal !

Eine Funktion, die void*-Pointer liefert, halte ich übrigens nicht mehr für zeitgemäß, und auch nicht für gutes Design.
Aber sie ist die einzige loesung, unbekannte schnittstellen in ein vorhandens system vorab zu integrieren, ohne die verwaltung fuer jede schnittstelle anpassen zu muessen. IMHO ein integraler bestandteil von Plugins. Alle anderen loesungen, ala "Ich kenne doch alle Schnittstellen sowieso" ist kein richtiges, sondern nurn halbes Plugin konzept.
ALso reden wir hier nicht um dynamische erweiterbarkeit ohne anpassung der basis, sondern du willst nur nen Design fuer eine ueberschaubare Anzahl an Interfaces ???
Natürlich könnte PluginRoot nur ein Interface anbieten, das dann Funktion wie

Code: Alles auswählen

I1* GetInterfaceI1();
I2* GetInterfaceI2();
IMHO der sauebere weg ....

fuer "richtige" Schnittstellen (aka Protokollklassen) gibts sicher noch paar haetere Regeln ....
wenn sich irgendwie die struktur aendert -> neuer Namen ala IWidget -> IWidget2
wenn "nur"irgendwas hinzugefuegt werden soll, also du alte und neue Schnittstelle gleich behandeln willst / musst dann kommst um IWidget2: public IWidget auch ned drumherum (und nu denk hier noch mal über die multiple vererbung deiner Plugin Basis nach !!! ) ....

Schnittstellen "ändern" iss immer ne heikle sache .... die Probleme die das mit sich bringt, stellt jeden cast in den schatten. entweder hasst Aufwand ohne ende, oder Probleme ohne ende. Deswegen sollte man fuer schnittstellen auch nen eigenen entwicklungszyklus haben, und erst rausgeben, wenn die funktionalitaet ausreichend getestet wurde. Ich meine nur die Funktionalitaet der schnittstelle, ned der Impl dahinter !
Schnittstellen die sich laufend aendern werden auch ned akzeptiert ...
Imho macht es deswegen keinen Sinn, Schnittstellen eine versionsummer zu geben, sondern auf grund Ihrer Static nur einen Anhang in den Namen bekommen sollten, um den Typ der Schnittstelle zu aendern und sich durch den compiler scho durch schnittstelleninkompatiblitaeten zu schuetzen !

Ciao ....
Antworten