La «composition par héritage» viole-t-elle le «principe sec»?

36

Par exemple, considérons que j'ai une classe pour que d'autres classes puissent s'étendre:

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

et quelques sous-classes:

public class HomePage extends LoginPage {

}

public class EditInfoPage extends LoginPage {

}

En fait, la sous-classe n'a aucune méthode à remplacer, je ne voudrais pas non plus accéder à la page d'accueil de manière générique, c'est-à-dire: je ne ferais pas quelque chose comme:

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

Je veux juste réutiliser la page de connexion. Mais selon https://stackoverflow.com/a/53354 , je devrais préférer la composition ici car je n'ai pas besoin de l'interface LoginPage, je n'utilise donc pas l'héritage ici:

public class HomePage {
    public LoginPage loginPage;
}

public class EditInfoPage {
    public LoginPage loginPage;
}

mais le problème vient ici, à la nouvelle version, du code:

public LoginPage loginPage;

duplique quand une nouvelle classe est ajoutée. Et si LoginPage doit être défini et activé, davantage de codes doivent être copiés:

public LoginPage loginPage;

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

Ma question est donc la suivante: la "composition fondée sur l'héritage" viole-t-elle le "principe sec"?

ocomfd
la source
13
Parfois, vous n'avez besoin ni d'héritage ni de composition, c'est-à-dire que parfois une classe avec quelques objets fait le travail.
Erik Eidt
23
Pourquoi voudriez-vous que toutes vos pages soient la page de connexion? Peut-être juste avoir une page de connexion.
M. Cochese
29
Mais, avec l'héritage, vous avez des doublons extends LoginPagepartout. CHECK MATE!
el.pescado
2
Si vous avez souvent le problème dans le dernier extrait, vous utilisez peut-être trop de getters et de setters. Ils ont tendance à violer l'encapsulation.
Brian McCutchon
4
Alors, faites que votre page puisse être décorée et faites de votre LoginPagedécoratrice. Pas plus de duplication, juste un simple page = new LoginPage(new EditInfoPage())et vous avez terminé. Ou bien vous utilisez le principe open-closed pour créer le module d'authentification pouvant être ajouté à n'importe quelle page de manière dynamique. Il existe de nombreuses façons de gérer la duplication de code, notamment en trouvant une nouvelle abstraction. LoginPageest probablement un mauvais nom, ce que vous voulez vous assurer, c’est que l’utilisateur est authentifié lorsqu’il navigue sur cette page, tout en étant redirigé vers le LoginPageou affiché un message d’erreur approprié s’il ne l’est pas.
Polygnome

Réponses:

46

Euh attendez vous êtes préoccupé par le fait de répéter

public LoginPage loginPage;

à deux endroits viole DRY? Par cette logique

int x;

ne peut désormais plus exister que dans un seul objet de la base de code. Bleh.

SEC est une bonne chose à garder à l'esprit mais allez. outre

... extends LoginPage

est en train de se dupliquer dans votre alternative alors même être anal à propos de DRY ne donnera pas de sens à cela.

Les problèmes de DRY valides tendent à se focaliser sur le comportement identique requis dans plusieurs endroits et défini dans plusieurs endroits, de sorte que la nécessité de changer ce comportement finit par nécessiter un changement à plusieurs endroits. Prenez des décisions à un endroit et il vous suffira de les changer à un endroit. Cela ne signifie pas qu'un seul objet peut contenir une référence à votre LoginPage.

DRY ne devrait pas être suivi aveuglément. Si vous dupliquez parce que copier et coller est plus facile que de penser à une bonne méthode ou à un bon nom de classe, vous vous trompez probablement.

Mais si vous voulez mettre le même code dans un endroit différent, car cet endroit est soumis à une responsabilité différente et doit probablement être modifié de façon indépendante, il est probablement sage de relâcher l'application de DRY et de laisser ce même comportement adopter une identité différente. . C'est le même genre de pensée qui consiste à interdire les nombres magiques.

DRY ne concerne pas seulement l'apparence du code. Il s'agit de ne pas répandre les détails d'une idée avec une répétition irréfléchie, ce qui oblige les responsables à résoudre le problème à l'aide d'une répétition inconsidérée. C'est lorsque vous essayez de vous dire que la répétition insensée est juste votre convention que les choses vont vraiment mal.

Je pense que vous essayez vraiment de vous plaindre du code standard. Oui, l'utilisation de la composition plutôt que de l'héritage exige un code standard. Rien n'est exposé gratuitement, vous devez écrire du code qui l'expose. Avec ce passe-partout, la souplesse de l’état, la possibilité de rétrécir l’interface exposée, de donner à différents objets des noms qui conviennent à leur niveau d’abstraction, un bon indial, et vous utilisez ce dont vous êtes composé de l’extérieur, pas le à l'intérieur, de sorte que vous faites face à son interface normale.

Mais oui, il y a beaucoup de frappe au clavier supplémentaire. Tant que je peux empêcher le problème yo-yo de faire rebondir une pile d'héritage pendant que je lis le code, ça vaut le coup.

Maintenant, ce n'est pas que je refuse d'utiliser jamais l'héritage. Une de mes utilisations préférées est de donner aux exceptions de nouveaux noms:

public class MyLoginPageWasNull extends NullPointerException{}
confits_orange
la source
int x;ne peut exister plus de zéro fois dans une base de code
K. Alan Bates
@ K.AlanBates excusez-moi ?
candied_orange
... Je savais que tu allais répliquer avec la classe Point lol. Personne ne se soucie de la classe de points. Si j'entre dans votre base de code et que int a; int b; int x;je vois, je ferai pression pour que "vous" soit supprimé.
K. Alan Bates
@ K.AlanBates Wow. Il est regrettable que vous pensiez que ce type de comportement ferait de vous un bon codeur. Le meilleur codeur n'est pas celui qui peut trouver le plus de choses à propos des autres. C'est celui qui rend le reste meilleur.
candied_orange
L'utilisation de noms dénués de sens est un non-débutant et fait probablement du développeur de ce code un passif plutôt qu'un atout.
K. Alan Bates
127

Un malentendu courant avec le principe DRY est qu'il est en quelque sorte lié à la non répétition de lignes de code. Le principe DRY est le suivant: "Chaque élément de connaissance doit avoir une représentation unique, non ambiguë et faisant autorité dans un système" . C'est une question de connaissance, pas de code.

LoginPagesait comment dessiner la page pour se connecter. Si on EditInfoPagesavait comment faire cela, ce serait une violation. Y compris LoginPagepar la composition est pas en aucune manière une violation du principe DRY.

Le principe DRY est peut-être le principe le plus mal utilisé en génie logiciel et il devrait toujours être considéré non pas comme un principe permettant de ne pas dupliquer le code, mais comme un principe permettant de ne pas dupliquer les connaissances du domaine abstrait. En fait, dans de nombreux cas, si vous appliquez correctement DRY, vous dupliquez le code et ce n’est pas nécessairement une mauvaise chose.

wasatz
la source
27
Le "principe de responsabilité unique" est beaucoup plus utilisé à mauvais escient. "Ne vous répétez pas" pourrait facilement entrer comme numéro deux :-)
gnasher729
28
"DRY" est un acronyme pratique pour "Don't Repeat Yourself" qui est un slogan, mais pas le nom du principe. Le nom réel du principe est "Once And Only Once", qui à son tour est un nom accrocheur. pour un principe qui prend quelques paragraphes à décrire. L'important est de comprendre les quelques paragraphes et non de mémoriser les trois lettres D, R et Y.
Jörg W Mittag
4
@ gnasher729 Vous savez, je suis d'accord avec vous pour dire que SRP est probablement mal utilisé beaucoup plus. Bien que pour être juste, dans de nombreux cas, je pense qu'ils sont souvent mal utilisés ensemble. Ma théorie est qu'il ne faut pas faire confiance aux programmeurs en général pour manier des acronymes avec des "noms faciles".
wasatz
17
IMHO c'est une bonne réponse. Cependant, pour être juste: selon mon expérience, la forme la plus fréquente d'infractions DRY est provoquée par la programmation par copier-coller et par la simple duplication de code. Quelqu'un m'a dit une fois "à chaque fois que vous allez copier-coller du code, réfléchissez-y à deux fois si la partie dupliquée ne peut pas être extraite dans une fonction commune", ce qui était un très bon conseil, à mon humble avis.
Doc Brown
9
L'abus de copier-coller est certainement un élément moteur de la violation de DRY, mais il est tout simplement interdit de le copier-coller. Je ne ressentirais aucun remords pour avoir deux méthodes avec un code identique lorsque ces méthodes représentent des éléments de connaissance différents. Ils servent deux responsabilités différentes. Il se trouve que leur code est identique aujourd'hui. Ils devraient être libres de changer indépendamment. Oui, la connaissance, pas le code. Bien placé. J'ai écrit l'une des autres réponses mais je m'inclinerai devant celle-ci. +1
candied_orange
12

Réponse courte: oui, cela est vrai - dans une certaine mesure, à un degré acceptable.

À première vue, l'héritage peut parfois vous faire économiser quelques lignes de code, car il a pour effet de dire "ma classe de réutilisation contiendra toutes les méthodes et tous les attributs publics de manière 1: 1". Donc, s'il y a une liste de 10 méthodes dans un composant, il n'est pas nécessaire de les répéter dans le code de la classe héritée. Lorsque dans un scénario de composition, 9 de ces 10 méthodes doivent être exposées publiquement via un composant de réutilisation, il faut ensuite noter 9 les appels de délégation et laisser le reste, il n'y a pas d'autre solution.

Pourquoi est-ce tolérable? Examinez les méthodes qui sont répliquées dans un scénario de composition - ces méthodes consistent exclusivement à déléguer des appels à l'interface du composant, ce qui ne contient aucune logique réelle.

Le principe de base de DRY est d’éviter d’avoir deux endroits dans le code où les mêmes règles logiques sont codées. ce qui introduit une erreur.

Mais comme les appels de délégation ne contiennent pas de logique, ceux-ci ne sont normalement pas soumis à une telle modification, ce qui ne pose donc pas de réel problème lorsque vous "préférez la composition à l'héritage". Et même si l'interface du composant change, ce qui peut induire des changements formels au niveau de toutes les classes utilisant le composant, dans un langage compilé, le compilateur nous dira quand nous aurons oublié de changer l'un des appelants.

Une note à votre exemple: je ne sais pas à quoi ressemblent votre HomePageet votre EditInfoPage, mais s’ils ont la fonctionnalité de connexion et qu’un HomePage(ou un EditInfoPage) est un LoginPage , l’héritage peut alors être le bon outil ici. Un exemple moins discutable, où la composition sera le meilleur outil d’une manière plus évidente, rendrait probablement les choses plus claires.

En supposant que ni l'un HomePageni l' autre ne EditInfoPagesoient LoginPage, et que l'on veuille réutiliser ce dernier, comme vous l'avez écrit, il est fort probable que l'on n'exige que certaines parties du LoginPage, pas tout. Si tel est le cas, une meilleure approche que d’utiliser la composition de la manière indiquée pourrait consister à:

  • extraire la partie réutilisable de LoginPagedans un composant de son propre

  • réutiliser ce composant HomePageet de EditInfoPagela même manière, il est utilisé maintenant à l'intérieurLoginPage

De cette façon, il sera généralement beaucoup plus clair de savoir pourquoi et quand la composition par héritage est la bonne approche.

Doc Brown
la source