Qu'est-ce que cela signifie quand on dit «Encapsuler ce qui varie»?

25

L'un des principes de POO que j'ai rencontrés est: -Encapsuler ce qui varie.

Je comprends le sens littéral de l'expression, c'est-à-dire cacher ce qui varie. Cependant, je ne sais pas exactement comment cela contribuerait à une meilleure conception. Quelqu'un peut-il l'expliquer en utilisant un bon exemple?

Haris Ghauri
la source
Voir en.wikipedia.org/wiki/Encapsulation_(computer_programming) qui l'explique bien. Je pense que "ce qui varie" n'est pas correct car il faut parfois encapsuler des constantes.
qwerty_so du
I don't know how exactly would it contribute to a better designL'encapsulation des détails concerne le couplage lâche entre le «modèle» et les détails de mise en œuvre. Moins le «modèle» est lié aux détails de mise en œuvre, plus la solution est flexible. Et cela facilite son évolution. "Abstenez-vous des détails".
Laiv
@Laiv Donc "varie" se réfère à ce qui évolue au cours du cycle de vie de votre logiciel ou à ce qui change pendant l'exécution de votre programme ou les deux?
Haris Ghauri
2
@HarisGhauri les deux. Regroupez ce qui varie ensemble. Isolez ce qui varie indépendamment. Méfiez-vous de ce que vous supposez ne changera jamais.
candied_orange
1
@laiv penser "abstrait" est un bon point. Cela peut sembler écrasant de le faire. Dans n'importe quel objet, vous êtes censé avoir une seule responsabilité. La bonne chose à ce sujet est que vous n'avez qu'à réfléchir attentivement à cette seule chose ici. Lorsque les détails du reste du problème concernent quelqu'un d'autre, cela facilite les choses.
candied_orange

Réponses:

30

Vous pouvez écrire du code qui ressemble à ceci:

if (pet.type() == dog) {
  pet.bark();
} else if (pet.type() == cat) {
  pet.meow();
} else if (pet.type() == duck) {
  pet.quack()
}

ou vous pouvez écrire du code qui ressemble à ceci:

pet.speak();

Si ce qui varie est encapsulé, vous n'avez pas à vous en préoccuper. Vous vous préoccupez simplement de ce dont vous avez besoin et de tout ce que vous utilisez. Découvrez comment faire ce dont vous avez vraiment besoin en fonction de ce qui varie.

Encapsulez ce qui varie et vous n'avez pas à diffuser de code qui se soucie de ce qui varie. Vous définissez simplement l'animal comme étant un certain type qui sait parler comme ce type et après cela, vous pouvez oublier quel type et le traiter comme un animal. Vous n'avez pas à demander quel type.

Vous pourriez penser que le type est encapsulé car un getter est nécessaire pour y accéder. Je ne. Getter's n'encapsule pas vraiment. Ils bavent juste quand quelqu'un rompt votre encapsulation. C'est un joli décorateur comme un crochet orienté aspect qui est le plus souvent utilisé comme code de débogage. Peu importe comment vous le coupez, vous exposez toujours le type.

Vous pourriez regarder cet exemple et penser que j'associe le polymorphisme et l'encapsulation. Je ne suis pas. J'associe «ce qui varie» et «détails».

Le fait que votre animal soit un chien est un détail. Celui qui pourrait varier pour vous. Un qui pourrait ne pas l'être. Mais certainement celui qui peut varier d'une personne à l'autre. Sauf si nous pensons que ce logiciel ne sera utilisé que par les amoureux des chiens, il est judicieux de traiter le chien comme un détail et de l'encapsuler. De cette façon, certaines parties du système ignorent parfaitement le chien et ne seront pas affectées lorsque nous fusionnerons avec "les perroquets sont nous".

Découplez, séparez et masquez les détails du reste du code. Ne laissez pas la connaissance des détails se propager à travers votre système et vous suivrez très bien "encapsuler ce qui varie".

candied_orange
la source
3
C'est vraiment bizarre. "Encapsuler ce qui varie" signifie pour moi cacher les changements d'état, par exemple ne jamais avoir de variables globales. Mais votre réponse a également du sens, même si cela semble plus une réponse au polymorphisme qu'à l'encapsulation :)
David Arno
2
Le polymorphisme @DavidArno est une façon de faire fonctionner ce système. J'aurais pu créer la structure if en animal de compagnie et les choses auraient l'air bien ici grâce à l'encapsulation de l'animal. Mais cela ne ferait que déplacer le désordre au lieu de le nettoyer.
candied_orange
1
"Encapsuler ce qui varie" signifie pour moi cacher les changements d'état . Non, non. J'aime le commentaire de CO. La réponse de Derick Elkin va plus loin, lisez-la plus d'une fois. Comme @JacquesB l'a dit "Ce principe est en fait assez profond"
radarbob
16

"Varie" signifie ici "peut changer avec le temps en raison de l'évolution des exigences". Il s'agit d'un principe de conception de base: séparer et isoler des éléments de code ou de données qui devront peut-être changer séparément à l'avenir. Si une seule exigence change, elle devrait idéalement nous obliger à changer le code associé en un seul endroit. Mais si la base de code est mal conçue, c'est-à-dire hautement interconnectée et la logique de l'exigence répartie dans de nombreux endroits, alors le changement sera difficile et aura un risque élevé de provoquer des effets inattendus.

Supposons que vous ayez une application qui utilise le calcul de la taxe de vente dans de nombreux endroits. Si le taux de la taxe de vente change, que préféreriez-vous:

  • le taux de la taxe de vente est un littéral codé en dur partout dans l'application où la taxe de vente est calculée.

  • le taux de la taxe de vente est une constante globale, qui est utilisée partout dans l'application où la taxe de vente est calculée.

  • il existe une seule méthode appelée calculateSalesTax(product)qui est le seul endroit où le taux de la taxe de vente est utilisé.

  • le taux de la taxe de vente est spécifié dans un fichier de configuration ou un champ de base de données.

Étant donné que le taux de la taxe de vente peut changer en raison d'une décision politique indépendante d'une autre exigence, nous préférons l'avoir isolé dans une configuration, afin qu'il puisse être modifié sans affecter aucun code. Mais il est également concevable que la logique de calcul de la taxe de vente puisse changer, par exemple des taux différents pour un produit différent, nous aimons donc également que la logique de calcul soit encapsulée. La constante globale peut sembler une bonne idée, mais elle est en fait mauvaise, car elle pourrait encourager l'utilisation de la taxe de vente à différents endroits du programme plutôt qu'à un seul endroit.

Considérons maintenant une autre constante, Pi, qui est également utilisée à de nombreux endroits dans le code. Le même principe de conception est-il valable? Non, car Pi ne va pas changer. Son extraction dans un fichier de configuration ou un champ de base de données ne fait qu'introduire une complexité inutile (et toutes choses étant égales par ailleurs, nous préférons le code le plus simple). Il est logique d'en faire une constante globale plutôt que de la coder en dur à plusieurs endroits pour éviter les incohérences et améliorer la lisibilité.

Le point est, si l' on considère seulement comment le programme fonctionne maintenant , le taux de taxe de vente et Pi sont équivalentes, les deux sont des constantes. Ce n'est que lorsque nous considérons ce qui peut varier à l'avenir que nous réalisons que nous devons les traiter différemment dans la conception.

Ce principe est en fait assez profond, car il signifie que vous devez regarder au-delà de ce que la base de code est censée faire aujourd'hui , et également tenir compte des forces externes qui peuvent le faire changer, et même comprendre les différentes parties prenantes derrière les exigences.

JacquesB
la source
2
Les taxes en sont un bon exemple. Les calculs de lois et impôts peuvent changer d'un jour à l'autre. Si vous mettez en place un système de déclaration fiscale, vous êtes fortement lié à ce type de changements. Change également d'un Locale à l'autre (pays, provinces, ...)
Laiv
"Pi ne va pas changer" m'a fait rire. C'est vrai, Pi est peu susceptible de changer, mais supposez que vous n'étiez plus autorisé à l'utiliser? Si certaines personnes réussissent, Pi sera déprécié. Supposons que cela devienne une exigence? J'espère que vous avez une bonne journée Tau . Belle réponse BTW. Profond en effet.
candied_orange
14

Les deux réponses actuelles semblent ne frapper que partiellement la cible, et elles se concentrent sur des exemples qui brouillent l'idée de base. Ce n'est pas non plus (uniquement) un principe de POO mais un principe de conception de logiciel en général.

La chose qui "varie" dans cette phrase est le code. Christophe a raison de dire que c'est généralement quelque chose qui peut varier, c'est-à-dire que vous l' anticipez souvent . Le but est de vous protéger des modifications futures du code. Ceci est étroitement lié à la programmation sur une interface . Cependant, Christophe a tort de limiter cela aux "détails d'implémentation". En fait, la valeur de ces conseils est souvent due à des changements dans les exigences .

Ceci n'est qu'indirectement lié à l'encapsulation de l'état, ce à quoi je pense que David Arno pense. Ce conseil ne suggère pas toujours (mais souvent) un état d'encapsulation, et ce conseil s'applique également aux objets immuables. En fait, le simple fait de nommer des constantes est une forme (très basique) d'encapsuler ce qui varie.

CandiedOrange confond explicitement «ce qui varie» avec «détails». Ce n'est que partiellement correct. Je suis d'accord que tout code qui varie est des "détails" dans un certain sens, mais un "détail" ne peut pas varier (sauf si vous définissez des "détails" pour rendre cela tautologique). Il peut y avoir des raisons d'encapsuler des détails non variables, mais ce dicton n'en est pas un. En gros, si vous étiez très confiant que "chien", "chat" et "canard" seraient les seuls types avec lesquels vous auriez besoin de traiter, alors ce dicton ne suggère pas le refactoring effectué par CandiedOrange.

Casting l'exemple de CandiedOrange dans un contexte différent, supposons que nous avons un langage procédural comme C. Si j'ai du code qui contient:

if (pet.type() == dog) {
  pet.bark();
} else if (pet.type() == cat) {
  pet.meow();
} else if (pet.type() == duck) {
  pet.quack()
}

Je peux raisonnablement m'attendre à ce que ce morceau de code change à l'avenir. Je peux "l'encapsuler" simplement en définissant une nouvelle procédure:

void speak(pet) {
  if (pet.type() == dog) {
    pet.bark();
  } else if (pet.type() == cat) {
    pet.meow();
  } else if (pet.type() == duck) {
    pet.quack()
  }
}

et en utilisant cette nouvelle procédure au lieu du bloc de code (c'est-à-dire une refactorisation de "méthode d'extraction"). À ce stade, l'ajout d'un type "vache" ou autre ne nécessite que la mise à jour de la speakprocédure. Bien sûr, dans une langue OO, vous pouvez plutôt tirer parti de la répartition dynamique comme le mentionne la réponse de CandiedOrange. Cela se produira naturellement si vous accédez petvia une interface. L'élimination de la logique conditionnelle via la répartition dynamique est une préoccupation orthogonale qui faisait partie de la raison pour laquelle j'ai fait ce rendu procédural. Je tiens également à souligner que cela ne nécessite pas de fonctionnalités particulières à la POO. Même dans un langage OO, encapsuler ce qui varie ne signifie pas nécessairement qu'une nouvelle classe ou interface doit être créée.

À titre d'exemple plus archétypal (qui est plus proche mais pas tout à fait OO), disons que nous voulons supprimer les doublons d'une liste. Supposons que nous l'implémentions en parcourant la liste en gardant une trace des éléments que nous avons vus jusqu'à présent dans une autre liste et en supprimant tous les éléments que nous avons vus. Il est raisonnable de supposer que nous pouvons vouloir changer la façon dont nous gardons la trace des éléments vus pour, au moins, pour des raisons de performances. Le dicton pour encapsuler ce qui varie suggère que nous devrions construire un type de données abstrait pour représenter l'ensemble des éléments vus. Notre algorithme est maintenant défini par rapport à ce type de données Set abstrait, et si nous décidons de passer à un arbre de recherche binaire, notre algorithme n'a pas besoin de changer ou de prendre soin. Dans un langage OO, nous pouvons utiliser une classe ou une interface pour capturer ce type de données abstrait. Dans une langue comme SML / O '

Pour un exemple axé sur les exigences, supposons que vous devez valider un champ en ce qui concerne une logique métier. Bien que vous puissiez avoir des exigences spécifiques maintenant, vous soupçonnez fortement qu'elles évolueront. Vous pouvez encapsuler la logique actuelle dans sa propre procédure / fonction / règle / classe.

Bien qu'il s'agisse d'une préoccupation orthogonale qui ne fait pas partie de "l'encapsulation de ce qui varie", il est souvent naturel d'abstraire, c'est-à-dire de paramétrer, la logique désormais encapsulée. Cela conduit généralement à un code plus flexible et permet à la logique d'être modifiée en remplaçant dans une implémentation alternative plutôt qu'en modifiant la logique encapsulée.

Derek Elkins
la source
Oh douce ironie amère. Oui, ce n'est pas uniquement un problème de POO. Vous m'avez surpris en train de laisser un détail du paradigme linguistique polluer ma réponse et vous m'avez puni à juste titre en "variant" le paradigme.
candied_orange
"Même dans un langage OO, encapsuler ce qui varie ne signifie pas nécessairement qu'une nouvelle classe ou interface doit être créée" - il est difficile d'imaginer une situation où la non-création d'une nouvelle classe ou interface ne violerait pas SRP
taurelas
11

«Encapsuler ce qui varie» fait référence au masquage des détails de mise en œuvre qui peuvent changer et évoluer.

Exemple:

Par exemple, supposons que la classe Coursegarde une trace de ce Studentsqui peut s'inscrire (). Vous pouvez l'implémenter avec un LinkedListet exposer le conteneur pour permettre l'itération sur celui-ci:

class Course { 
    public LinkedList<Student> Atendees; 
    public bool register (Student s);  
    ...
}

Mais ce n'est pas une bonne idée:

  • Premièrement, les gens peuvent manquer de bon comportement et l'utiliser comme libre-service, ajoutant directement des étudiants à la liste, sans passer par la méthode register ().
  • Mais encore plus ennuyeux: cela crée une dépendance du "code d'utilisation" avec les détails d'implémentation internes de la classe utilisée. Cela pourrait empêcher de futures évolutions de la classe, par exemple si vous préférez utiliser un tableau, un vecteur, une carte avec le numéro de siège ou votre propre structure de données persistante.

Si vous encapsulez ce qui varie (ou plutôt, ce qui pourrait varier), vous gardez la liberté pour le code utilisateur et la classe encapsulée d'évoluer les uns les autres. C'est pourquoi c'est un principe important en POO.

Lecture supplémentaire:

Christophe
la source