Ich arbeite derzeit an einem Softwareprojekt, das die Komprimierung und Indizierung von Videoüberwachungsmaterial durchführt. Bei der Komprimierung werden Hintergrund- und Vordergrundobjekte geteilt und der Hintergrund als statisches Bild und der Vordergrund als Sprite gespeichert.
Vor kurzem habe ich begonnen, einige der von mir für das Projekt entworfenen Klassen zu wiederholen.
Mir ist aufgefallen, dass es viele Klassen gibt, die nur eine einzige öffentliche Methode haben. Einige dieser Klassen sind:
- VideoCompressor (mit einer
compress
Methode, die ein Eingabevideo vom Typ aufnimmtRawVideo
und ein Ausgabevideo vom Typ zurückgibtCompressedVideo
). - VideoSplitter (mit einer
split
Methode, die ein Eingabevideo vom Typ aufnimmtRawVideo
und einen Vektor mit 2 Ausgabevideos vom Typ zurückgibtRawVideo
). - VideoIndexer (mit einer
index
Methode, die ein Eingabevideo vom Typ aufnimmt und einen Videoindex vom TypRawVideo
zurückgibtVideoIndex
).
Ich finde ich jede Klasse Instanziierung nur zu telefonieren wie VideoCompressor.compress(...)
, VideoSplitter.split(...)
, VideoIndexer.index(...)
.
An der Oberfläche denke ich, dass die Klassennamen ausreichend beschreibend für ihre beabsichtigte Funktion sind und es sich tatsächlich um Substantive handelt. Entsprechend sind ihre Methoden auch Verben.
Ist das eigentlich ein Problem?
quelle
Antworten:
Nein, das ist kein Problem, ganz im Gegenteil. Es ist ein Zeichen der Modularität und der klaren Verantwortung der Klasse. Die schlanke Oberfläche ist aus Sicht eines Benutzers dieser Klasse leicht zu erfassen und fördert die lose Kopplung. Dies hat viele Vorteile, aber fast keine Nachteile. Ich wünschte, mehr Komponenten würden so entworfen!
quelle
Es ist nicht mehr objektorientiert. Da diese Klassen nichts darstellen, sind sie nur Gefäße für die Funktionen.
Das heißt nicht, dass es falsch ist. Wenn die Funktionalität ausreichend komplex oder generisch ist (dh die Argumente sind Schnittstellen, keine konkreten Endtypen), ist es sinnvoll, diese Funktionalität in einem separaten Modul abzulegen .
Von dort hängt es von Ihrer Sprache ab. Wenn die Sprache freie Funktionen hat, sollten dies Module sein, die Funktionen exportieren. Warum so tun, als wäre es eine Klasse, wenn dies nicht der Fall ist? Wenn die Sprache keine freien Funktionen wie z. B. Java hat, erstellen Sie Klassen mit einer einzelnen öffentlichen Methode. Nun, das zeigt nur die Grenzen des objektorientierten Designs. Manchmal passt funktional einfach besser zusammen.
Es gibt einen Fall, in dem Sie möglicherweise eine Klasse mit einer einzelnen öffentlichen Methode benötigen, da die Schnittstelle mit einer einzelnen öffentlichen Methode implementiert werden muss. Sei es für Beobachtermuster oder Abhängigkeitsinjektion oder was auch immer. Hier kommt es wieder auf die Sprache an. In Sprachen mit erstklassigen Funktoren (C ++ (
std::function
oder Vorlagenparameter), C # (Delegat), Python, Perl, Ruby (proc
), Lisp, Haskell, ...) verwenden diese Muster Funktionstypen und benötigen keine Klassen. Java hat (noch) keine Funktionstypen, daher verwenden Sie einzelne Methodenschnittstellen und entsprechende einzelne Methodenklassen.Natürlich befürworte ich nicht das Schreiben einer einzigen großen Funktion. Es sollte über private Subroutinen verfügen, diese können jedoch für die Implementierungsdatei (statischer oder anonymer Namespace auf Dateiebene in C ++) oder für eine private Hilfsklasse privat sein, die nur innerhalb der öffentlichen Funktion instanziiert wird ( Daten speichern oder nicht? ).
quelle
Es kann Gründe geben, eine bestimmte Methode in eine dedizierte Klasse zu extrahieren. Einer dieser Gründe ist das Zulassen der Abhängigkeitsinjektion.
Stellen Sie sich vor, Sie haben eine Klasse,
VideoExporter
die ein Video komprimieren kann. Ein sauberer Weg wäre, eine Schnittstelle zu haben:was so implementiert werden würde:
und so benutzt:
Eine schlechte Alternative wäre eine,
VideoExporter
die viele öffentliche Methoden hat und alle Aufgaben erledigt, einschließlich des Komprimierens. Es würde schnell zu einem Wartungs-Albtraum werden, was es schwierig macht, Unterstützung für andere Videoformate hinzuzufügen.quelle
Dies ist ein Zeichen dafür, dass Sie Funktionen als Argumente an andere Funktionen übergeben möchten. Ich vermute, dass Ihre Sprache (Java?) Dies nicht unterstützt. Wenn dies der Fall ist, liegt dies weniger an Ihrem Design als vielmehr an der Sprache Ihrer Wahl. Dies ist eines der größten Probleme bei Sprachen, die darauf bestehen, dass alles eine Klasse sein muss.
Wenn Sie diese Faux-Funktionen nicht wirklich weitergeben, möchten Sie nur eine freie / statische Funktion.
quelle
Ich weiß, dass ich zu spät zur Party komme, aber wie jeder scheint es versäumt zu haben, darauf hinzuweisen:
Dies ist ein bekanntes Entwurfsmuster mit dem Namen: Strategy Pattern .
Ein Strategiemuster wird verwendet, wenn es mehrere mögliche Strategien zur Lösung eines Unterproblems gibt. In der Regel definieren Sie eine Schnittstelle, die einen Vertrag für alle Implementierungen erzwingt, und verwenden dann eine Form der Abhängigkeitsinjektion , um die konkrete Strategie für Sie bereitzustellen.
Zum Beispiel in diesem Fall könnten Sie haben
interface VideoCompressor
und haben dann mehrere alternative Implementierungen zum Beispielclass H264Compressor implements VideoCompressor
undclass XVidCompressor implements VideoCompressor
. Aus dem OP geht nicht hervor, dass es sich um eine Schnittstelle handelt, auch wenn dies nicht der Fall ist. Es kann einfach sein, dass der ursprüngliche Autor die Tür offen gelassen hat, um bei Bedarf ein Strategiemuster zu implementieren. Was an und für sich auch gutes Design ist.Das Problem, dass OP ständig die Klassen instanziiert, um eine Methode aufzurufen, besteht darin, dass sie die Abhängigkeitsinjektion und das Strategiemuster nicht korrekt verwendet. Anstatt es dort zu instanziieren, wo Sie es benötigen, sollte die übergeordnete Klasse ein Mitglied mit dem Strategieobjekt haben. Und dieses Mitglied sollte zum Beispiel in den Konstruktor injiziert werden.
In vielen Fällen führt das Strategiemuster zu Schnittstellenklassen (wie Sie zeigen) mit nur einer einzigen
doStuff(...)
Methode.quelle
Was Sie haben, ist modular und das ist gut, aber wenn Sie diese Verantwortlichkeiten in IVideoProcessor gruppieren würden, wäre dies aus DDD-Sicht wahrscheinlich sinnvoller.
Andererseits, wenn Teilen, Komprimieren und Indizieren in keiner Weise zusammenhängen, dann würde ich sie als separate Komponenten behalten.
quelle
Es ist ein Problem - Sie arbeiten eher vom funktionalen Aspekt des Designs als von den Daten. Was Sie tatsächlich haben, sind 3 eigenständige Funktionen, die OO-geprüft wurden.
Sie haben beispielsweise eine VideoCompressor-Klasse. Warum arbeiten Sie mit einer Klasse zum Komprimieren von Videos? Warum gibt es keine Videoklasse mit Methoden zum Komprimieren der (Video-) Daten, die jedes Objekt dieses Typs enthält?
Beim Entwerfen von OO-Systemen empfiehlt es sich, Klassen zu erstellen, die Objekte darstellen, anstatt Klassen, die Aktivitäten darstellen, die Sie anwenden können. Früher wurden Klassen als Typen bezeichnet - OO war eine Möglichkeit, eine Sprache mit Unterstützung für neue Datentypen zu erweitern. Wenn Sie an OO so denken, erhalten Sie eine bessere Möglichkeit, Ihre Klassen zu entwerfen.
BEARBEITEN:
Lassen Sie mich versuchen, mich ein wenig besser zu erklären. Stellen Sie sich eine String-Klasse mit einer concat-Methode vor. Sie können so etwas implementieren, bei dem jedes aus der Klasse instanziierte Objekt die Zeichenfolgendaten enthält
aber das OP möchte, dass es so funktioniert:
Jetzt gibt es Orte, an denen eine Klasse verwendet werden kann, um eine Sammlung verwandter Funktionen zu speichern. Dies ist jedoch kein OO. Es ist eine praktische Möglichkeit, die OO-Funktionen einer Sprache zu verwenden, um die Codebasis besser zu verwalten von "OO Design". Das Objekt ist in solchen Fällen völlig künstlich und wird einfach so verwendet, weil die Sprache nichts Besseres bietet, um diese Art von Problem zu lösen. z.B. In Sprachen wie C # würden Sie eine statische Klasse verwenden, um diese Funktionalität bereitzustellen - der Klassenmechanismus wird wiederverwendet, Sie müssen jedoch kein Objekt mehr instanziieren, nur um die Methoden dafür aufzurufen. Am Ende haben Sie Methoden wie
string.IsNullOrEmpty(mystring)
die, die ich für schlecht haltemystring.isNullOrEmpty()
.Wenn also jemand fragt, wie ich meine Klassen entwerfe, empfehle ich, an die Daten zu denken, die die Klasse enthalten wird, anstatt an die Funktionen, die sie enthält. Wenn Sie sich für "eine Klasse ist ein Bündel von Methoden" entscheiden, schreiben Sie am Ende einen Code im "besseren C" -Stil. (was nicht unbedingt eine schlechte Sache ist, wenn Sie C-Code verbessern), aber es wird Ihnen nicht das beste von OO entworfene Programm geben.
quelle
Video
und neigt dazu, solche Klassen mit Funktionalität zu überschwemmen, was häufig in unordentlichem Code mit> 10 KB LOC pro Klasse endet. Das erweiterte OO-Design unterteilt die Funktionalität in kleinere Einheiten wie aVideoCompressor
(und lässt eineVideo
Klasse nur eine Datenklasse oder eine Fassade für das seinVideoCompressor
).VideoCompressor
kein Objekt darstellt . Daran ist nichts auszusetzen, es zeigt nur die Grenze des objektorientierten Designs.Video
Klasse, aber die Komprimierungsmethode ist keineswegs trivial, sodass ich diecompress
Methode tatsächlich in mehrere andere private Methoden aufteilen würde. Dies ist ein Teil des Grundes meiner Frage, nachdem ich " Don't create verb classes"Video
Klasse zu haben , aber es würde sich aus einerVideoCompressor
, einerVideoSplitter
und anderen verwandten Klassen zusammensetzen, die in guter OO-Form sehr zusammenhängende Einzelklassen sein sollten.Nach dem ISP- Prinzip (Interface Segregation) sollte kein Client gezwungen werden, sich auf Methoden zu verlassen, die er nicht verwendet. Die Vorteile sind vielfältig und klar. Ihr Ansatz respektiert den ISP voll und ganz, und das ist gut so.
Ein anderer Ansatz, der auch den ISP berücksichtigt, besteht beispielsweise darin, eine Schnittstelle für jede Methode (oder einen Satz von Methoden mit hoher Kohäsion) zu erstellen und dann eine einzige Klasse zu haben, die alle diese Schnittstellen implementiert. Ob dies eine bessere Lösung ist oder nicht, hängt vom Szenario ab. Dies hat den Vorteil, dass bei Verwendung der Abhängigkeitsinjektion möglicherweise ein Client mit verschiedenen Mitarbeitern (einer pro Schnittstelle) vorhanden ist. Letztendlich verweisen jedoch alle Mitarbeiter auf dieselbe Objektinstanz.
Übrigens hast du gesagt
scheinen diese Klassen Dienste (und damit staatenlos) zu sein. Hast du darüber nachgedacht, sie zu Singletons zu machen?
quelle
Ja, da ist ein Problem. Aber nicht schwerwiegend. Ich meine, Sie können Ihren Code so strukturieren und nichts Schlimmes wird passieren, er ist wartbar. Diese Strukturierung weist jedoch einige Schwächen auf. Überlegen Sie beispielsweise, ob sich die Darstellung Ihres Videos (in Ihrem Fall in der RawVideo-Klasse gruppiert) ändert, und aktualisieren Sie alle Ihre Operationsklassen. Oder denken Sie daran, dass Sie möglicherweise mehrere Darstellungen von Videos haben, die sich in der Laufzeit unterscheiden. Dann müssen Sie die Darstellung einer bestimmten "Operations" -Klasse zuordnen. Auch subjektiv ist es ärgerlich, für jede Operation, die Sie mit smth ausführen möchten, eine Abhängigkeit zu ziehen. und um die Liste der Abhängigkeiten zu aktualisieren, die jedes Mal übergeben werden, wenn Sie entscheiden, dass die Operation nicht mehr benötigt wird oder wenn Sie eine neue Operation benötigen.
Auch das ist eigentlich eine Verletzung von SRP. Einige Leute behandeln SRP lediglich als Leitfaden für die Aufteilung von Verantwortlichkeiten (und gehen zu weit, indem sie jedem Vorgang eine eigene Verantwortung zuweisen), vergessen jedoch, dass SRP auch ein Leitfaden für die Gruppierung von Verantwortlichkeiten ist. Und gemäß SRP sollten Verantwortlichkeiten, die sich aus dem gleichen Grund ändern, zusammengefasst werden, damit sie sich bei Änderungen auf so wenige Klassen / Module wie möglich beschränken. Für große Klassen ist es kein Problem, mehrere Algorithmen in derselben Klasse zu haben, solange diese Algorithmen miteinander verwandt sind (dh Wissen teilen, das außerhalb dieser Klasse nicht bekannt sein sollte). Das Problem sind große Klassen mit Algorithmen, die in keiner Weise zusammenhängen und sich aus verschiedenen Gründen ändern / variieren.
quelle