Verstößt "Komposition über Vererbung" gegen das "Trockenprinzip"?

36

Angenommen, ich habe eine Klasse für andere Klassen, die erweitert werden sollen:

public class LoginPage {
    public String userId;
    public String session;
    public boolean checkSessionValid() {
    }
}

und einige Unterklassen:

public class HomePage extends LoginPage {

}

public class EditInfoPage extends LoginPage {

}

Tatsächlich hat die Unterklasse keine Methoden zum Überschreiben. Außerdem würde ich nicht generisch auf die HomePage zugreifen, dh: Ich würde nicht Folgendes tun:

for (int i = 0; i < loginPages.length; i++) {
    loginPages[i].doSomething();
}

Ich möchte nur die Anmeldeseite wiederverwenden. Laut https://stackoverflow.com/a/53354 sollte ich hier jedoch die Komposition bevorzugen, da ich die Schnittstelle LoginPage nicht benötige. Daher verwende ich hier keine Vererbung:

public class HomePage {
    public LoginPage loginPage;
}

public class EditInfoPage {
    public LoginPage loginPage;
}

aber das problem kommt hier, bei der neuen version, der code:

public LoginPage loginPage;

dupliziert, wenn eine neue Klasse hinzugefügt wird. Und wenn LoginPage Setter und Getter benötigt, müssen mehr Codes kopiert werden:

public LoginPage loginPage;

private LoginPage getLoginPage() {
    return this.loginPage;
}
private void setLoginPage(LoginPage loginPage) {
    this.loginPage = loginPage;
}

Meine Frage lautet also: Verstößt "Komposition über Vererbung" gegen das "Trockenprinzip"?

ocomfd
quelle
13
Manchmal braucht man weder Vererbung noch Komposition, das heißt, manchmal erledigt eine Klasse mit ein paar Objekten die Arbeit.
Erik Eidt
23
Warum sollten alle Ihre Seiten die Anmeldeseite sein? Vielleicht haben Sie nur eine Anmeldeseite.
Mr Cochese
29
Aber mit der Vererbung haben Sie extends LoginPageüberall ein Duplikat . CHECK MATE!
el.pescado
2
Wenn Sie das Problem im letzten Snippet häufig haben, können Sie Getter und Setter überbeanspruchen. Sie neigen dazu, die Kapselung zu verletzen.
Brian McCutchon
4
Machen Sie Ihre Seite zu LoginPageeinem Dekorateur. Keine Vervielfältigung mehr, nur ein einfaches page = new LoginPage(new EditInfoPage())und Sie sind fertig. Oder Sie verwenden das Open-Closed-Prinzip, um das Authentifizierungsmodul zu erstellen, das dynamisch zu jeder Seite hinzugefügt werden kann. Es gibt viele Möglichkeiten, mit Code-Duplikaten umzugehen, vor allem, wenn eine neue Abstraktion gefunden werden soll. LoginPageWahrscheinlich handelt es sich um einen schlechten Namen. Wenn dies nicht der Fall ist, möchten Sie sicherstellen, dass der Benutzer beim Browsen auf dieser Seite authentifiziert wird und zur LoginPageentsprechenden Fehlermeldung umgeleitet wird.
Polygnome

Antworten:

46

Warten Sie, Sie sind besorgt, dass es sich wiederholt

public LoginPage loginPage;

an zwei Stellen verletzt DRY? Nach dieser Logik

int x;

kann jetzt immer nur in einem Objekt in der gesamten Codebasis existieren. Bleh.

TROCKEN ist eine gute Sache zu beachten, aber komm schon. Außerdem

... extends LoginPage

wird immer in Ihrer Alternative dupliziert, so dass auch anal über DRY keinen Sinn ergibt.

Gültige DRY-Bedenken konzentrieren sich in der Regel auf dasselbe Verhalten, das an mehreren Stellen erforderlich ist, und werden an mehreren Stellen definiert, sodass eine Änderung dieses Verhaltens an mehreren Stellen erforderlich ist. Treffen Sie Entscheidungen an einem Ort und Sie müssen sie nur an einem Ort ändern. Dies bedeutet nicht, dass immer nur ein Objekt einen Verweis auf Ihre LoginPage enthalten kann.

DRY sollte nicht blind verfolgt werden. Wenn Sie duplizieren, weil das Kopieren und Einfügen einfacher ist, als sich eine gute Methode oder einen guten Klassennamen auszudenken, liegen Sie wahrscheinlich falsch.

Wenn Sie jedoch den gleichen Code an einer anderen Stelle ablegen möchten, da diese andere Stelle einer anderen Verantwortung unterliegt und wahrscheinlich unabhängig geändert werden muss, ist es wahrscheinlich ratsam, die Durchsetzung von DRY zu lockern und dass dieses identische Verhalten eine andere Identität aufweist . Es ist die gleiche Art des Denkens, die darin besteht, magische Zahlen zu verbieten.

Bei DRY geht es nicht nur darum, wie der Code aussieht. Es geht darum, die Details einer Idee nicht mit sinnlosen Wiederholungen zu verbreiten und die Betreuer dazu zu zwingen, Dinge mit sinnlosen Wiederholungen zu reparieren. Wenn du versuchst dir selbst zu sagen, dass die gedankenlose Wiederholung nur deine Konvention ist, dass die Dinge auf eine wirklich schlechte Art und Weise geleitet werden.

Was ich denke, dass Sie wirklich versuchen, sich zu beschweren, wird Kesselschildcode genannt. Ja, die Verwendung von Komposition anstelle von Vererbung erfordert Code für das Textfeld. Nichts wird kostenlos verfügbar gemacht, Sie müssen Code schreiben, der es verfügbar macht. Mit diesem Boilerplate kommt die Flexibilität des Zustands, die Fähigkeit, die exponierte Schnittstelle einzugrenzen, den Dingen unterschiedliche Namen zu geben, die für ihre Abstraktionsebene geeignet sind, eine gute indirekte Funktion, und Sie verwenden, woraus Sie von außen zusammengesetzt sind, nicht die innen, so dass Sie es normale Schnittstelle konfrontiert sind.

Aber ja, es ist eine Menge zusätzlicher Tastatureingaben. Solange ich das Jojo-Problem des Auf- und Abspringens eines Vererbungsstapels verhindern kann, während ich den Code lese, lohnt es sich.

Jetzt ist es nicht so, dass ich es ablehne, jemals Vererbung zu verwenden. Eine meiner Lieblingsanwendungen ist es, Ausnahmen neue Namen zu geben:

public class MyLoginPageWasNull extends NullPointerException{}
kandierte_orange
quelle
int x;kann nicht mehr als null Mal in einer Codebasis existieren
K. Alan Bates
@K.AlanBates Entschuldigung ?
candied_orange
... Ich wusste, dass Sie mit der Point-Klasse zurückkehren würden lol. Die Point-Klasse interessiert niemanden. Wenn ich zu Ihrer Codebasis gehe und sehe, werde int a; int b; int x;ich darauf drängen, dass Sie entfernt werden.
K. Alan Bates
@ K.AlanBates Wow. Es ist traurig, dass du denkst, dass diese Art von Verhalten dich zu einem guten Programmierer machen würde. Der beste Programmierer ist nicht derjenige, der die meisten Dinge über die anderen herausfindet, über die man meckern kann. Es ist derjenige, der den Rest besser macht.
candied_orange
Die Verwendung bedeutungsloser Namen ist ein Nichtstarter und macht den Entwickler dieses Codes wahrscheinlich eher zu einer Verbindlichkeit als zu einem Aktivposten.
K. Alan Bates
127

Ein häufiges Missverständnis mit dem DRY-Prinzip besteht darin, dass es in irgendeiner Weise damit zusammenhängt, dass Codezeilen nicht wiederholt werden. Das DRY - Prinzip ist „Jedes Stück Wissen eine einzige, eindeutige, verbindliche Darstellung innerhalb eines Systems haben muß“ . Es geht um Wissen, nicht um Code.

LoginPageweiß, wie man die Seite zum Einloggen zeichnet. Wenn man EditInfoPageweiß, wie man das macht, wäre das ein Verstoß. Einschließlich LoginPageüber Zusammensetzung ist nicht eine Verletzung des DRY - Prinzip in keiner Weise.

Das DRY-Prinzip ist vielleicht das am häufigsten missbrauchte Prinzip in der Softwareentwicklung, und es sollte immer nicht als Prinzip betrachtet werden, Code nicht zu duplizieren, sondern als Prinzip, abstraktes Domänenwissen nicht zu duplizieren. Tatsächlich duplizieren Sie in vielen Fällen Code , wenn Sie DRY richtig anwenden , und dies ist nicht unbedingt eine schlechte Sache.

wasatz
quelle
27
"Prinzip der einmaligen Verantwortung" wird viel häufiger missbraucht. "Wiederholen Sie sich nicht" könnte leicht als Nummer zwei
hereinkommen
28
"DRY" ist eine praktische Abkürzung für "Don't Repeat Yourself". Dies ist ein Schlagwort, aber nicht der Name des Prinzips. Der eigentliche Name des Prinzips lautet "Once And Only Once", was wiederum ein eingängiger Name ist für ein Prinzip, das ein paar Absätze braucht, um zu beschreiben. Das Wichtigste ist, die paar Absätze zu verstehen und die drei Buchstaben D, R und Y nicht auswendig zu lernen.
Jörg W Mittag
4
@ gnasher729 Weißt du, ich stimme dir tatsächlich zu, dass SRP wahrscheinlich viel häufiger missbraucht wird. Um fair zu sein, denke ich in vielen Fällen, dass sie oft zusammen missbraucht werden. Meine Theorie ist, dass Programmierern im Allgemeinen nicht vertraut werden sollte, um Akronyme mit "einfachen Namen" zu verwenden.
Wasatz
17
IMHO ist dies eine gute Antwort. Um ehrlich zu sein: Meiner Erfahrung nach wird die häufigste Form von DRY-Verstößen durch Copy-Paste-Programmierung und nur durch das Duplizieren von Code verursacht. Jemand sagte mir einmal: "Wann immer Sie Code kopieren und einfügen, überlegen Sie zweimal, ob der duplizierte Teil nicht in eine gemeinsame Funktion extrahiert werden kann." Das war meiner Meinung nach ein sehr guter Rat.
Doc Brown
9
Der Missbrauch von Copy-Paste ist sicherlich eine treibende Kraft für die Verletzung von DRY, aber das Verbot von Copy-Paste ist eine schlechte Anleitung für die Befolgung von DRY. Ich würde überhaupt keine Reue empfinden, wenn ich zwei Methoden mit identischem Code hätte, wenn diese Methoden unterschiedliche Kenntnisse repräsentieren. Sie haben zwei unterschiedliche Aufgaben. Sie haben heute einfach identischen Code. Sie sollten frei sein, sich unabhängig zu ändern. Ja, Wissen, kein Code. Gut gesagt. Ich habe eine der anderen Antworten geschrieben, aber ich werde mich dieser beugen. +1.
candied_orange
12

Kurze Antwort: Ja, bis zu einem gewissen Grad.

Auf den ersten Blick kann die Vererbung manchmal einige Codezeilen einsparen, da sie besagt, dass "meine wiederverwendende Klasse alle öffentlichen Methoden und Attribute 1: 1 enthält". Wenn eine Komponente also eine Liste von 10 Methoden enthält, müssen diese in der geerbten Klasse nicht im Code wiederholt werden. Wenn in einem Kompositionsszenario 9 dieser 10 Methoden durch eine Wiederverwendungskomponente öffentlich verfügbar gemacht werden sollen, müssen 9 delegierende Aufrufe notiert und der verbleibende ausgelassen werden, da führt kein Weg daran vorbei.

Warum ist das erträglich? Sehen Sie sich die Methoden an, die in einem Kompositionsszenario repliziert werden. Diese Methoden delegieren ausschließlich Aufrufe an die Schnittstelle der Komponente und enthalten daher keine echte Logik.

Der Kern des DRY-Prinzips besteht darin, zu vermeiden, dass zwei Stellen im Code dieselben logischen Regeln enthalten. Wenn sich diese logischen Regeln ändern, ist es im Nicht-DRY-Code einfach, eine dieser Stellen anzupassen und die andere zu vergessen. was einen Fehler einführt.

Da das Delegieren von Aufrufen jedoch keine Logik enthält, werden diese normalerweise nicht geändert und verursachen daher kein echtes Problem, wenn "die Komposition der Vererbung vorgezogen wird". Und selbst wenn sich die Schnittstelle der Komponente ändert, was zu formalen Änderungen bei allen Klassen führen kann, die die Komponente verwenden, teilt uns der Compiler in einer kompilierten Sprache mit, wenn wir vergessen haben, einen der Aufrufer zu ändern.

Ein Hinweis zu Ihrem Beispiel: Ich weiß nicht, wie Sie HomePageund Ihr EditInfoPageAussehen aussehen, aber wenn sie Anmeldefunktionen haben und ein HomePage(oder ein EditInfoPage) ein ist LoginPage , ist die Vererbung hier möglicherweise das richtige Werkzeug. Ein weniger umstrittenes Beispiel, bei dem die Komposition in offensichtlicherer Weise das bessere Werkzeug sein wird, würde die Dinge wahrscheinlich klarer machen.

Vorausgesetzt, weder a HomePagenoch EditInfoPagea ist LoginPage, und man möchte das letztere wiederverwenden, wie Sie geschrieben haben, als es ziemlich wahrscheinlich ist, dass man nur einige Teile von LoginPage, nicht alles benötigt. Wenn dies der Fall ist, könnte ein besserer Ansatz als die Verwendung der Komposition in der gezeigten Weise darin bestehen,

  • extrahieren Sie den wiederverwendbaren Teil von LoginPagein eine eigene Komponente

  • Verwenden Sie diese Komponente erneut HomePageund EditInfoPagegenauso, wie sie jetzt im Inneren verwendet wirdLoginPage

Auf diese Weise wird in der Regel viel klarer, warum und wann Komposition über Vererbung der richtige Ansatz ist.

Doc Brown
quelle