it-swarm.com.de

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

Angenommen, ich habe eine Klasse, die andere Klassen erweitern können:

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, d. H.: Ich würde nichts tun wie:

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 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;

duplikate, 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 ist also, verstößt "Zusammensetzung über Vererbung" gegen "trockenes Prinzip"?

36
ocomfd

Äh, warte, du machst dir Sorgen, dass du es wiederholst

public LoginPage loginPage;

an zwei Stellen gegen DRY verstößt? Nach dieser Logik

int x;

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

TROCKEN ist eine gute Sache, die man sich merken sollte, aber komm schon. Außerdem

... extends LoginPage

wird in Ihrer Alternative dupliziert, so dass selbst ein Analsex über DRY macht keinen Sinn daraus.

Gültig DRY Bedenken konzentrieren sich in der Regel auf identisches Verhalten, das an mehreren Stellen benötigt wird und an mehreren Stellen definiert wird, sodass eine Änderung dieses Verhaltens eine Änderung an mehreren Stellen erfordert. Treffen Sie Entscheidungen an einem Ort und Sie müssen sie nur an einer Stelle ändern. Dies bedeutet nicht, dass nur ein Objekt jemals 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 einen guten Methoden- oder Klassennamen auszudenken, liegen Sie wahrscheinlich falsch.

Wenn Sie jedoch denselben Code an einem anderen Ort ablegen möchten, weil dieser andere Ort einer anderen Verantwortung unterliegt und sich wahrscheinlich unabhängig ändern muss, ist es wahrscheinlich ratsam, die Durchsetzung von DRY) zu lockern und lassen Sie dieses identische Verhalten eine andere Identität haben. Es ist die gleiche Art des Denkens, die dazu beiträgt, 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, die die Betreuer dazu zwingen, Dinge mit sinnlosen Wiederholungen zu reparieren. Wenn Sie versuchen, sich selbst zu sagen, dass die sinnlose Wiederholung nur Ihre Konvention ist, dass die Dinge wirklich schlecht laufen.

Ich denke, Sie versuchen sich wirklich zu beschweren, heißt Boilerplate-Code. Ja, für die Verwendung von Komposition anstelle von Vererbung ist Boilerplate-Code erforderlich. Nichts wird kostenlos verfügbar gemacht, Sie müssen Code schreiben, der es verfügbar macht. Mit diesem Boilerplate geht es um Zustandsflexibilität, die Fähigkeit, die exponierte Schnittstelle einzugrenzen, Dingen unterschiedliche Namen zu geben, die für ihren Abstraktionsgrad geeignet sind, eine gute alte Indirektion, und Sie verwenden das, woraus Sie von außen bestehen, nicht das innen, so dass Sie vor der normalen Benutzeroberfläche stehen.

Aber ja, es ist eine Menge zusätzlicher Tastatureingaben. Solange ich verhindern kann, dass das Jojo-Problem einen Vererbungsstapel beim Lesen des Codes auf und ab hüpft, lohnt es sich.

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

public class MyLoginPageWasNull extends NullPointerException{}
46
candied_orange

Ein häufiges Missverständnis mit dem DRY Prinzip ist, dass es irgendwie damit zusammenhängt, Codezeilen nicht zu wiederholen. Das DRY Prinzip ist "Jedes Stück von Wissen muss eine einzige, eindeutige, maßgebliche Darstellung innerhalb eines Systems haben " . Es geht um Wissen, nicht um Code.

LoginPage weiß, wie die Seite zum Anmelden gezeichnet wird. Wenn EditInfoPage wüsste, wie dies zu tun ist, wäre dies eine Verletzung. Das Einbeziehen von LoginPage über die Komposition ist in keiner Weise eine Verletzung des DRY -Prinzips).

Das DRY -Prinzip ist vielleicht das am meisten missbrauchte Prinzip in der Softwareentwicklung und sollte immer nicht als Prinzip zum Nicht-Duplizieren von Code, sondern als Prinzip zum Nicht-Duplizieren von abstraktem Domänenwissen betrachtet werden. In vielen Fällen duplizieren Sie Code , wenn Sie DRY richtig anwenden), und dies ist nicht unbedingt eine schlechte Sache .

127
wasatz

Kurze Antwort: Ja, bis zu einem geringfügigen, akzeptablen Grad.

Auf den ersten Blick können Sie durch Vererbung manchmal einige Codezeilen sparen, da dies bedeutet, dass "meine wiederverwendbare Klasse alle öffentlichen Methoden und Attribute 1: 1 enthält". Wenn eine Komponente eine Liste von 10 Methoden enthält, müssen diese nicht im Code der geerbten Klasse wiederholt werden. Wenn in einem Kompositionsszenario 9 dieser 10 Methoden durch eine wiederverwendbare Komponente öffentlich zugänglich gemacht werden sollen, muss man 9 delegierende Aufrufe aufschreiben und die verbleibende herauslassen, daran führt kein Weg vorbei.

Warum ist das erträglich? Schauen 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.

Das Herzstück des DRY -Prinzips ist es, zu vermeiden, dass zwei Stellen im Code vorhanden sind, an denen dieselben logischen Regeln codiert sind - denn wenn sich diese logischen Regeln ändern, im Nicht-DRY-Code Es ist einfach, einen dieser Orte anzupassen und den anderen zu vergessen, was zu einem Fehler führt.

Da das Delegieren von Aufrufen keine Logik enthält, sind diese normalerweise nicht Gegenstand einer solchen Änderung, sodass kein echtes Problem entsteht, wenn "Komposition gegenüber Vererbung bevorzugt wird". Und selbst wenn sich die Schnittstelle der Komponente ändert, was bei allen Klassen, die die Komponente verwenden, zu formalen Änderungen führen kann, teilt uns der Compiler in einer kompilierten Sprache mit, wann wir vergessen haben, einen der Aufrufer zu ändern.

Ein Hinweis zu Ihrem Beispiel: Ich weiß nicht, wie Ihr HomePage und Ihr EditInfoPage aussehen, aber wenn sie Anmeldefunktionen haben und ein HomePage (oder ein EditInfoPage) ist aLoginPage, dann könnte die Vererbung hier das richtige Werkzeug sein. Ein weniger umstrittenes Beispiel, bei dem die Komposition auf offensichtlichere Weise das bessere Werkzeug sein wird, würde die Dinge wahrscheinlich klarer machen.

Angenommen, weder HomePage noch EditInfoPage sind LoginPage, und man möchte letzteres wiederverwenden, wie Sie geschrieben haben, als es ziemlich wahrscheinlich ist, dass man nur einige Teile benötigt vom LoginPage nicht alles. Wenn dies der Fall ist, könnte ein besserer Ansatz als die Verwendung der Komposition auf die gezeigte Weise sein

  • extrahieren Sie den wiederverwendbaren Teil von LoginPage in eine eigene Komponente

  • verwenden Sie diese Komponente in HomePage und EditInfoPage genauso wieder, wie sie jetzt in LoginPage verwendet wird

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

12
Doc Brown