Diese Frage ist sprachunabhängig, aber nicht vollständig, da sich die objektorientierte Programmierung (Object Oriented Programming, OOP) beispielsweise in Java , das keine erstklassigen Funktionen hat, von der in Python unterscheidet .
Mit anderen Worten, ich fühle mich weniger schuldig, unnötige Klassen in einer Sprache wie Java zu erstellen, aber ich habe das Gefühl, dass es einen besseren Weg geben könnte, wenn Sprachen wie Python weniger gut geschrieben sind.
Mein Programm muss mehrmals eine relativ komplexe Operation ausführen. Dieser Vorgang erfordert viel "Buchhaltung", muss einige temporäre Dateien erstellen und löschen, usw.
Aus diesem Grund müssen auch viele andere "Unteroperationen" aufgerufen werden - alles in einer großen Methode zusammenzufassen ist nicht sehr schön, modular, lesbar usw.
Das sind Ansätze, die mir in den Sinn kommen:
1. Erstellen Sie eine Klasse, die nur über eine öffentliche Methode verfügt und den für die Unteroperationen erforderlichen internen Status in ihren Instanzvariablen beibehält.
Es würde ungefähr so aussehen:
class Thing:
def __init__(self, var1, var2):
self.var1 = var1
self.var2 = var2
self.var3 = []
def the_public_method(self, param1, param2):
self.var4 = param1
self.var5 = param2
self.var6 = param1 + param2 * self.var1
self.__suboperation1()
self.__suboperation2()
self.__suboperation3()
def __suboperation1(self):
# Do something with self.var1, self.var2, self.var6
# Do something with the result and self.var3
# self.var7 = something
# ...
self.__suboperation4()
self.__suboperation5()
# ...
def suboperation2(self):
# Uses self.var1 and self.var3
# ...
# etc.
Das Problem, das ich bei diesem Ansatz sehe, ist, dass der Status dieser Klasse nur intern Sinn macht und mit ihren Instanzen nichts tun kann, außer ihre einzige öffentliche Methode aufzurufen.
# Make a thing object
thing = Thing(1,2)
# Call the only method you can call
thing.the_public_method(3,4)
# You don't need thing anymore
2. Erstellen Sie eine Reihe von Funktionen ohne Klasse und übergeben Sie die verschiedenen intern benötigten Variablen (als Argumente) zwischen ihnen.
Das Problem, das ich dabei sehe, ist, dass ich viele Variablen zwischen Funktionen übergeben muss. Außerdem wären die Funktionen eng miteinander verwandt, würden jedoch nicht zu Gruppen zusammengefasst.
3. Wie 2., aber machen Sie die Statusvariablen global, anstatt sie zu übergeben.
Dies wäre überhaupt nicht gut, da ich die Operation mehr als einmal mit unterschiedlichen Eingaben durchführen muss.
Gibt es einen vierten, besseren Ansatz? Wenn nicht, welcher dieser Ansätze wäre besser und warum? Fehlt mir etwas?
quelle
Antworten:
Option 1 ist ein gutes Beispiel für die korrekte Verwendung der Kapselung . Sie möchten, dass der interne Status vor externem Code verborgen bleibt.
Wenn das bedeutet, dass Ihre Klasse nur eine öffentliche Methode hat, dann soll es so sein. Es wird so viel einfacher zu warten sein.
Wenn Sie in OOP eine Klasse haben, die genau eins macht, eine kleine öffentliche Oberfläche hat und ihren gesamten internen Zustand verbirgt, dann sind Sie (wie Charlie Sheen sagen würde) GEWINNEN .
Option 2 weist eine geringe Kohäsion auf . Dies erschwert die Wartung.
Option 3 leidet wie Option 2 unter einer geringen Kohäsion, aber viel stärker!
Die Geschichte hat gezeigt, dass die Bequemlichkeit globaler Variablen durch die damit verbundenen hohen Wartungskosten aufgewogen wird. Deshalb hört man alte Fürze wie mich die ganze Zeit über die Einkapselung schimpfen.
Die gewinnende Wahl ist # 1 .
quelle
ThingDoer(var1, var2).do_it()
vs.do_thing(var1, var2)
.def do_thing(var1, var2): return _ThingDoer(var1, var2).run()
auf Modulebene , um die externe API etwas hübscher zu gestalten.Ich denke # 1 ist eigentlich eine schlechte Option.
Betrachten wir Ihre Funktion:
Welche Daten verwendet die Unteroperation1? Sammelt es Daten, die von Suboperation2 verwendet werden? Wenn Sie Daten weitergeben, indem Sie sie auf sich selbst speichern, kann ich nicht sagen, wie die Teile der Funktionalität zusammenhängen. Wenn ich mich selbst betrachte, stammen einige der Attribute aus dem Konstruktor, einige aus dem Aufruf von the_public_method und einige wurden zufällig an anderen Stellen hinzugefügt. Meiner Meinung nach ist es ein Chaos.
Was ist mit Nummer 2? Schauen wir uns zunächst das zweite Problem an:
Sie würden in einem Modul zusammen sein, also würden sie total zusammen gruppiert.
Meiner Meinung nach ist das gut. Dies macht die Datenabhängigkeiten in Ihrem Algorithmus explizit. Indem Sie sie in globalen Variablen oder in einem Selbst speichern, verbergen Sie die Abhängigkeiten und lassen sie weniger schlimm erscheinen, aber sie sind immer noch da.
In der Regel bedeutet dies, dass Sie nicht den richtigen Weg gefunden haben, um Ihr Problem zu lösen. Sie finden es umständlich, mehrere Funktionen aufzuteilen, weil Sie versuchen, sie falsch aufzuteilen.
Natürlich ist es schwer zu erraten, was ein guter Vorschlag wäre, ohne Ihre tatsächliche Funktion zu sehen. Aber Sie geben hier einen kleinen Hinweis darauf, womit Sie es zu tun haben:
Lassen Sie mich ein Beispiel für etwas auswählen, das Ihrer Beschreibung entspricht, einen Installateur. Ein Installationsprogramm muss eine Reihe von Dateien kopieren. Wenn Sie den Vorgang jedoch vorzeitig abbrechen, müssen Sie den gesamten Vorgang zurückspulen, einschließlich des Zurücksetzens aller Dateien, die Sie ersetzt haben. Der Algorithmus dafür sieht ungefähr so aus:
Multiplizieren Sie nun diese Logik, um Registrierungseinstellungen, Programmsymbole usw. vornehmen zu müssen, und Sie haben ein ziemliches Durcheinander.
Ich denke, dass Ihre Lösung Nummer 1 so aussieht:
Dies macht den gesamten Algorithmus klarer, verbirgt jedoch die Art und Weise, wie die verschiedenen Teile kommunizieren.
Ansatz 2 sieht ungefähr so aus:
Nun ist es explizit, wie sich Teile der Daten zwischen Funktionen bewegen, aber es ist sehr umständlich.
Wie soll diese Funktion geschrieben werden?
Das FileTransactionLog-Objekt ist ein Kontextmanager. Wenn copy_new_files eine Datei kopiert, geschieht dies über das FileTransactionLog, das die temporäre Kopie erstellt und festhält, welche Dateien kopiert wurden. Im Ausnahmefall werden die Originaldateien zurückkopiert, und im Erfolgsfall werden die temporären Kopien gelöscht.
Dies funktioniert, weil wir eine natürlichere Zerlegung der Aufgabe gefunden haben. Zuvor mischten wir die Logik zum Installieren der Anwendung mit der Logik zum Wiederherstellen einer abgebrochenen Installation. Jetzt verarbeitet das Transaktionsprotokoll alle Details zu temporären Dateien und zur Buchhaltung, und die Funktion kann sich auf den grundlegenden Algorithmus konzentrieren.
Ich vermute, dass sich Ihr Fall im selben Boot befindet. Sie müssen die Buchhaltungselemente in eine Art Objekt extrahieren, damit Ihre komplexe Aufgabe einfacher und eleganter ausgedrückt werden kann.
quelle
Da der einzige offensichtliche Nachteil von Methode 1 sein suboptimales Verwendungsmuster ist, denke ich, besteht die beste Lösung darin, die Kapselung einen Schritt weiter zu treiben: Verwenden Sie eine Klasse, stellen Sie aber auch eine freistehende Funktion bereit, die nur das Objekt erstellt, die Methode aufruft und zurückgibt :
Damit haben Sie die kleinstmögliche Schnittstelle zu Ihrem Code, und die Klasse, die Sie intern verwenden, wird tatsächlich nur zu einem Implementierungsdetail Ihrer öffentlichen Funktion. Das Aufrufen von Code muss nie über Ihre interne Klasse informiert sein.
quelle
Einerseits ist die Frage irgendwie sprachunabhängig; Andererseits hängt die Implementierung von der Sprache und ihren Paradigmen ab. In diesem Fall ist es Python, das mehrere Paradigmen unterstützt.
Neben Ihren Lösungen gibt es auch die Möglichkeit, die Operationen auf eine funktionalere Weise zustandslos abzuschließen , z
Es kommt alles darauf an
-Wenn Ihre Codebasis OOP ist, verletzt dies die Konsistenz, wenn plötzlich einige Teile in einem (mehr) funktionalen Stil geschrieben werden.
quelle
Warum gestalten, wenn ich codieren kann?
Ich biete den gelesenen Antworten eine konträre Sichtweise. Aus dieser Perspektive konzentrieren sich alle Antworten und offen gesagt sogar die Fragen auf die Codierungsmechanik. Dies ist jedoch ein Designproblem.
Ja, da es für Ihr Design Sinn macht. Es kann eine Klasse für sich selbst oder ein Teil einer anderen Klasse sein oder sein Verhalten kann auf Klassen verteilt sein.
Bei objektorientiertem Design geht es um Komplexität
Der Sinn von OO besteht darin, große, komplexe Systeme erfolgreich zu erstellen und zu warten, indem der Code in Bezug auf das System selbst eingekapselt wird. "Richtiges" Design sagt, dass alles in einer Klasse ist.
OO-Design bewältigt Komplexität in erster Linie über fokussierte Klassen, die dem Prinzip der Einzelverantwortung folgen. Diese Klassen verleihen dem gesamten Atemzug und der Tiefe des Systems Struktur und Funktionalität, interagieren und integrieren diese Dimensionen.
Angesichts dessen wird oft behauptet, dass Funktionen, die an dem System hängen - der allzu allgegenwärtigen allgemeinen Dienstprogrammklasse -, ein Code-Geruch sind, der auf ein unzureichendes Design hindeutet. Ich neige dazu, zuzustimmen.
quelle
Vergleichen Sie Ihre Anforderungen mit denen der Standard-Python-Bibliothek und sehen Sie, wie diese implementiert werden.
Wenn Sie kein Objekt benötigen, können Sie dennoch Funktionen innerhalb von Funktionen definieren. In Python 3 gibt es eine neue
nonlocal
Deklaration, mit der Sie Variablen in Ihrer übergeordneten Funktion ändern können.Möglicherweise ist es dennoch nützlich, einige einfache private Klassen in Ihrer Funktion zu haben, um Abstraktionen zu implementieren und Vorgänge aufzuräumen.
quelle
nonlocal
in meinen derzeit installierten Python-Bibliotheken nichts . Vielleicht können Sie sich ein Herz nehmen,textwrap.py
das eineTextWrapper
Klasse hat, aber auch einedef wrap(text)
Funktion, die einfach eineTextWrapper
Instanz erstellt, die.wrap()
Methode darauf aufruft und zurückgibt. Verwenden Sie also eine Klasse, aber fügen Sie einige praktische Funktionen hinzu.