Hier ist ein Problem, auf das ich häufig stoße: Es soll ein Webshop-Projekt mit einer Produktklasse geben. Ich möchte eine Funktion hinzufügen, mit der Benutzer Bewertungen zu einem Produkt veröffentlichen können. Ich habe also eine Review-Klasse, die auf ein Produkt verweist. Jetzt brauche ich eine Methode, die alle Bewertungen zu einem Produkt auflistet. Es gibt zwei Möglichkeiten:
(EIN)
public class Product {
...
public Collection<Review> getReviews() {...}
}
(B)
public class Review {
...
static public Collection<Review> forProduct( Product product ) {...}
}
Wenn ich mir den Code anschaue, würde ich (A) wählen: Er ist nicht statisch und benötigt keinen Parameter. Ich spüre jedoch, dass (A) das Prinzip der Einzelverantwortung (Single Responsibility Principle, SRP) und das Open-Closed-Prinzip (OCP) verletzt, während (B) nicht:
(SRP) Wenn ich die Art und Weise ändern möchte, wie Bewertungen für ein Produkt gesammelt werden, muss ich die Produktklasse ändern. Es sollte jedoch nur einen Grund geben, die Produktklasse zu ändern. Und das sind sicherlich nicht die Bewertungen. Wenn ich alle Funktionen packe, die etwas mit Produkten in Product zu tun haben, wird es bald klappern.
(OCP) Ich muss die Produktklasse ändern, um sie mit dieser Funktion zu erweitern. Ich denke, dies verstößt gegen den Teil des Prinzips, der für Veränderungen geschlossen ist. Bevor ich die Anfrage des Kunden zur Implementierung der Überprüfungen erhielt, betrachtete ich das Produkt als fertig und "schloss" es.
Was ist wichtiger: Befolgen Sie die SOLID-Prinzipien oder haben Sie eine einfachere Oberfläche?
Oder mache ich hier überhaupt etwas falsch?
Ergebnis
Wow, danke für all deine tollen Antworten! Es ist schwer, eine als offizielle Antwort auszuwählen.
Lassen Sie mich die Hauptargumente aus den Antworten zusammenfassen:
- Pro (A): OCP ist kein Gesetz und die Lesbarkeit des Codes ist ebenfalls wichtig.
- pro (A): Die Entitätsbeziehung sollte navigierbar sein. Beide Klassen kennen möglicherweise diese Beziehung.
- pro (A) + (B): Beides tun und in (A) an (B) delegieren, damit das Produkt weniger wahrscheinlich erneut geändert wird.
- pro (C): Finder-Methoden in die dritte Klasse (Service) einordnen, wo sie nicht statisch sind.
- contra (B): Verhindert das Verspotten in Tests.
Ein paar zusätzliche Dinge, die meine Hochschulen bei der Arbeit beigetragen haben:
- pro (B): Unser ORM-Framework kann automatisch den Code für (B) generieren.
- pro (A): Aus technischen Gründen unseres ORM-Frameworks muss in einigen Fällen die "geschlossene" Einheit geändert werden, unabhängig davon, wohin der Finder geht. Ich werde mich also sowieso nicht immer an SOLID halten können.
- contra (C): zu viel Aufhebens ;-)
Fazit
Ich verwende beide (A) + (B) mit Delegation für mein aktuelles Projekt. In einer serviceorientierten Umgebung werde ich jedoch mit (C) gehen.
Assert(5 = Math.Abs(-5));
Abs()
ist nicht das Problem, etwas zu testen, das davon abhängt. Sie haben keine Naht zum Isolieren des abhängigen Code-Under-Test (CUT), um ein Modell zu verwenden. Dies bedeutet, dass Sie es nicht als atomare Einheit testen können und alle Ihre Tests zu Integrationstests werden, die die Logik der Testeinheit testen. Ein Fehler in einem Test kann in CUT oder in Abs()
(oder seinem abhängigen Code) liegen und die Diagnosevorteile von Komponententests aufheben.