Benutzer mit den meisten Antworten
Interfaces, richtig designen?

Frage
-
Hallo Forum und frohe Weihnachten!
rein theoretisch sind die Interfaces doch nicht so schwer zu verstehen, oder?
Rein praktisch hadere ich noch mit den vermeintlichen Vorteilen von Interfaces.
Ich habe mich durch unzählige Interfaces-Tutorials durchgebissen und sehe immer noch kein Licht am Ende des Tunels.
Was ich bis jetzt verstanden habe:
1. eine einmal geschriebene (und getestete!) Klasse soll nie (in der Praxis: selten) angefasst werden
2. diese Klasse soll eigentlich nicht direkt instanziert werden, sondern durch die Interfaces angesprochen werden
3. die Klassen sollen nur einem einzigen Zweck dienen und nichts von der Existenz anderer Klassen wissen.
Soviel zur Theorie.
Lasst uns doch gemeinsam auf der Grundlage eines meiner eigentlich einfachen Programme die richtige Anwendung
von Interfaces erörten.Das Programm soll in regelmäßigen Zeitabständen eine administrative Datenbank (AdminDB) updaten (neue Tabellen/Jobs usw. erstellen).
Im Detail soll das Programm so ablaufen:
1. eine ConnSQL-Klasse verbindet sich in Schleife mit jedem der ca. 250 SQL-Server und gibt eine SQlConnection zurück
2. eine isAdminDBThere-Klasse überprüft, ob die AdminDB auf dem jeweiligen Server vorhanden ist und einen bool-Wert zurückgibt
3. eine RenameAdminDB-Klasse (wenn "ja" vom Pkt. 2 zurückkommt) benennt die vorhandene AdminDB um
4. eine InstallNewAdminDB-Klasse erstellt eine leere, aktuelle Version von AdminDB
5. eine InstallScriptsAndJobs-Klasse installiert neue Scripte usw.
6. eine LoadDataFromOldAdminDB-Klasse (wenn "ja" in Pkt. 2) überträgt die Daten aus der alten AdminDB in die neue.
7. eine DeleteOldAdminDB-Klasse löscht die alte AdminDBSo der grobe Ablauf, dazu kommen noch ca. 10 HilfsKlassen.
Ich habe das Programm ohne Interfaces umgesetzt, d. h. es wird immer eine neue Instanz einer der o. g. Klassen aufgebaut usw.
Angenommen, ich würde weder Zeit noch Aufwand scheuen und das von Anfang an neu mit Interfaces aufbauen wollen (viele
würden sagen, dass der Aufwand nicht lohnt beim solch einem kleinen Projekt) um den Nutzen der Interfaces zu verstehen. Wie gehe ich vor?Vielen Dank für Eure Ratschläge.
P.
Antworten
-
Hi,Interfaces sind nur erforderlich, wenn Objekte unterschiedlicher Typen (Klassen) mit dem gleichen Verfahren (Algorithmus, Member-Aufruf usw.) genutzt werden sollen. In Deinem Fall wäre das sinnvoll, wenn es unterschiedliche Datenbanktypen geben würde, für die unterschiedliche Zugriffsdetails erforderlich sind, aber durch einheitliche Aufrufe genutzt werden sollen.Als Beispiel könnte die Bereitstellung von Daten aus einer Access-Datei oder aus den SQL Server dienen. Dem Nutzer der Daten ist es egal, wo die Daten herkommen. Er nutzt beispielsweise lediglich eine GetData-Methode. Über Parameter, z.B. aus einer Konfigurationsdatei, wird zur Ausführungszeit festgelegt, ob ein Objekt vom Typ Access-Dataklasse oder vom Typ SQLServer-Dataklasse instanziiert und dann genutzt wird. Der Aufruf nutzt nur das Interface mit der GetData-Deklaration. Die beiden Klassen (Access-Dataklasse, SQLServer-Dataklasse) implementieren das Interface und damit die GetData-Methode.Interfaces sind auch dann interessant, wenn beispielsweise für erste Tests erst einmal eine Klasse bereitgestellt wird, die Testdaten erzeugt, um die übergeordnete Geschäftslogik zu testen, die dann später gegen die endgültige Klasse ausgetauscht werden kann, ohne dass in der Geschäftslogik etwas geändert werden muss.In Deinem Fall sollte aber auch noch bedacht werden, ob so viele Klassen erforderlich sind. Ich denke, dass mehrere Funktionalitäten zusammengefasst werden können. Daraus resultieren dann eine Datenzugriffsklasse oder auch eine sehr geringe Anzahl mehrerer Klassen, mit denen der gesamten Datenbankzugriff realisiert wird.Ein Interface stört in Deinem Fall nicht. Im Gegenteil, es bietet die Möglichkeit, schrittweise unterschiedliche Varianten einer Datenzugriffsschicht zu entwickeln, ohne die Geschäftslogik ändern zu müssen. Auch könnten mit Hilfe des Interfaces Testprojekte erstellt werden, um die unterschiedlichen Versionen der Datenzugriffsschicht zu testen.Der nachträgliche Einbau eines Interfaces ist nur wenig Arbeit. Viel Aufwand dagegen erfordert die konzeptionelle Vorarbeit, im Ergebnis derer die funktionelle Trennung der Schichten festzulegen ist, die sich dann in den konkreten Deklarationen im Interface niederschlagen.--
Peter Fleischer- Als Antwort markiert Purclot Donnerstag, 27. Dezember 2012 08:27
-
Hallo P.,
Interfaces sind ein Tool. Du würdest auch nicht einen Hammer zum Eindrehen einer Schraube verwenden, oder? Also mußt Du zunächst verstehen, wozu Interfaces gut sind.
Wir alle schreiben Code. Viel Code. Das Problem dabei ist, dass wir - wie Du mit deinem Langzeitprojekt auch - den Code immer und immer wieder über lange Zeiträume verändern und an die geänderten Anforderungen und Einsichten anpassen müssen. Ändern wir an der einen Stelle was (eben mal schnell), geht an vielen anderen Stellen was kaputt.Man könnte sich jetzt an die ersten OOP-Stunden erinnern und an die Kapselungsfunktion von Klassen denken (hast Du auch getan, wie ich sehe). Durch diese sollte erreicht werden, dass Interna einer Klasse nicht nach außen, öffentlich sichtbar und damit verändert werden können. Bringt uns das unserem Ziel näher, sauberen Code zu schreiben? Nur einen kleinen Schritt.
Denn außer der Kapselung müssen wir dafür sorgen, dass die Klassen untereinander lose gekoppelt sind. Wenn ich z.B. an den Konstruktor einer Klasse A eine Instanz der Klasse B als Parameter übergebe, dann mache ich die Klasse B von der Klasse A abhängig. Besser ist es statt der Instanzvariablen - und jetzt kommen die Schnittstellen dran - eine Schnittstelle zu übergeben.
Damit macht sich Klasse A unabhängig von Klasse B. Das geht nun so weit, dass Klasse A die Klasse B gar nicht mehr braucht, sondern stattdessen eine beliebige Klasse, die die übergebene Schnittstelle implementiert. Die Implementationdetails sind nicht wichtig, wichtig ist dass der vereinbarte Vertrag eingehalten wird.Jede IDbConnection-Schnittstelle z.B. muss eine Open() und eine Close()-Methode bereitstellen. Das reicht mir schon um beliebige Verbindungen (zu SQL Server-/Oracle-/SQLite-Datenbanken) datenbank-agnostisch zu öffnen. Würde ich keine Schnittstelle verwenden, müßten meine Methoden OpenSqlServerDb() und OpenOracleDb() heißen. Das würde zu Code-Duplizierung führen und die Wartung des Codes sehr erschweren und kostspielig machen.
Wie Peter das schon schrieb, macht es nicht viel Sinn, Schnittstellen zu verwenden, wenn Du nicht zumindest erwartest, dass in Zukunft auch andere Klassen diese Schnittstelle implementieren werden. Aber das ist mit der Kapselung in Klassen genauso: Wenn eine bestimmte Klasse A exklusiv in Zusammenhang mit einer anderen Klasse B verwendet werden wird, dann wäre es besser die Klasse A in B zu deklarieren (einzubetten). Selbst interne Sichtbarkeit wäre für eine solche Klasse fehl am Platz, da viel zu weit.
Du hast die Open/Closed- bzw. Single Responsibility-Prinzipien kurz erwähnt. Interfaces helfen vor allem bei der Implementierung des ersten Prinzips weiter, da man die Angriffsfläche reduziert und die Abhängigkeit von äußerem Code reduziert.
Sollte man dann alle Abhängigkeiten als Interfaces formulieren? Nein. Genauso wie man nur das einkapseln soll, was sich in seinem Zustand ändert, sollte man nur das als Schnittstelle übergeben, von dem man erwartet, dass es sich in Zukunft ändern könnte und von vielen Implementierern implementiert wird.
Eine Schnittstelle ist ein Vertrag. Es wäre schrecklich, wenn wir im Alltag alles vertragsmäßig (d.h. ohne Vertrauen) regeln müßten. Wenn Du eine Library schreibst, die von hunderten Entwicklern verwendet wird, oder wenn Du im allgemeinen wiederverwendbare Klassen schreibst, sind Schnittstellen hiflreicher, als wenn Du eine nicht-erweiterbare Insel-Anwendung hast, die zwar hunderte verwenden, aber ein einzelner Entwickler schreibt.Gruß
Marcel- Als Antwort markiert Purclot Donnerstag, 27. Dezember 2012 08:28
Alle Antworten
-
Hi,Interfaces sind nur erforderlich, wenn Objekte unterschiedlicher Typen (Klassen) mit dem gleichen Verfahren (Algorithmus, Member-Aufruf usw.) genutzt werden sollen. In Deinem Fall wäre das sinnvoll, wenn es unterschiedliche Datenbanktypen geben würde, für die unterschiedliche Zugriffsdetails erforderlich sind, aber durch einheitliche Aufrufe genutzt werden sollen.Als Beispiel könnte die Bereitstellung von Daten aus einer Access-Datei oder aus den SQL Server dienen. Dem Nutzer der Daten ist es egal, wo die Daten herkommen. Er nutzt beispielsweise lediglich eine GetData-Methode. Über Parameter, z.B. aus einer Konfigurationsdatei, wird zur Ausführungszeit festgelegt, ob ein Objekt vom Typ Access-Dataklasse oder vom Typ SQLServer-Dataklasse instanziiert und dann genutzt wird. Der Aufruf nutzt nur das Interface mit der GetData-Deklaration. Die beiden Klassen (Access-Dataklasse, SQLServer-Dataklasse) implementieren das Interface und damit die GetData-Methode.Interfaces sind auch dann interessant, wenn beispielsweise für erste Tests erst einmal eine Klasse bereitgestellt wird, die Testdaten erzeugt, um die übergeordnete Geschäftslogik zu testen, die dann später gegen die endgültige Klasse ausgetauscht werden kann, ohne dass in der Geschäftslogik etwas geändert werden muss.In Deinem Fall sollte aber auch noch bedacht werden, ob so viele Klassen erforderlich sind. Ich denke, dass mehrere Funktionalitäten zusammengefasst werden können. Daraus resultieren dann eine Datenzugriffsklasse oder auch eine sehr geringe Anzahl mehrerer Klassen, mit denen der gesamten Datenbankzugriff realisiert wird.Ein Interface stört in Deinem Fall nicht. Im Gegenteil, es bietet die Möglichkeit, schrittweise unterschiedliche Varianten einer Datenzugriffsschicht zu entwickeln, ohne die Geschäftslogik ändern zu müssen. Auch könnten mit Hilfe des Interfaces Testprojekte erstellt werden, um die unterschiedlichen Versionen der Datenzugriffsschicht zu testen.Der nachträgliche Einbau eines Interfaces ist nur wenig Arbeit. Viel Aufwand dagegen erfordert die konzeptionelle Vorarbeit, im Ergebnis derer die funktionelle Trennung der Schichten festzulegen ist, die sich dann in den konkreten Deklarationen im Interface niederschlagen.--
Peter Fleischer- Als Antwort markiert Purclot Donnerstag, 27. Dezember 2012 08:27
-
Hallo P.,
Interfaces sind ein Tool. Du würdest auch nicht einen Hammer zum Eindrehen einer Schraube verwenden, oder? Also mußt Du zunächst verstehen, wozu Interfaces gut sind.
Wir alle schreiben Code. Viel Code. Das Problem dabei ist, dass wir - wie Du mit deinem Langzeitprojekt auch - den Code immer und immer wieder über lange Zeiträume verändern und an die geänderten Anforderungen und Einsichten anpassen müssen. Ändern wir an der einen Stelle was (eben mal schnell), geht an vielen anderen Stellen was kaputt.Man könnte sich jetzt an die ersten OOP-Stunden erinnern und an die Kapselungsfunktion von Klassen denken (hast Du auch getan, wie ich sehe). Durch diese sollte erreicht werden, dass Interna einer Klasse nicht nach außen, öffentlich sichtbar und damit verändert werden können. Bringt uns das unserem Ziel näher, sauberen Code zu schreiben? Nur einen kleinen Schritt.
Denn außer der Kapselung müssen wir dafür sorgen, dass die Klassen untereinander lose gekoppelt sind. Wenn ich z.B. an den Konstruktor einer Klasse A eine Instanz der Klasse B als Parameter übergebe, dann mache ich die Klasse B von der Klasse A abhängig. Besser ist es statt der Instanzvariablen - und jetzt kommen die Schnittstellen dran - eine Schnittstelle zu übergeben.
Damit macht sich Klasse A unabhängig von Klasse B. Das geht nun so weit, dass Klasse A die Klasse B gar nicht mehr braucht, sondern stattdessen eine beliebige Klasse, die die übergebene Schnittstelle implementiert. Die Implementationdetails sind nicht wichtig, wichtig ist dass der vereinbarte Vertrag eingehalten wird.Jede IDbConnection-Schnittstelle z.B. muss eine Open() und eine Close()-Methode bereitstellen. Das reicht mir schon um beliebige Verbindungen (zu SQL Server-/Oracle-/SQLite-Datenbanken) datenbank-agnostisch zu öffnen. Würde ich keine Schnittstelle verwenden, müßten meine Methoden OpenSqlServerDb() und OpenOracleDb() heißen. Das würde zu Code-Duplizierung führen und die Wartung des Codes sehr erschweren und kostspielig machen.
Wie Peter das schon schrieb, macht es nicht viel Sinn, Schnittstellen zu verwenden, wenn Du nicht zumindest erwartest, dass in Zukunft auch andere Klassen diese Schnittstelle implementieren werden. Aber das ist mit der Kapselung in Klassen genauso: Wenn eine bestimmte Klasse A exklusiv in Zusammenhang mit einer anderen Klasse B verwendet werden wird, dann wäre es besser die Klasse A in B zu deklarieren (einzubetten). Selbst interne Sichtbarkeit wäre für eine solche Klasse fehl am Platz, da viel zu weit.
Du hast die Open/Closed- bzw. Single Responsibility-Prinzipien kurz erwähnt. Interfaces helfen vor allem bei der Implementierung des ersten Prinzips weiter, da man die Angriffsfläche reduziert und die Abhängigkeit von äußerem Code reduziert.
Sollte man dann alle Abhängigkeiten als Interfaces formulieren? Nein. Genauso wie man nur das einkapseln soll, was sich in seinem Zustand ändert, sollte man nur das als Schnittstelle übergeben, von dem man erwartet, dass es sich in Zukunft ändern könnte und von vielen Implementierern implementiert wird.
Eine Schnittstelle ist ein Vertrag. Es wäre schrecklich, wenn wir im Alltag alles vertragsmäßig (d.h. ohne Vertrauen) regeln müßten. Wenn Du eine Library schreibst, die von hunderten Entwicklern verwendet wird, oder wenn Du im allgemeinen wiederverwendbare Klassen schreibst, sind Schnittstellen hiflreicher, als wenn Du eine nicht-erweiterbare Insel-Anwendung hast, die zwar hunderte verwenden, aber ein einzelner Entwickler schreibt.Gruß
Marcel- Als Antwort markiert Purclot Donnerstag, 27. Dezember 2012 08:28
-
danke Marcel,
vielen Dank für die ausführliche Antwort. wie ich bereits sagte, von der Theorie her sind die Interfaces zwar nicht kompliziert, die Kunst ist aber sie in der Praxis vernünftig umzusetzen.
In meinem Fall werde ich ein Interface für die ConnSQL-Klasse bauen. Es ist momentan zwar nicht notwendig (da ich mich nur mit SQL-Servern verbinde), aber wer weiss? Für die restlichen Klassen sehe ich aktuell keine Verwendung für die Interfaces.Gruß
P. -
Hi,ich denke, dass für die ConnSQL-Klasse kein Interface erforderlich ist. Die darüber liegende funktionelle Schicht (Geschäftslogik) will Daten haben und manipulieren. Dieser Schicht ist es egal, wie die Verbindung aufgebaut wird. Das Interface zwischen Datenzugriffsschicht und Geschäftslogik sollte die Methoden und Eigenschaften beschreiben, die für die Manipulation mit den Daten erforderlich sind: Daten bereitstellen (holen) und geänderte Daten ablegen (speichern). Das sollte für die Geschäftslogik ausreichend sein. Verbindungsinformation (ConnectionString) braucht die Geschäftslogik nicht.Es kann aber auch möglich sein, dass ein Interface-Bestandteil für die Verbindung erforderlich sein kann. Das wäre beispielsweise der Fall, wenn die Oberfläche für die Generierung einer Verbindungszeichenfolge eingebunden werden soll. Dann sollten aber auch wieder Interfaces genutzt werden, wie IDbConnection usw.--
Peter Fleischer -
Hi Marcel,
du könntest dir mal die Entwurfsmuster und PEAA anschauen da findest du Beispiele wie man Interfaces sinnvoll einsetzt. Wie z.B. bei Strategy Pattern.
MFG
Björn
-
Hallo Björn,
Ich arbeite seit der Veröffentlichung der GoF-Publikation mit Entwurfmustern. Aber danke dennoch für deinen wertvollen Hinweis innerhalb dieser Diskussion.
Bei der Publikation der GoF-Patterns verstand man noch etwas anderes unter Schnittstelle als heutzutage in C#, nämlich eine abstrakte Klasse, die ihre Schnittstelle vererbt.
Im Laufe der Entwicklung der Programmiersprachen, vor allem unter dem Einfluß der Verbreitung von Java und später C#, die keine reale multiple Vererbung unterstützten und ein Surrogat über reine Schnittstellendefinitionen (interface) implementierten, wurde die Rolle der abstrakten Klassen als reine Schnittstellen etwas abgeschwächt.Aber wie Du siehst, steht eine C#-Interface nur im losen Zusammenhang mit Entwurfmustern, man kann alles ganz bequem auch über klassische OOP implementieren, wie es Erich Gamma und Konsorten anno dazumal auch getan haben.
Es ist wichtig nicht in Formalismus zu verfallen, und immer den tieferen Sinn hinter Sprachmittel zu verstehen. Also ja, Schnittstellen dort verwenden, wo sie was bringen - wenn Du möchtest auch innerhalb von Entwurfmuster-Implementationen - aber nein, Schnittstellen gehören nicht überall rein, ein Code ist nicht cooler oder schneller, oder professioneller nur weil Schnittstellen implementiert werden. Ein Vertrag muss immer einen gut definierten Sinn und eine Daseinsberechtigung haben.
Gruß
Marcel -
Hallo Marcel,
vielen Dank für Deine Antwort. Mein Ziel ist es, das Programmdesign mit den Interfaces zu verbessern, es übersichtlicher und wartungsarmer zu machen. Einerseits glaube ich zwar generell auf dem richtigen Dampfer zu sein, andererseits weiss ich noch nicht so recht "wie". Es heisst für mich also weiter probieren..
Gruß P.