Zuweisung einer Schnittstellenumsetzung

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

Mein Code hat gravierende Architekturprobleme, da ich nicht weiß, wie man auf einigermaßen vernünftige Art eine Schnittstellenumsetzung der entsprechenden Schnittstelle zuweist. (Ich bin kein Informatiker :( )


Die meisten meiner .py-Dateien fangen mit Anweisungen folgender Art an:

Code: Alles auswählen

#!/usr/bin/python3

#from alg_2011 import Alg
from alg_2012 import Alg
#from alg_2013 import Alg
wobei 'Alg' der Name einer Schnittstellen-Umsetzungsklasse ist, die ich im Code dann aufrufe. Das kommt mir nicht nur unflexibel vor, sondern auch deshalb störend, weil die verschiedenen Umsetzungsklassen die verschiedene Namen haben sollten, und nicht wie bei mir nur die Dateien, welche die Umsetzungsklassen enthalten. Eigentlich sollten meine verschiedenen Umsetzungsklassen nicht alle mit dem gleichen

Code: Alles auswählen

class Alg(object): #SCHLECHT
beginnen, sondern mit

Code: Alles auswählen

class Alg_2012(Alg): #richtig
oder so ähnlich. Auch muss ich mir immer aufschreiben, welches Modul welche Schnittstellen benutzt, und dann bei einer Änderung sämtliche Module von Hand abändern, was extrem zeitraubend und fehleranfällig ist. Ich sehe aber einfach keine Möglichkeit, eine zentralisierte Klasse wie

Code: Alles auswählen

class Zuweisungen(object):
   #
   #weist Klasse 'umsetzung' der abstrakten Schnittstellenklasse 'Alg' zu
   def hole_Alg_umsetzung(self, umsetzung):
      assert umsetzung in ("alg_2011", "alg_2012", "alg_2013")
      ... #??
zu schreiben, die ich mit

Code: Alles auswählen

hole_Alg(self,umsetzung="alg_2012")
im Hauptprogramm (oder wo ich sonst will) aufrufen kann.


Ich konnte bisher weder in Python-Handbüchern noch in Büchern über Entwurfsmustern Beispiele finden, wie man so etwas macht (wobei ich gerne zugebe, dass ich von den Entwurfsmustern fast nur Bahnhof verstanden habe). Meine Kollegen arbeiten alle in Java; kann mir vielleicht jemand konkret zeigen, wie man so etwas in Python3 macht?
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Ich werde nicht ganz schlau aus deiner Beschreibung, aber ich denke du suchst soetwas:

Code: Alles auswählen

def retrieve_implementation(base_class, module_name):
    module = __import__(module_name)
    implementations = [name for name in dir(module) if issubclass(base_class)]
    assert len(implementations) == 1, "More than one implementation"
    return implementations[0]
P.S. "Schnittstellen-Umsetzungsklasse" ist IMO nicht sonderlich gebraeuchlich, mit "Implementierung einer Schnittstelle" hast du da mehr Glueck (und bist nicht an Klassen gebunden).
lunar

@Goswin Man nennt das „Dependency Injection“. In Java nimmt man dafür Spring oder Guice. StackOverflow kennt entsprechende Frameworks für Python, bezweifelt aber auch den Sinn derselben.

Ich halte DI-Frameworks für sinnvoll… in relativ statischen Sprachen wie C# oder Java. In Python würde ich von der Verwendung eines Frameworks absehen, da die Sprache selbst eigentlich dynamisch genug ist, um das entsprechende Problem ohne Framework-Ballast zu lösen.

Im konkreten Fall würde ich einfach ein zentrales Alias für den zu verwendenden Algorithmus erstellen. Erstelle ein Modul names "alg" mit folgendem Inhalt:

Code: Alles auswählen

if need_to_use_old_algorithm:
    from old_algs import Alg2011 as Alg
else:
    from new_algs import Alg2013 as Alg
Namen und Bedingung sind natürlich entsprechend anzupassen. Befinden sich alle Deine "Alg"-Implementierungen in einem Modul, dann kannst Du oben Quelltext auch direkt in selbiges einfügen, ohne ein eigenes Modul zu definieren.

Dieser Quelltext bindet den Namen "Alg" auf Modulebene an die jeweilige Implementierung. An jedweder anderen Stelle Deines Quelltexts importierst Du dann immer mit "from alg import Alg". Dann nutzt Dein Quelltext automatisch diejenige Implementierung, die Du an zentraler Stelle konfiguriert hast.

Von einer dynamischen Implementierung mittels Reflection, wie sie cofi vorschlägt, würde ich absehen, sofern die Wahl der Implementierung nicht erst wesentlich zur Laufzeit basierend auf sehr dynamischen Kriterien gewählt wird. Im Zweifelsfall ist das nur komplizierter, ohne nennenswerte Vorteile zu bringen.

Ganz allgemein hilft bei der Vermeidung derartiger Probleme übrigens die Richtlinie, derartige dynamische Abhängigkeiten auf Modulebene ganz zu vermeiden. Statt also an vielen verstreuten Stellen im Quelltext überall "Alg" zu importieren, ist es also vielmehr ratsam, den Objekten, die Exemplare von Alg benötigen, diese Exemplare von außen über Konstruktorparameter mitzugeben. Da Python Funktionen höherer Ordnung hat, kannst Du alternativen auch eine Funktion übergeben, mit der das jeweilige Objekt sein Alg-Exemplar erzeugen kann. Da Klassen ebenfalls aufrufbar sind, kannst Du sogar die entsprechende Alg-Klasse selbst direkt übergeben.
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

@cofi: Dein Code enthält einen kleinen Fehler

Code: Alles auswählen

assert len(implementations) == 1, "More than one implementation"
müsste natürlich

Code: Alles auswählen

assert len(implementations) >= 1, "More than one implementation"
lauten.

Grüße,
anogayales
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

*hust* Absicht, um Aufmerksamkeit zu testen ;)
Der richtige Fix waere aber IMO der Test, ob es nicht genau eine gibt.
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

:D Vielen Dank, es scheint zu funktionieren! Freilich mache ich (noch) keine Zuweisungen zur Laufzeit, weshalb ich mir cofi's Vorschlag wohl erst später vornehmen werde.

@lunar:
Wenn ich nur *ein* Objekt pro Umsetzung erstellen möchte, wie zum Beispiel bei einem Algorithmus, dann kann ich meinen "Arbeitsklassen" das Objekt der Umsetzungsklasse übergeben. Wenn ich aber, wie es bei Datenstrukturen fast immer der Fall ist, *viele* Objekte der gleichen Umsetzung haben möchte, dann werde ich wohl die Umsetzungsklasse selber übergeben müssen. (Vielleicht ist die Objektübergabe aber ein guter Hinweis für den Benutzer, die Singleton-Eigenschaft eines Objekts zu respektieren)
lunar

@Goswin Es ist nichts verwerfliches darin, die Klasse selbst als Parameter zu übergeben, gleichsam als eine leichtgewichtige Fabrik. Wo also ist das Problem?
Antworten