Un des principes essentiels en programmation orientée objets concerne l'héritage de classes et la surcharge de méthodes: plutôt que de partir sur une série de comparaisons pour définir le comportement d'une instance, il est parfois préférable de définir une nouvelle sous-classe, qui surcharge une méthode bien précise.
Pour l'exemple, on pourrait ainsi définir trois classes:
> [...] if S is a subtype of T, then objects of type T in a computer program may be replaced with objects of type S (i.e., objects of type S may be substituted for objects of type T), without altering any of the desirable properties of that program (correctness, task performed, etc.). (Source: http://en.wikipedia.org/wiki/Liskov_substitution_principle[Wikipédia]).
> Let q(x) be a property provable about objects x of type T. Then q(y) should be provable for objects y of type S, where S is a subtype of T. (Source: http://en.wikipedia.org/wiki/Liskov_substitution_principle[Wikipédia aussi])
Ce n'est donc pas parce qu'une classe **a besoin d'une méthode définie dans une autre classe** qu'elle doit forcément en hériter.
Cela bousillerait le principe de substitution, dans la mesure où une instance de cette classe pourra toujours être considérée comme étant du type de son parent.
Nous vous laissons tester la structure ci-dessus en glissant une antilope dans la boite à goûter du lion, ce qui nous donnera quelques trucs bizarres (et un lion atteint de botulisme).
Un exemple est d'avoir une interface permettant d'accéder à des éléments.
Modifier cette interface pour permettre l'écriture impliquerait que toutes les applications ayant déjà accès à la première, obtiendraient (par défaut) un accès en écriture, ce qui n'est pas souhaité/souhaitable.
Pour contrer ceci, on aurait alors une première interface permettant la lecture, tandis qu'une deuxième (héritant de la première) permettrait l'écriture. On aurait alors le schéma suivant :
* A : lecture
* B (héritant de A) : lecture (par A) et écriture.
Dans une architecture conventionnelle, les composants de haut-niveau dépendent directement des composants de bas-niveau.
L'inversion de dépendances stipule que c'est le composant de haut-niveau qui possède la définition de l'interface dont il a besoin, et le composant de bas-niveau qui l'implémente.
> The dependency inversion principle tells us that the most flexible systems are those in which source code dependencies
refer only to abstractions, not to concretions.
L’objectif est que les interfaces soient stables, et de réduire au maximum les modifications qui pourraient y être appliquées.
De la meme manière, il convient d’éviter de surcharger des fonctions ou des classes concrètes (= non abstraites).
En termes architecturaux, ce principe définira les frontières dont il a déjà été question,
en demandant à ce qu’une délimitation ne se base que sur une dépendance qui soit …
cela permet notamment une séparation claire au niveau des frontières, en inversant le flux de dépendance et en faisant en sorte (par exemple) que les règles métiers n’aient aucune connaissance des interfaces graphiques qui les exploitent. Ces interfaces pouvant être desktop, web, … cela n’a pas vraiment d’importance. Cela autorise une for.e d’immunité entre les composants.
Ne pas oublier qu’écrire du nouveau code est toujours plus facile que de modifier du code existant.
> The database is really nothing more than a big bucket of bits where we store our data on a long term basis (the database is a detail, chap 30, page 281)
D’un point de vue architectural, nous ne devons pas nous soucier de la manière dont les données sont stockées, s’il s’agit d’un disque magnétique, de ram, … en fait, on ne devrait même pas savoir s’il y a un disque du tout.
* http://lostechies.com/derickbailey/2011/09/22/dependency-injection-is-not-the-same-as-the-dependency-inversion-principle/[Dependency Injection is NOT the same as dependency inversion]
* http://en.wikipedia.org/wiki/Dependency_injection[Injection de dépendances]
De la même manière que pour les principes définis ci-dessus,
Mais toujours en faisant attention qu’une fois que les frontières sont implémentés, elles sont coûteuses à maintenir.
Cependant, il ne s’agit pas une décision à réaliser une seule fois, puisque cela peut être réévalué.
Et de la même manière que nous devons délayer au maximum les choix architecturaux et techniques,
> but this is not a one time decision. You don’t simply decide at the start of a project which boundaries to implémentent and which to ignore. Rather, you watch. You pay attention as the system evolves. You note where boundaries may be required, and then carefully watch for the first inkling of friction because those boundaries don’t exist.
> at that point, you weight the costs of implementing those boundaries versus the cost of ignoring them and you review that decision frequently. Your goal is to implement the boundaries right at the inflection point where the cost of implementing becomes less than the cost of ignoring.
En gros, il faut projeter sur la capacité à s’adapter en minimisant la maintenance.
Le problème est qu’elle ne permettait aucune adaptation, et qu’à la première demande, l’architecture se plante complètement sans aucune malléabilité.
==== Reuse/release equivalence principle
[quote]
----
Classes and modules that are grouped together into a component should be releasable together
-- (Chapitre 13, Component Cohesion, page 105)
----
==== CCP
(= l’équivalent du SRP, mais pour les composants)
> If two classes are so tightly bound, either physically or conceptually, that they always change together, then they belong in the same component
Il y a peut-être aussi un lien à faire avec «Your code as a crime scene» 🤟
La définition exacte devient celle-ci: «gather together those things that change at the same times and for the same reasons. Separate those things that change at different times or for different reasons».
==== CRP
… que l’on résumera ainsi: «don’t depend on things you don’t need» 😘
Au niveau des composants, au niveau architectural, mais également à d’autres niveaux.
==== SDP
(Stable dependency principle) qui définit une formule de stabilité pour les composants, en fonction de sa faculté à être modifié et des composants qui dépendent de lui: au plus un composant est nécessaire, au plus il sera stable (dans la mesure où il lui sera difficile de changer). En C++, cela correspond aux mots clés #include.
Pour faciliter cette stabilité, il convient de passer par des interfaces (donc, rarement modifiées, par définition).
En Python, ce ratio pourrait être calculé au travers des import, via les AST.
==== SAP
(= Stable abstraction principle) pour la définition des politiques de haut niveau vs les composants plus concrets.
SAP est juste une modélisation du OCP pour les composants: nous plaçons ceux qui ne changent pas ou pratiquement pas le plus haut possible dans l’organigramme (ou le diagramme), et ceux qui changent souvent plus bas, dans le sens de stabilité du flux. Les composants les plus bas sont considérés comme volatiles