Ich entwerfe gerade meine Anwendung und bin mir nicht sicher, ob ich SOLID und OOP richtig verstehe. Klassen sollten eine Sache tun und es gut machen, aber andererseits sollten sie reale Objekte darstellen, mit denen wir arbeiten.
In meinem Fall mache ich eine Feature-Extraktion für einen Datensatz und dann eine Analyse des maschinellen Lernens. Ich gehe davon aus, dass ich drei Klassen schaffen könnte
- FeatureExtractor
- DataSet
- Analyzer
Aber die FeatureExtractor-Klasse stellt nichts dar, sie tut etwas, was sie eher zu einer Routine als zu einer Klasse macht. Es wird nur eine Funktion verwendet: extract_features ()
Ist es richtig, Klassen zu erstellen, die nicht eine Sache darstellen, sondern eine Sache tun?
EDIT: nicht sicher, ob es wichtig ist, aber ich benutze Python
Und wenn extract_features () so aussehen würde: Lohnt es sich, eine spezielle Klasse für diese Methode zu erstellen?
def extract_features(df):
extr = PhrasesExtractor()
extr.build_vocabulary(df["Text"].tolist())
sent = SentimentAnalyser()
sent.load()
df = add_features(df, extr.features)
df = mark_features(df, extr.extract_features)
df = drop_infrequent_features(df)
df = another_processing1(df)
df = another_processing2(df)
df = another_processing3(df)
df = set_sentiment(df, sent.get_sentiment)
return df
quelle
Antworten:
Ja, das ist im Allgemeinen ein guter Ansatz.
Nein, das ist meiner Meinung nach ein weit verbreitetes Missverständnis. Der Zugang eines guten Anfängers zu OOP besteht oft darin, "mit Objekten zu beginnen, die Dinge aus der realen Welt darstellen" , das ist wahr.
Damit sollten Sie jedoch nicht aufhören !
Klassen können (und sollten) verwendet werden, um Ihr Programm auf verschiedene Arten zu strukturieren. Das Modellieren von Objekten aus der realen Welt ist ein Aspekt, aber nicht der einzige. Das Erstellen von Modulen oder Komponenten für eine bestimmte Aufgabe ist ein weiterer sinnvoller Anwendungsfall für Klassen. Ein "Feature Extractor" ist wahrscheinlich ein solches Modul, und selbst wenn es nur eine öffentliche Methode enthält
extract_features()
, würde ich mich wundern, wenn es nicht auch viele private Methoden und möglicherweise einen gemeinsamen Status enthält. Wenn Sie also eine KlasseFeatureExtractor
haben, erhalten Sie einen natürlichen Ort für diese privaten Methoden.Randnotiz: In Sprachen wie Python, die ein separates Modulkonzept unterstützen, kann man auch ein Modul
FeatureExtractor
dafür verwenden, aber im Kontext dieser Frage ist dies meiner Meinung nach ein vernachlässigbarer Unterschied.Darüber hinaus kann man sich einen "Merkmalsextraktor" als "eine Person oder einen Bot vorstellen, der Merkmale extrahiert". Das ist ein abstraktes "Ding", vielleicht nicht etwas, das man in der realen Welt findet, aber der Name selbst ist eine nützliche Abstraktion, die jedem eine Vorstellung davon gibt, was die Verantwortung dieser Klasse ist. Ich bin also anderer Meinung, dass diese Klasse "nichts darstellt".
quelle
extract_features
? Ich ging einfach davon aus, dass es sich um öffentliche Veranstaltungen von einem anderen Ort aus handelt. Fairerweise bin ich damit einverstanden, dass wenn diese privat sind, sie wahrscheinlich in ein Modul aufgenommen werden sollten (aber immer noch: keine Klasse, es sei denn, sie teilen den Status), zusammen mitextract_features
. (Das heißt, Sie können sie natürlich lokal innerhalb dieser Funktion deklarieren.)Doc Brown ist genau richtig: Klassen müssen keine realen Objekte darstellen. Sie müssen nur nützlich sein . Klassen sind grundsätzlich nur zusätzliche Typen, und was bedeutet
int
oderstring
entspricht das in der realen Welt? Es sind abstrakte Beschreibungen, keine konkreten, greifbaren Dinge.Das heißt, Ihr Fall ist etwas Besonderes. Nach Ihrer Beschreibung:
Sie haben vollkommen recht: Wenn Ihr Code wie abgebildet ist, hat es keinen Sinn, ihn in eine Klasse zu verwandeln. Es gibt einen berühmten Vortrag , der argumentiert, dass solche Verwendungen von Klassen in Python Code-Geruch sind und dass einfache Funktionen oft ausreichen. Ihr Fall ist ein perfektes Beispiel dafür.
Überbeanspruchung von Klassen ist auf die Tatsache zurückzuführen, dass OOP in den 1990er Jahren mit Java zum Mainstream wurde. Leider fehlten in Java zu dieser Zeit einige moderne Sprachfunktionen (z. B. Closures), was dazu führte, dass viele Konzepte ohne den Einsatz von Klassen nur schwer oder gar nicht auszudrücken waren. Zum Beispiel war es in Java bis vor kurzem unmöglich, Methoden zu haben, die state (dh closures) trugen. Stattdessen mussten Sie eine Klasse schreiben, die den Status enthält und eine einzelne Methode (so etwas wie
invoke
) verfügbar macht .Leider wurde dieser Programmierstil weit über Java hinaus populär (teilweise aufgrund eines einflussreichen Softwareentwicklungsbuchs , das ansonsten sehr nützlich ist), auch in Sprachen, die keine derartigen Problemumgehungen erfordern.
In Python sind Klassen offensichtlich ein sehr wichtiges Werkzeug und sollten großzügig eingesetzt werden. Aber sie sind nicht das einzige Werkzeug, und es gibt keinen Grund, sie dort einzusetzen, wo sie keinen Sinn ergeben. Es ist ein weit verbreitetes Missverständnis, dass freie Funktionen in OOP keinen Platz haben.
quelle
__call__
sei denn, es ist definiert, verwenden Sie in diesem Fall einfach eine Funktion!)__call__()
? Unterscheidet sich das wirklich von einer anonymen Instanz der inneren Klasse? Syntaktisch gesehen, sicher, aber von einem Sprachentwurf abgesehen, scheint es eine weniger bedeutende Unterscheidung zu sein, als Sie hier präsentieren.Seit über 20 Jahren dabei und ich bin mir auch nicht sicher.
Hier kann man kaum etwas falsch machen.
Ja wirklich? Lassen Sie mich Ihnen die Einzel beliebtesten und erfolgreichsten Klasse aller Zeiten:
String
. Wir verwenden es für Text. Und das Objekt der realen Welt, das es darstellt, ist folgendes:Warum nein, nicht alle Programmierer sind vom Fischen besessen. Hier verwenden wir eine sogenannte Metapher. Es ist in Ordnung, Modelle von Dingen zu erstellen, die nicht wirklich existieren. Es ist die Idee, die klar sein muss. Sie erstellen Bilder in den Köpfen Ihrer Leser. Diese Bilder müssen nicht echt sein. Einfach verstanden.
Ein gutes OOP-Design gruppiert Nachrichten (Methoden) um Daten (Status), sodass die Reaktionen auf diese Nachrichten je nach den Daten variieren können. Wenn Sie das tun, dann modellieren Sie eine reale Sache, schick. Wenn nicht, na ja. Solange es für den Leser sinnvoll ist, ist es in Ordnung.
Nun sicher, Sie könnten sich das so vorstellen:
Aber wenn Sie denken, dass dies in der realen Welt existieren muss, bevor Sie die Metapher verwenden können, wird Ihre Programmierkarriere eine Menge Kunst und Handwerk beinhalten.
quelle
class
undtable
undcolumn
...In acht nehmen! Nirgendwo sagt SOLID, dass eine Klasse nur "eine Sache tun" sollte. Wenn dies der Fall wäre, hätten Klassen immer nur eine einzige Methode, und es würde keinen wirklichen Unterschied zwischen Klassen und Funktionen geben.
SOLID sagt, dass eine Klasse eine einzelne Verantwortung darstellen sollte . Dies entspricht in etwa der Verantwortung von Personen in einem Team: dem Fahrer, dem Anwalt, dem Taschendieb, dem Grafikdesigner usw. Jede dieser Personen kann mehrere (verwandte) Aufgaben ausführen, die jedoch alle eine einzelne Verantwortung betreffen.
Der springende Punkt dabei ist: Wenn sich die Anforderungen ändern, müssen Sie idealerweise nur eine einzelne Klasse ändern. Dies macht den Code einfacher zu verstehen, einfacher zu ändern und reduziert das Risiko.
Es gibt keine Regel, dass ein Objekt "eine reale Sache" darstellen sollte. Dies ist nur eine Frachtkult-Überlieferung, da OO ursprünglich für die Verwendung in Simulationen erfunden wurde. Da es sich bei Ihrem Programm jedoch nicht um eine Simulation handelt (es gibt nur wenige moderne OO-Anwendungen), gilt diese Regel nicht. Solange jede Klasse eine klar definierte Verantwortung hat, sollte es Ihnen gut gehen.
Wenn eine Klasse wirklich nur eine einzige Methode hat und die Klasse keinen Status hat, können Sie in Betracht ziehen, sie zu einer eigenständigen Funktion zu machen. Dies ist absolut in Ordnung und folgt den KISS- und YAGNI-Prinzipien - Sie müssen keine Klasse erstellen, wenn Sie sie mit einer Funktion lösen können. Wenn Sie jedoch Grund zu der Annahme haben, dass Sie möglicherweise einen internen Status oder mehrere Implementierungen benötigen, können Sie dies auch zu einer Klasse von vornherein machen. Sie müssen hier Ihr bestes Urteilsvermögen anwenden.
quelle
Im Allgemeinen ist das in Ordnung.
Ohne ein bisschen genauere Beschreibung, was die
FeatureExtractor
Klasse genau machen soll, ist es schwer zu sagen.Wie auch immer, selbst wenn das
FeatureExtractor
nur eine öffentlicheextract_features()
Funktion verfügbar macht , könnte ich daran denken, sie mit einerStrategy
Klasse zu konfigurieren , die bestimmt, wie genau die Extraktion durchgeführt werden soll.Ein weiteres Beispiel ist eine Klasse mit einer Template-Funktion .
Und es gibt weitere Verhaltensentwurfsmuster , die auf Klassenmodellen basieren.
Wie Sie einige Code zur Verdeutlichung hinzugefügt.
Die Linie
genau das, was ich damit gemeint habe, dass man eine klasse mit einer strategie konfigurieren kann .
Wenn Sie eine Schnittstelle für diese
SentimentAnalyser
Klasse haben, können Sie sie an dieFeatureExtractor
Klasse an ihrem Konstruktionspunkt übergeben, anstatt sie direkt an diese spezifische Implementierung in Ihrer Funktion zu koppeln.quelle
FeatureExtractor
Klasse) hinzuzufügen, nur um noch mehr Komplexität (eine Schnittstelle für dieSentimentAnalyser
Klasse) einzuführen . Wenn eine Entkopplung erwünscht ist,extract_features
kann dieget_sentiment
Funktion als Argument verwendet werden (derload
Aufruf scheint von der Funktion unabhängig zu sein und wird nur nach ihren Auswirkungen aufgerufen). Beachten Sie auch, dass Python keine Schnittstellen hat / anregt.CacheingFeatureExtractor
oder aTimeSeriesDependentFeatureExtractor
) würde ein Objekt viel besser passen. Nur weil es für ein Objekt keine Notwendigkeit zur Zeit bedeutet nicht , wird es nie sein.__call__
Die Methode ist kompatibel, wenn Sie sie benötigen (Sie werden es nicht tun), viertens, indem Sie einen Wrapper hinzufügen, als würdenFeatureExtractor
Sie den Code mit allen anderen jemals geschriebenen Codes inkompatibel machen (es sei denn, Sie geben eine__call__
Methode an, in diesem Fall würde eine Funktion deutlich einfacher )Abgesehen von Mustern und all den ausgefallenen Sprachen / Konzepten ist das, worüber Sie gestolpert sind, ein Job oder ein Stapelprozess .
Letztendlich muss sogar ein reines OOP-Programm von etwas getrieben werden, um tatsächlich Arbeit zu leisten. Irgendwie muss es einen Einstiegspunkt geben. In dem MVC-Muster beispielsweise empfängt der "C" -Ontroller Click-Ereignisse usw. von der GUI und orchestriert dann die anderen Komponenten. In klassischen Befehlszeilentools würde eine "Haupt" -Funktion dasselbe tun.
Ihre Klasse repräsentiert eine Entität, die etwas tut und alles andere orchestriert. Sie können es Controller , Job , Main oder was auch immer in den Sinn kommt nennen.
Das hängt von den Umständen ab (und ich bin nicht mit der üblichen Vorgehensweise in Python vertraut). Wenn dies nur ein kleines One-Shot-Kommandozeilen-Tool ist, sollte eine Methode anstelle einer Klasse in Ordnung sein. Die erste Version Ihres Programms kann sicher mit einer Methode davonkommen. Wenn Sie später feststellen, dass Sie am Ende Dutzende solcher Methoden haben, vielleicht sogar mit globalen Variablen, dann ist es an der Zeit, die Klassen neu zu faktorisieren.
quelle
this
oder explizitesself
) Argument der Methode gebunden wird. Die Methoden eines Objekts sind gegenseitig "offen" rekursiv, sodass beim Ersetzenfoo
alleself.foo
Aufrufe diese Ersetzung verwenden.Wir können uns OOP als Modellierung des Verhaltens eines Systems vorstellen . Beachten Sie, dass das System nicht in der "realen Welt" existieren muss, obwohl Metaphern in der realen Welt manchmal nützlich sein können (z. B. "Pipelines", "Fabriken" usw.).
Wenn unser gewünschtes System zu kompliziert ist, um es auf einmal zu modellieren, können wir es in kleinere Teile zerlegen und diese (die "Problemdomäne") modellieren, was eine weitere Zerlegung beinhalten kann, und so weiter, bis wir zu Teilen kommen, deren Verhalten übereinstimmt (mehr oder weniger) das eines eingebauten Sprachobjekts wie einer Zahl, einer Zeichenkette, einer Liste usw.
Sobald wir diese einfachen Teile haben, können wir sie kombinieren, um das Verhalten größerer Teile zu beschreiben, die wir zu noch größeren Teilen kombinieren können, und so weiter, bis wir alle Komponenten des Bereichs beschreiben können, die für ein Ganzes benötigt werden System.
In dieser Phase des "Zusammenfügens" könnten wir einige Klassen schreiben. Wir schreiben Klassen, wenn es kein Objekt gibt, das sich so verhält, wie wir es wollen. Beispielsweise könnte unsere Domain "foos", Sammlungen von foos, die als "bars" bezeichnet werden, und Sammlungen von Bars, die als "bazs" bezeichnet werden, enthalten. Wir bemerken vielleicht, dass Foos einfach genug sind, um mit Strings zu modellieren, also machen wir das. Wir stellen fest, dass Balken erfordern, dass ihr Inhalt einer bestimmten Einschränkung entspricht, die nicht mit den von Python bereitgestellten übereinstimmt. In diesem Fall schreiben wir möglicherweise eine neue Klasse, um diese Einschränkung zu erzwingen. Vielleicht haben Bas keine derartigen Besonderheiten, deshalb können wir sie nur mit einer Liste darstellen.
Beachten Sie, dass wir konnten eine neue Klasse für jede dieser Komponenten (Foos, Bars und bazs) schreiben, aber wir nicht brauchen , um , wenn es schon etwas mit dem richtigen Verhalten. Insbesondere, damit eine Klasse nützlich ist, muss sie etwas "bereitstellen" (Daten, Methoden, Konstanten, Unterklassen usw.). Selbst wenn wir viele Ebenen von benutzerdefinierten Klassen haben, müssen wir schließlich ein eingebautes Feature verwenden. Wenn wir beispielsweise eine neue Klasse für foos schreiben würden, würde sie wahrscheinlich nur einen String enthalten. Warum also nicht die foo-Klasse vergessen und die bar-Klasse stattdessen diese Strings enthalten? Denken Sie daran, dass Klassen auch ein integriertes Objekt sind, sie sind nur ein besonders flexibles Objekt.
Sobald wir unser Domänenmodell haben, können wir einige bestimmte Instanzen dieser Teile nehmen und sie in einer "Simulation" des bestimmten Systems anordnen, das wir modellieren möchten (z. B. "ein maschinelles Lernsystem für ...").
Sobald wir diese Simulation haben, können wir sie ausführen und hey presto, wir haben eine funktionierende (Simulation eines) maschinellen Lernsystems für ... (oder was auch immer wir modellieren).
In Ihrer speziellen Situation versuchen Sie nun, das Verhalten einer "Feature Extractor" -Komponente zu modellieren. Die Frage ist, ob es eingebaute Objekte gibt, die sich wie ein "Feature Extractor" verhalten, oder ob Sie es in einfachere Dinge aufteilen müssen. Es sieht so aus, als würden sich Feature-Extraktoren sehr ähnlich wie Funktionsobjekte verhalten. Ich denke, Sie können diese Objekte als Modell verwenden.
Beachten Sie beim Erlernen solcher Konzepte, dass verschiedene Sprachen unterschiedliche integrierte Funktionen und Objekte bereitstellen können (und einige verwenden natürlich nicht einmal eine Terminologie wie "Objekte"!). Daher sind Lösungen, die in einer Sprache sinnvoll sind, in einer anderen möglicherweise weniger nützlich (dies kann sogar für verschiedene Versionen derselben Sprache gelten!).
Historisch gesehen hat sich ein Großteil der OOP-Literatur (insbesondere "Design Patterns") auf Java konzentriert, was sich von Python stark unterscheidet. Beispielsweise sind Java-Klassen keine Objekte, Java hatte bis vor kurzem keine Funktionsobjekte, Java hat eine strenge Typprüfung (die Interfaces und Unterklassen fördert), während Python das Duck-Typing fördert, Java hat keine Modulobjekte, Java-Ganzzahlen / schwimmt / etc. sind keine Objekte, Meta-Programmierung / Introspektion in Java erfordert "Reflektion" und so weiter.
Ich versuche nicht, Java in Betracht zu ziehen (als weiteres Beispiel dreht sich eine Menge der OOP-Theorie um Smalltalk, das sich wiederum sehr von Python unterscheidet), sondern ich möchte nur darauf hinweisen, dass wir sehr sorgfältig über den Kontext und das Thema nachdenken müssen Einschränkungen, in denen Lösungen entwickelt wurden, und ob dies der Situation entspricht, in der wir uns befinden.
In Ihrem Fall scheint ein Funktionsobjekt eine gute Wahl zu sein. Wenn Sie sich fragen, warum in einer "Best Practice" -Richtlinie Funktionsobjekte nicht als mögliche Lösung erwähnt werden, liegt dies möglicherweise einfach daran, dass diese Richtlinien für ältere Versionen von Java geschrieben wurden!
quelle
Pragmatisch ausgedrückt, wenn ich ein "Verschiedenes, das etwas Wichtiges tut und getrennt sein sollte", und es keine eindeutige Heimat hat, schreibe ich es in einen
Utilities
Abschnitt und verwende diesen als meine Namenskonvention. dhFeatureExtractionUtility
.Vergessen Sie die Anzahl der Methoden in einer Klasse. Eine einzelne Methode heute muss möglicherweise morgen auf fünf Methoden erweitert werden. Entscheidend ist eine klare und konsistente Organisationsstruktur, beispielsweise ein Versorgungsbereich für verschiedene Funktionssammlungen.
quelle