Design: Objektmethode vs. Methode einer separaten Klasse, die Objekt als Parameter verwendet?

14

Ist es zum Beispiel besser, Folgendes zu tun:

Pdf pdf = new Pdf();
pdf.Print();

oder:

Pdf pdf = new Pdf();
PdfPrinter printer = new PdfPrinter();
printer.Print(pdf);

Ein anderes Beispiel:

Country m = new Country("Mexico");
double ratio = m.GetDebtToGDPRatio();

oder:

Country m = new Country("Mexico");
Country us = new Country("US");
DebtStatistics ds = new DebtStatistics();
double usRatio = ds.GetDebtToGDPRatio(us);
double mRatio = ds.GetDebtToGDPRatio(m);    

Mein Anliegen im letzten Beispiel ist, dass es möglicherweise endlose Statistiken gibt (aber sagen wir nur 10), die Sie über ein Land wissen möchten. gehören sie alle zum landobjekt?

z.B

Country m = new Country("Mexico");
double ratio = m.GetGDPToMedianIncomeRatio();

Dies sind einfache Verhältnisse, aber nehmen wir an, dass die Statistiken kompliziert genug sind, um eine Methode zu rechtfertigen.

Wo ist diese Grenze zwischen Operationen, die einem Objekt eigen sind, und Operationen, die für ein Objekt ausgeführt werden können, aber nicht Teil davon sind?

Benutzer
quelle

Antworten:

16

Schauen wir uns diese PDF-Beispiele als Ausgangspunkt an.

http://en.wikipedia.org/wiki/Single_responsibility_principle

Das Prinzip der Einzelverantwortung sieht vor, dass ein Objekt nur ein Ziel haben sollte. Behalte dies im Kopf.

http://en.wikipedia.org/wiki/Separation_of_concerns

Das Prinzip der Trennung von Interessen besagt, dass Klassen keine überlappenden Funktionen haben sollten.

Wenn Sie sich diese beiden ansehen, schlagen sie vor, dass Logik nur dann in eine Klasse aufgenommen werden sollte, wenn sie sinnvoll ist, und nur dann, wenn diese Klasse dafür verantwortlich ist.

In Ihrem PDF-Beispiel lautet die Frage: Wer ist für den Druck verantwortlich? Was macht Sinn?

Erstes Code-Snippet:

Pdf pdf = new Pdf();
pdf.Print();

Das ist nicht gut. Ein PDF-Dokument wird nicht gedruckt. Es wird gedruckt von ... ta da! .. einem Drucker. Ihr zweites Code-Snippet ist also viel besser:

Pdf pdf = new Pdf();
PdfPrinter printer = new PdfPrinter();
printer.Print(pdf);

Das macht Sinn. Ein PDF-Drucker druckt ein PDF-Dokument. Besser noch, ein Drucker sollte kein PDF-Drucker oder ein Fotodrucker sein. Es sollte nur ein Drucker sein, der in der Lage ist, die an ihn gesendeten Daten so gut wie möglich zu drucken.

Pdf pdf = new Pdf();
Printer printer = new Printer();
printer.Print(pdf);

So ist das einfach. Setzen Sie Methoden dort ein, wo sie sinnvoll sind. Offensichtlich ist es nicht immer so einfach. Nehmen Sie zum Beispiel Ihre Länderstatistik:

Country m = new Country("Mexico");
double ratio = m.GetDebtToGDPRatio();

Ihre Sorge ist , dass es sein könnte n Anzahl von Statistiken, und dass sie nicht in einem Land Klasse sein. Das ist wahr. Wenn Ihr Modell jedoch nur diese bestimmte Statistik benötigt, ist dieses Modellierungsbeispiel möglicherweise in Ordnung.

In diesem Fall können Sie logischerweise sagen, dass ein Land in der Lage sein sollte, seine eigenen Statistiken zu erstellen, die für Ihr Modell und die vorliegenden Anforderungen spezifisch sind.

Und darin liegt die Sache: Was sind Ihre Anforderungen? Ihre Anforderungen bestimmen die Art und Weise, wie Sie die Welt modellieren, den Kontext, in dem diese Anforderungen erfüllt werden sollen.

Wenn Sie tatsächlich eine Vielzahl / variable Anzahl von Statistiken haben, ist Ihr zweites Beispiel sinnvoller:

Country m = new Country("Mexico");
DebtStatistics ds = new DebtStatistics();
double usRatio = ds.GetDebtToGDPRatio(m);

Besser noch, haben Sie eine abstrakte Superklasse oder Schnittstelle namens Statistik, die ein Land als Parameter verwendet:

interface StatisticsCalculator // or a pure abstract class if doing C++
{
   double getStatistics(Country country); // or a pure virtual function if in C++
}

Klasse DebtToGDPRatioStatisticsCalculator implementiert StatisticsCalculator ....

Klasse InfantMortalityStatisticsCalculator implementiert StatisticsCalculator ...

Und so weiter und so fort. Was zu Folgendem führt: Verallgemeinerung, Delegation, Abstraktion. Die statistische Erfassung wird an bestimmte Instanzen delegiert , die eine bestimmte Abstraktion verallgemeinern (eine Statistiksammlungs-API).

Ich weiß nicht, ob dies Ihre Frage zu 100% beantwortet. Schließlich haben wir keine unfehlbaren Modelle, die auf unantastbaren Gesetzen beruhen (wie es die EE-Leute tun). Alles, was Sie tun können, ist Dinge zu formulieren, wenn sie sinnvoll sind. Und das ist eine technische Entscheidung, die Sie treffen müssen. Das Beste, was Sie tun können, ist, sich mit den OO-Prinzipien (und den Prinzipien der guten Softwaremodellierung im Allgemeinen) wirklich vertraut zu machen.

luis.espinal
quelle
1
+1 für die StatisticsCalculator-Schnittstelle (und nachfolgende Verwendung des Strategiemusters). Und die gründlich durchdachte Antwort
edwardsmatt
3
Derzeit ist nicht genügend Zeit vorhanden, um dies gründlich zu dekonstruieren, es muss jedoch darauf hingewiesen werden, dass die Printer-Klasse im Laufe der Zeit eine God-Klasse wird, die eng an alle Arten von Dokumentenklassen gekoppelt ist. Pdf.Print wäre vorzuziehen - aber alles hängt davon ab, wie Sie die "Einzelverantwortung" definieren ;-)
Steven A. Lowe
@Steve - was Sie vorschlagen, ist eine schreckliche Idee (mit Pdf implementieren print ()). Es spiegelt nicht wider, wie das Drucken im wirklichen Leben implementiert wird. Jedes Betriebssystem und jede Druck-API, die mir bekannt ist, bietet eine Abstraktion für Printer. Sehen Sie sich die Druckerliste auf Ihrem XP / Vista-Computer an (oder unter / var / spool oder in * nix äquivalent). Jede Anwendung serialisiert ein Dokumentobjekt auf einem ihrer Drucker. Es gibt keinen Word- oder Textdrucker oder PDF-Drucker. Es gibt nur Drucker spezifisch an die Druckvorrichtung und nicht spezifisch für den Dokumenttyp.
Luis.espinal
2
+1 Es gefällt mir. Ich überlege, was du gesagt hast. @Steve und Luis: Ich denke, der fehlende Teil der Debatte über Gott-Objekte ist, dass ein generisches Druckerobjekt einige Standardformate wie ASCII oder Bitmap (obwohl PDF) akzeptieren sollte ist wahrscheinlich auch vernünftig) und es sollte in der Verantwortung einer dritten Klasse liegen, einen bestimmten Dokumenttyp (z. B. ein MS-Word-Dokument) in eines dieser Standardformate zu konvertieren.
Benutzer
2
Es scheint mir, dass ein PDF sich vielleicht selbst auf einer Canvas-Oberfläche oder in einem Image-Objekt rendern kann, das dann von einem Printer-Objekt verarbeitet werden kann.
Winston Ewert
4

Ich denke, keiner ist definitiv besser als der andere. Die Verwendung von pdf.Print () ist enger, aber eine PdfPrinter-Klasse ist möglicherweise besser, wenn:

  • Sie müssen Instanzen der Drucker verwalten
  • Es gibt eine Vielzahl von Optionen und Aktionen, die die Komplexität von pdf.Print (...) verringern (z. B. Druckabbruch, zusätzliche Formatierung usw.).

Ich würde sonst nicht hängen bleiben.

Kevin Hsu
quelle
eine gute, praktische Antwort; Die Zeit wird zeigen, wie sich dies entwickeln muss
Steven A. Lowe
1
Der kurze Vorschlag ist, bei der Anwendung von SRP sowohl Logik als auch Daten zu betrachten , um zu entscheiden, ob wir es bereuen werden, sie nicht früher entkoppelt zu haben. Das Problem beim Speichern von Einstellungen pro Drucker in der PdfKlasse besteht darin, dass sie nicht zusammen Pdfgespeichert werden sollen - sie werden in einer Datei gespeichert, die Einstellungen pro Drucker sollten jedoch mit dem Benutzer- / Maschinenprofil gespeichert werden.
rwong