Objektorientiertes Klassendesign

12

Ich habe mich über gutes objektorientiertes Klassendesign gewundert. Insbesondere fällt es mir schwer, mich zwischen diesen Optionen zu entscheiden:

  1. statische vs Instanzmethode
  2. Methode ohne Parameter oder Rückgabewert vs. Methode mit Parametern und Rückgabewert
  3. überlappende vs unterschiedliche Methode Funktionalität
  4. private vs public Methode

Beispiel 1:

Diese Implementierung verwendet Instanzmethoden ohne Rückgabewert oder Parameter, ohne überlappende Funktionalität und alle Methoden öffentlich

XmlReader reader = new XmlReader(url);
reader.openUrl();
reader.readXml();
Document result = reader.getDocument();

Beispiel 2:

Diese Implementierung verwendet statische Methoden mit Rückgabewerten und Parametern, mit überlappender Funktionalität und privaten Methoden

Document result = XmlReader.readXml(url); 

In Beispiel eins sind alle Methoden öffentliche Instanzen, was den Komponententest vereinfacht. Obwohl alle Methoden unterschiedlich sind, ist readXml () von openUrl () abhängig, da openUrl () zuerst aufgerufen werden muss. Alle Daten werden in Instanzfeldern deklariert, sodass in keiner Methode außer im Konstruktor und in den Accessoren Rückgabewerte oder Parameter vorhanden sind.

In Beispiel zwei ist nur eine Methode öffentlich, der Rest ist statisch, was den Komponententest erschwert. Die Methoden überlappen sich, wenn readXml () openUrl () aufruft. Es gibt keine Felder, alle Daten werden als Parameter in Methoden übergeben und das Ergebnis wird sofort zurückgegeben.

Welchen Grundsätzen sollte ich folgen, um eine ordnungsgemäße objektorientierte Programmierung durchzuführen?

siamii
quelle
2
Statische Dinge sind schlecht, wenn Sie Multithreading ausführen. Neulich hatte ich einen statischen XMLWriter wie XMLWriter.write (data, fileurl). Da es jedoch einen privaten statischen FileStream gab, der diese Klasse aus mehreren Threads gleichzeitig verwendete, überschrieb der zweite Thread den ersten Thread FileStream und verursachte einen Fehler, der sehr schwer zu finden war. Statische Klassen mit statischen Elementen + Multithreading sind ein Rezept für eine Katastrophe.
Per Alexandersson
1
@Paxinum. Das von Ihnen beschriebene Problem ist ein Statusproblem, kein "statisches" Problem. Wenn Sie einen Singleton mit nicht statischen Elementen verwenden, tritt beim Multithreading dasselbe Problem auf.
mike30
1
@Per Alexandersson Statische Methoden sind in Bezug auf die Parallelität nicht schlecht. Statischer Zustand ist schlecht. Aus diesem Grund funktioniert die funktionale Programmierung, bei der alle Methoden statisch sind, in gleichzeitigen Situationen sehr gut.
Yuli Bonner

Antworten:

10

Beispiel 2 ist ziemlich schlecht zum Testen ... und ich meine nicht, dass Sie die Interna nicht testen können. Sie können Ihr XmlReaderObjekt auch nicht durch ein Scheinobjekt ersetzen, da Sie überhaupt kein Objekt haben.

Beispiel 1 ist unnötig schwer zu verwenden. Wie wäre es mit

XmlReader reader = new XmlReader(url);
Document result = reader.getDocument();

Das ist nicht schwieriger zu bedienen als Ihre statische Methode.

Dinge wie das Öffnen der URL, das Lesen von XML, das Konvertieren von Bytes in Zeichenfolgen, das Parsen, das Schließen von Sockets usw. sind uninteressant. Das Erstellen und Verwenden eines Objekts ist wichtig.

Also meiner Meinung nach ist es das richtige OO-Design, nur die beiden Dinge öffentlich zu machen (es sei denn, Sie benötigen aus irgendeinem Grund wirklich die Zwischenschritte). Statik ist böse.

maaartinus
quelle
-1. Sie KÖNNEN XmlReader tatsächlich durch ein Scheinobjekt ersetzen. Nicht mit einem hirntoten Open-Source-Moick-Framework, aber mit einem guten Industrial-Grade-Framework können Sie es;) Kostet ein paar Hundert US-Dollar pro Entwickler, aber es funktioniert wunderbar, die versiegelte Funktionalität der von Ihnen veröffentlichten APIs zu testen.
TomTom
2
+1 für die Nichteinhaltung der Verkaufsargumente von TomTom. Wenn ich einen Einzeiler will, schreibe ich lieber etwas wie Document result = new XmlReader(url).getDocument();Warum? Damit ich es upgraden kann Document result = whateverReader.getDocument();und whateverReadermir von etwas anderem übergeben habe.
candied_orange
6

Hier ist die Sache - es gibt keine richtige Antwort und es gibt keine absolute Definition für "richtiges objektorientiertes Design" (einige Leute werden dir eine anbieten, aber sie sind naiv ... gib ihnen Zeit).

Es kommt alles auf Ihre Ziele an.

Sie sind Künstler und das Papier ist leer. Sie können ein zartes, fein gezeichnetes Schwarz-Weiß-Seitenporträt oder ein abstraktes Gemälde mit riesigen Streifen gemischter Neons zeichnen. Oder irgendetwas dazwischen.

Also, was fühlt sich für das Problem, das Sie lösen, richtig an? Was sind die Beschwerden der Leute, die Ihre Klassen benutzen müssen, um mit XML zu arbeiten? Was ist schwer an ihrem Job? Welche Art von Code versuchen sie zu schreiben, der die Aufrufe Ihrer Bibliothek umgibt , und wie können Sie dazu beitragen, dass diese besser für sie fließen?

Möchten sie mehr Prägnanz? Möchten sie, dass es sehr klug ist, Standardwerte für Parameter zu ermitteln, damit sie nicht viel (oder nichts) angeben müssen und es richtig errät? Können Sie die für Ihre Bibliothek erforderlichen Setup- und Bereinigungsaufgaben automatisieren, damit sie diese Schritte nicht vergessen können? Was können Sie noch für sie tun?

Was Sie wahrscheinlich tun müssen, ist, es auf vier oder fünf verschiedene Arten zu codieren, dann Ihren Consumer-Hut aufzusetzen und Code zu schreiben, der alle fünf verwendet, und zu sehen, was sich besser anfühlt. Wenn Sie das nicht für Ihre gesamte Bibliothek tun können, dann tun Sie es für eine Teilmenge. Und Sie müssen Ihrer Liste auch einige zusätzliche Alternativen hinzufügen - wie wäre es mit einer fließenden Benutzeroberfläche oder einem funktionaleren Ansatz oder benannten Parametern oder etwas, das auf DynamicObject basiert, damit Sie aussagekräftige "Pseudomethoden" erstellen können, die ihnen helfen aus?

Warum ist jQuery gerade jetzt der König? Da Resig und das Team diesen Prozess befolgten, stießen sie auf ein syntaktisches Prinzip, das die Menge an JS-Code, die für die Arbeit mit dom und Ereignissen erforderlich ist, unglaublich reduzierte. Dieses syntaktische Prinzip war ihnen oder irgendjemand anderem nicht klar, als sie begannen. Sie haben es gefunden.

Als Programmierer ist das Ihre höchste Berufung. Sie tappen im Dunkeln herum, bis Sie es finden. Wenn du das tust, wirst du es wissen. Und Sie geben Ihren Benutzern einen enormen Produktivitätssprung. Und genau darum geht es beim Design (im Softwarebereich).

Charlie Flowers
quelle
1
Dies impliziert, dass es keine Unterschiede zwischen Best Practices und anderen Praktiken gibt, außer wie es sich anfühlt. Auf diese Weise werden Sie mit nicht zu pflegenden Schlammbällen fertig - denn für viele Entwickler "fühlt" sich das Ausstrecken über Klassengrenzen hinweg einfach wunderbar an.
Amy Blankenship
@Amy Blankenship, ich würde unzweifelhaft sagen, dass es keinen "besten Weg" gibt, die Entscheidungen zu treffen, nach denen das OP fragt. Es kommt auf eine Million Dinge an und es gibt eine Million Freiheitsgrade. Allerdings glaube ich gibt es einen Platz für „Best Practices“ , und das ist in einem Team - Umgebung, wo bestimmte Entscheidungen sind bereits gemacht worden, und wir müssen den Rest des Teams mit den früheren Entscheidungen konsistent bleiben. Mit anderen Worten, in einem bestimmten Kontext kann es Gründe geben, bestimmte Dinge als "Best Practices" zu bezeichnen. Das OP hat jedoch keinen Kontext angegeben. Er baut etwas und ...
Charlie Flowers
... er steht vor all diesen möglichen Entscheidungen. Es gibt keine "richtige Antwort" auf diese Entscheidungen. Es wird von den Zielen und den Schmerzpunkten des Systems gesteuert. Ich garantiere Ihnen, dass Haskell-Programmierer nicht der Meinung sind, dass alle Methoden Instanzmethoden sein sollten. Und Linux-Kernel-Programmierer halten es überhaupt nicht für wichtig, Dinge für TDD zugänglich zu machen. Und C ++ - Spieleprogrammierer bündeln ihre Daten oft lieber in einer engen Datenstruktur im Speicher, als alles in Objekte zu kapseln. Jedes "Best Practice" ist in einem bestimmten Kontext nur ein "Best Practice" und in einem anderen Kontext ein Anti-Pattern.
Charlie Flowers
@AmyBlankenship Noch eine Sache: Ich bin nicht einverstanden, dass es sich "einfach wunderbar anfühlt, wenn man über Klassengrenzen hinweg greift". Es kommt zu nicht zu wartenden Schlammballen, die sich schrecklich anfühlen . Ich denke, Sie versuchen das Problem zu lösen, dass einige Arbeiter schlampig / unmotiviert / sehr unerfahren sind. In diesem Fall sollte jemand, der vorsichtig, motiviert und erfahren ist, die wichtigsten Entscheidungen treffen und sie als "Best Practices" bezeichnen. Aber die Person, die diese "Best Practices" auswählt, trifft immer noch Entscheidungen basierend auf dem, was sich "richtig anfühlt", und es gibt keine festgelegten richtigen Antworten. Sie kontrollieren nur, wer die Entscheidungen trifft.
Charlie Flowers
Ich habe mit mehreren Programmierern zusammengearbeitet, die sich selbst als Senior-Ebene betrachteten und von der Geschäftsleitung so gesehen wurden. Sie waren der festen Überzeugung, dass Statik und Singletons die absolut richtige Art sind, mit Kommunikationsproblemen umzugehen. Der statische Teil dieser Frage wäre nicht einmal gestellt worden, wenn es sich für Entwickler "falsch" anfühlt, über Klassengrenzen hinweg zu greifen, und die Antwort, die die statische Alternative befürwortet, würde keine Gegenstimmen erhalten.
Amy Blankenship
3

Die zweite Option ist besser, da es für die Benutzer einfacher ist (auch wenn es nur Sie sind), viel einfacher.

Zum Testen von Einheiten würde ich nur die Schnittstelle testen, nicht die Interna, wenn Sie die Interna wirklich von privat zu geschützt verschieben möchten.


quelle
2
Sie können Ihr Methodenpaket auch für Unit-Tests als privat festlegen (Standard), wenn sich Ihre Testfälle im selben Paket befinden.
Guter Punkt - das ist besser.
2

Konzentrieren Sie sich auf die Perspektive des Kunden.

IReader reader = new XmlReader.readXml(url);  // or injection, or factory or ...
Document document = reader.read();

Statische Methoden neigen dazu, zukünftige Entwicklungen einzuschränken. Unser Kunde arbeitet mit einer Schnittstelle, die möglicherweise von vielen verschiedenen Implementierungen bereitgestellt wird.

Das Hauptproblem bei Ihrer Open / Read-Sprache ist, dass der Client die Reihenfolge kennen muss, in der Methoden aufgerufen werden, wenn er nur einfache Aufgaben erledigen möchte. Offensichtlich hier, aber in einer größeren Klasse ist es alles andere als offensichtlich.

Die zu testende Hauptmethode ist read (). Interne Methoden können für Testprogramme sichtbar gemacht werden, indem sie weder öffentlich noch privat gemacht werden und Tests in dasselbe Paket gestellt werden. Die Tests können weiterhin vom freigegebenen Code getrennt werden.

djna
quelle
Sind Methoden mit Standardsichtbarkeit weiterhin sichtbar, wenn sich die Testsuite in einem anderen Projekt befindet?
Siamii
Java kennt sich mit Projekten nicht aus. Projekte sind ein IDE-Konstrukt. Der Compiler und die JVM sehen sich die Pakete an, in denen sich die getestete und die Testerklasse befinden. Standardmäßige Sichtbarkeit ist zulässig. In Eclipse verwende ich ein einzelnes Projekt mit zwei verschiedenen Quellverzeichnissen. Ich habe es gerade mit zwei Projekten versucht, es funktioniert.
2

statische vs instanz methode

In der Praxis werden Sie feststellen, dass statische Methoden im Allgemeinen auf Dienstprogrammklassen beschränkt sind und Ihre Domänenobjekte, Manager, Controller oder DAOs nicht überladen sollten. Statische Methoden sind am nützlichsten, wenn alle erforderlichen Referenzen in angemessener Weise als Parameter übergeben werden können und einige Funktionen enthalten, die in vielen Klassen wiederverwendet werden können. Wenn Sie feststellen, dass Sie eine statische Methode als Problemumgehung für einen Verweis auf ein Instanzobjekt verwenden, fragen Sie sich, warum Sie nicht stattdessen nur diesen Verweis haben.

Methode ohne Parameter oder Rückgabewert vs. Methode mit Parametern und Rückgabewert

Wenn Sie keine Parameter für die Methode benötigen, fügen Sie diese nicht hinzu. Gleiches gilt für den Rückgabewert. Wenn Sie dies berücksichtigen, vereinfachen Sie Ihren Code und stellen sicher, dass Sie nicht für eine Vielzahl von Szenarien programmieren, die niemals eintreten.

Überlappende oder unterschiedliche Methodenfunktionen

Es ist eine gute Idee, Überschneidungen zu vermeiden. Manchmal kann es schwierig sein, aber wenn eine Änderung der Logik erforderlich ist, ist es viel einfacher, eine wiederverwendete Methode zu ändern, als eine ganze Reihe von Methoden mit ähnlichen Funktionen zu ändern

private vs public Methode

Üblicherweise sollten Getter, Setter und Konstrukteure öffentlich sein. Alles andere möchten Sie versuchen, privat zu bleiben, es sei denn, es gibt einen Fall, in dem eine andere Klasse es ausführen muss. Durch Beibehalten der Standardeinstellung "privat" können Sie die Kapselung beibehalten . Gleiches gilt für Felder, gewöhne dich standardmäßig an private

Erich
quelle
2

1. Statisch vs. Instanz

Ich denke, es gibt sehr klare Richtlinien darüber, was gutes OO-Design ist und was nicht. Das Problem ist, dass die Blogosphäre es schwierig macht, die Guten von den Schlechten und Hässlichen zu trennen. Sie können finden eine Art Referenz sogar die schlechteste Praxis unterstützen Sie denken können.

Und die schlimmste Praxis, die ich mir vorstellen kann, ist Global State, einschließlich der von Ihnen erwähnten Statik und des beliebtesten Singleton aller. Einige Auszüge aus dem klassischen Artikel von Misko Hevery zu diesem Thema .

Um die Abhängigkeiten wirklich zu verstehen, müssen Entwickler jede Codezeile lesen. Es verursacht Spooky Action in einiger Entfernung: Wenn Testsuiten ausgeführt werden, kann ein globaler Status, der in einem Test geändert wurde, dazu führen, dass ein nachfolgender oder paralleler Test unerwartet fehlschlägt. Unterbrechen Sie die statische Abhängigkeit mit der manuellen oder Guice-Abhängigkeitsinjektion.

Spooky Action in einiger Entfernung ist, wenn wir eine Sache ausführen, von der wir glauben, dass sie isoliert ist (da wir keine Referenzen übergeben haben), aber unerwartete Interaktionen und Zustandsänderungen an entfernten Orten des Systems auftreten, von denen wir dem Objekt nichts erzählt haben. Dies kann nur über den globalen Status geschehen.

Möglicherweise haben Sie das vorher nicht so gesehen, aber wenn Sie den statischen Status verwenden, erstellen Sie geheime Kommunikationskanäle und machen sie in der API nicht klar. Spooky Action at a Distance zwingt Entwickler, jede Codezeile zu lesen, um die möglichen Interaktionen zu verstehen, verringert die Entwicklerproduktivität und verwirrt neue Teammitglieder.

Daraus ergibt sich, dass Sie keine statischen Verweise auf Objekte bereitstellen sollten, die einen gespeicherten Status haben. Der einzige Ort, an dem ich Statik verwende, ist für aufgezählte Konstanten, und ich habe sogar darüber Bedenken.

2. Methoden mit Eingabeparametern und Rückgabewerten im Vergleich zu Methoden ohne

Sie müssen sich darüber im Klaren sein, dass Methoden ohne Eingabe- und Ausgabeparameter so gut wie garantiert in einem intern gespeicherten Zustand arbeiten (was tun sie sonst?). Es gibt ganze Sprachen , die auf der Idee aufbauen, einen gespeicherten Zustand zu vermeiden.

Jedes Mal, wenn Sie den Status gespeichert haben, haben Sie die Möglichkeit von Nebenwirkungen, stellen Sie also sicher, dass Sie ihn immer mit Bedacht anwenden. Dies bedeutet, dass Sie Funktionen mit definierten Ein- und / oder Ausgängen bevorzugen sollten .

Tatsächlich sind Funktionen mit definierten Ein- und Ausgängen viel einfacher zu testen. Sie müssen hier keine Funktion ausführen und dort nachsehen, um zu sehen, was passiert ist, und Sie müssen keine Eigenschaft irgendwo festlegen bevor Sie die zu testende Funktion ausführen.

Sie können diese Art von Funktion auch sicher als Statik verwenden. Das würde ich aber nicht tun, denn wenn ich später irgendwo eine etwas andere Implementierung dieser Funktion verwenden wollte, anstatt mit der neuen Implementierung eine andere Instanz bereitzustellen, kann ich die Funktionalität nicht ersetzen.

3. Überlappung vs.

Ich verstehe die Frage nicht. Was wäre der Vorteil bei 2 überlappenden Methoden?

4. Privat gegen öffentlich

Belichten Sie nichts, was Sie nicht belichten müssen. Allerdings bin ich auch kein großer Fan von Privat. Ich bin kein C # -Entwickler, sondern ein ActionScript-Entwickler. Ich habe viel Zeit mit dem Flex Framework-Code von Adobe verbracht, der um 2007 geschrieben wurde. Und sie haben einige wirklich schlechte Entscheidungen getroffen, was das Privatisieren zum Albtraum macht, wenn sie versuchen, ihre Klassen zu erweitern.

Wenn Sie also nicht glauben, dass Sie ein besserer Architekt sind als die Adobe-Entwickler um 2007 (Ihrer Frage nach haben Sie wahrscheinlich noch ein paar Jahre Zeit, um diesen Anspruch geltend zu machen), möchten Sie wahrscheinlich nur standardmäßig geschützt werden .


Es gibt einige Probleme mit Ihren Codebeispielen, die bedeuten, dass sie nicht gut strukturiert sind. Daher ist es nicht möglich, A oder B auszuwählen.

Zum einen sollten Sie Ihre Objekterstellung wahrscheinlich von ihrer Verwendung trennen . Normalerweise haben Sie also kein new XMLReader()Recht in der Nähe des Verwendungsorts.

Wie @djna sagt, sollten Sie auch die in Ihrem XML Reader verwendeten Methoden einkapseln, damit Ihre API (Instanzbeispiel) vereinfacht werden kann:

_document Document = reader.read(info);

Ich weiß nicht, wie C # funktioniert, aber da ich mit einer Reihe von Webtechnologien gearbeitet habe, würde ich den Verdacht haben, dass Sie ein XML-Dokument nicht immer sofort zurückgeben können (außer vielleicht als Versprechen oder zukünftiger Typ) Objekt), aber ich kann Ihnen keinen Rat geben, wie mit einer asynchronen Last in C # umgegangen werden soll.

Beachten Sie, dass Sie mit diesem Ansatz mehrere Implementierungen erstellen können, die einen Parameter verwenden, der angibt, wo / was gelesen und ein XML-Objekt zurückgegeben werden soll, und diese basierend auf Ihren Projektanforderungen austauschen können. Beispielsweise können Sie direkt aus einer Datenbank, einem lokalen Geschäft oder, wie in Ihrem ursprünglichen Beispiel, aus einer URL lesen. Sie können das nicht tun, wenn Sie eine statische Methode verwenden.

Amy Blankenship
quelle
1

Ich werde Ihre Frage nicht beantworten, aber ich denke, dass ein Problem durch die von Ihnen verwendeten Begriffe entsteht. Z.B

XmlReader.read => twice "read"

Ich denke, Sie brauchen eine XML, also erstelle ich eine Objekt-XML, die aus einem Texttyp erstellt werden kann (ich weiß nicht, C # ... in Java heißt es String). Z.B

class XML {
    XML(String text) { [...] }
}

Sie können es testen und es ist klar. Wenn Sie dann eine Factory benötigen, können Sie eine Factory-Methode hinzufügen (und diese kann wie Ihr zweites Beispiel statisch sein). Z.B

class XML {
    XML(String text) { [...] }

    static XML fromUrl(url) { [...] }

}
sixro
quelle
0

Sie können einige einfache Regeln befolgen. Es hilft, wenn Sie die Gründe für die Regeln verstehen.

statische vs instanz methode

Bei Methoden müssen Sie diese Entscheidung nicht bewusst treffen. Wenn es so aussieht, als ob Ihre Methode keine Feldmitglieder verwendet (Ihr bevorzugter Analysator sollte es Ihnen mitteilen), sollten Sie das statische Schlüsselwort hinzufügen.

Methode ohne Parameter oder Rückgabewert vs. Methode mit Parametern und Rückgabewert

Die zweite Option ist aufgrund des Umfangs besser. Sie sollten Ihr Zielfernrohr immer eng halten. Wenn Sie nach Dingen suchen, die Sie brauchen, sollten Sie Eingaben haben, lokal daran arbeiten und ein Ergebnis zurückgeben. Ihre erste Option ist aus dem gleichen Grund schlecht. Globale Variablen sind in der Regel schlecht: Sie sind nur für einen Teil Ihres Codes von Bedeutung, aber sie sind überall sichtbar (und damit Rauschen) und können von überall manipuliert werden. Dies macht es schwierig, ein vollständiges Bild Ihrer Logik zu erhalten.

Überlappende oder unterschiedliche Methodenfunktionen

Ich denke nicht, dass dies ein Problem ist. Methoden, die andere Methoden aufrufen, sind in Ordnung, wenn dadurch Aufgaben in kleinere, deutlich funktionierende Teile zerlegt werden.

private vs public Methode

Machen Sie alles privat, es sei denn, Sie müssen es öffentlich machen. Der Benutzer Ihrer Klasse kann auf den Lärm verzichten, er will nur das sehen, was ihm wichtig ist.

Martin Maat
quelle