From 9c051318d440f19381c96e144a0950a18ea3538e Mon Sep 17 00:00:00 2001 From: Fred Pauchet Date: Fri, 15 Apr 2022 22:25:44 +0200 Subject: [PATCH] Just finished SOLID --- asciidoc-to-tex.tex | 310 ----------------------------------- chapters/maintenability.tex | 316 ++++++++++++++++++++++++++++++++++-- 2 files changed, 306 insertions(+), 320 deletions(-) diff --git a/asciidoc-to-tex.tex b/asciidoc-to-tex.tex index bbac2be..ea5a903 100644 --- a/asciidoc-to-tex.tex +++ b/asciidoc-to-tex.tex @@ -1,314 +1,4 @@ - -\hypertarget{_interface_segregation_principle}{% -\subsubsection{Interface Segregation -Principle}\label{_interface_segregation_principle}} - -Le principe de ségrégation d'interface suggère de limiter la nécessité -de recompiler un module, en n'exposant que les opérations nécessaires à -l'exécution d'une classe. Ceci évite d'avoir à redéployer l'ensemble -d'une application. - -\begin{quote} -The lesson here is that depending on something that carries baggage that -you don't need can cause you troubles that you didn't except. -\end{quote} - -Ce principe stipule qu'un client ne doit pas dépendre d'une méthode dont -il n'a pas besoin. Plus simplement, plutôt que de dépendre d'une seule -et même (grosse) interface présentant un ensemble conséquent de -méthodes, il est proposé d'exploser cette interface en plusieurs (plus -petites) interfaces. Ceci permet aux différents consommateurs de -n'utiliser qu'un sous-ensemble précis d'interfaces, répondant chacune à -un besoin précis. - -GNU/Linux Magazine cite:{[}gnu\_linux\_mag\_hs\_104(37-42){]} propose un -exemple d'interface permettant d'implémenter une imprimante: - -\begin{Shaded} -\begin{Highlighting}[] -\KeywordTok{interface}\NormalTok{ IPrinter} -\NormalTok{\{} - \KeywordTok{public} \KeywordTok{abstract} \DataTypeTok{void} \FunctionTok{printPage}\NormalTok{();} - - \KeywordTok{public} \KeywordTok{abstract} \DataTypeTok{void} \FunctionTok{scanPage}\NormalTok{();} - - \KeywordTok{public} \KeywordTok{abstract} \DataTypeTok{void} \FunctionTok{faxPage}\NormalTok{();} -\NormalTok{\}} - -\KeywordTok{public} \KeywordTok{class}\NormalTok{ Printer} -\NormalTok{\{} - \KeywordTok{protected}\NormalTok{ string name;} - - \KeywordTok{public} \FunctionTok{Printer}\NormalTok{(string name)} -\NormalTok{ \{} - \KeywordTok{this}\NormalTok{.}\FunctionTok{name}\NormalTok{ = name;} -\NormalTok{ \}} -\NormalTok{\}} -\end{Highlighting} -\end{Shaded} - -L'implémentation d'une imprimante multifonction aura tout son sens: - -\begin{Shaded} -\begin{Highlighting}[] -\KeywordTok{public} \KeywordTok{class}\NormalTok{ AllInOnePrinter }\KeywordTok{implements}\NormalTok{ Printer }\KeywordTok{extends}\NormalTok{ IPrinter} -\NormalTok{\{} - \KeywordTok{public} \FunctionTok{AllInOnePrinter}\NormalTok{(string name)} -\NormalTok{ \{} - \KeywordTok{super}\NormalTok{(name);} -\NormalTok{ \}} - - \KeywordTok{public} \DataTypeTok{void} \FunctionTok{printPage}\NormalTok{()} -\NormalTok{ \{} - \BuiltInTok{System}\NormalTok{.}\FunctionTok{out}\NormalTok{.}\FunctionTok{println}\NormalTok{(}\KeywordTok{this}\NormalTok{.}\FunctionTok{name}\NormalTok{ + }\StringTok{": Impression"}\NormalTok{);} -\NormalTok{ \}} - - \KeywordTok{public} \DataTypeTok{void} \FunctionTok{scanPage}\NormalTok{()} -\NormalTok{ \{} - \BuiltInTok{System}\NormalTok{.}\FunctionTok{out}\NormalTok{.}\FunctionTok{println}\NormalTok{(}\KeywordTok{this}\NormalTok{.}\FunctionTok{name}\NormalTok{ + }\StringTok{": Scan"}\NormalTok{);} -\NormalTok{ \}} - - \KeywordTok{public} \DataTypeTok{void} \FunctionTok{faxPage}\NormalTok{()} -\NormalTok{ \{} - \BuiltInTok{System}\NormalTok{.}\FunctionTok{out}\NormalTok{.}\FunctionTok{println}\NormalTok{(}\KeywordTok{this}\NormalTok{.}\FunctionTok{name}\NormalTok{ + }\StringTok{": Fax"}\NormalTok{);} -\NormalTok{ \}} -\NormalTok{\}} -\end{Highlighting} -\end{Shaded} - -Tandis que l'implémentation d'une imprimante premier-prix ne servira pas -à grand chose: - -\begin{Shaded} -\begin{Highlighting}[] -\KeywordTok{public} \KeywordTok{class}\NormalTok{ FirstPricePrinter }\KeywordTok{implements}\NormalTok{ Printer }\KeywordTok{extends}\NormalTok{ IPrinter} -\NormalTok{\{} - \KeywordTok{public} \FunctionTok{FirstPricePrinter}\NormalTok{(string name)} -\NormalTok{ \{} - \KeywordTok{super}\NormalTok{(name);} -\NormalTok{ \}} - - \KeywordTok{public} \DataTypeTok{void} \FunctionTok{printPage}\NormalTok{()} -\NormalTok{ \{} - \BuiltInTok{System}\NormalTok{.}\FunctionTok{out}\NormalTok{.}\FunctionTok{println}\NormalTok{(}\KeywordTok{this}\NormalTok{.}\FunctionTok{name}\NormalTok{ + }\StringTok{": Impression"}\NormalTok{);} -\NormalTok{ \}} - - \KeywordTok{public} \DataTypeTok{void} \FunctionTok{scanPage}\NormalTok{()} -\NormalTok{ \{} - \BuiltInTok{System}\NormalTok{.}\FunctionTok{out}\NormalTok{.}\FunctionTok{println}\NormalTok{(}\KeywordTok{this}\NormalTok{.}\FunctionTok{name}\NormalTok{ + }\StringTok{": Fonctionnalité absente"}\NormalTok{);} -\NormalTok{ \}} - - \KeywordTok{public} \DataTypeTok{void} \FunctionTok{faxPage}\NormalTok{()} -\NormalTok{ \{} - \BuiltInTok{System}\NormalTok{.}\FunctionTok{out}\NormalTok{.}\FunctionTok{println}\NormalTok{(}\KeywordTok{this}\NormalTok{.}\FunctionTok{name}\NormalTok{ + }\StringTok{": Fonctionnalité absente"}\NormalTok{);} -\NormalTok{ \}} -\NormalTok{\}} -\end{Highlighting} -\end{Shaded} - -L'objectif est donc de découpler ces différentes fonctionnalités en -plusieurs interfaces bien spécifiques, implémentant chacune une -opération isolée: - -\begin{Shaded} -\begin{Highlighting}[] -\KeywordTok{interface}\NormalTok{ IPrinterPrinter} -\NormalTok{\{} - \KeywordTok{public} \KeywordTok{abstract} \DataTypeTok{void} \FunctionTok{printPage}\NormalTok{();} -\NormalTok{\}} - -\KeywordTok{interface}\NormalTok{ IPrinterScanner} -\NormalTok{\{} - \KeywordTok{public} \KeywordTok{abstract} \DataTypeTok{void} \FunctionTok{scanPage}\NormalTok{();} -\NormalTok{\}} - -\KeywordTok{interface}\NormalTok{ IPrinterFax} -\NormalTok{\{} - \KeywordTok{public} \KeywordTok{abstract} \DataTypeTok{void} \FunctionTok{faxPage}\NormalTok{();} -\NormalTok{\}} -\end{Highlighting} -\end{Shaded} - -Cette réflexion s'applique finalement à n'importe quel composant: votre -système d'exploitation, les librairies et dépendances tierces, les -variables déclarées, \ldots\hspace{0pt} Quel que soit le composant que -l'on utilise ou analyse, il est plus qu'intéressant de se limiter -uniquement à ce dont nous avons besoin plutôt que - -En Python, ce comportement est inféré lors de l'exécution, et donc pas -vraiment d'application pour notre contexte d'étude: de manière plus -générale, les langages dynamiques sont plus flexibles et moins couplés -que les langages statiquement typés, pour lesquels l'application de ce -principe-ci permettrait de mettre à jour une DLL ou un JAR sans que cela -n'ait d'impact sur le reste de l'application. - -Il est ainsi possible de trouver quelques horreurs, dans tous les -langages: - -\begin{Shaded} -\begin{Highlighting}[] -\CommentTok{/*!} -\CommentTok{ * is{-}odd }\KeywordTok{\textless{}https:}\OtherTok{//github.com/jonschlinkert/is{-}odd}\KeywordTok{\textgreater{}} -\CommentTok{ *} -\CommentTok{ * Copyright (c) 2015{-}2017, Jon Schlinkert.} -\CommentTok{ * Released under the MIT License.} -\CommentTok{ */} - -\StringTok{\textquotesingle{}use strict\textquotesingle{}}\OperatorTok{;} - -\KeywordTok{const}\NormalTok{ isNumber }\OperatorTok{=} \PreprocessorTok{require}\NormalTok{(}\StringTok{\textquotesingle{}is{-}number\textquotesingle{}}\NormalTok{)}\OperatorTok{;} - -\NormalTok{module}\OperatorTok{.}\AttributeTok{exports} \OperatorTok{=} \KeywordTok{function} \FunctionTok{isOdd}\NormalTok{(value) \{} - \KeywordTok{const}\NormalTok{ n }\OperatorTok{=} \BuiltInTok{Math}\OperatorTok{.}\FunctionTok{abs}\NormalTok{(value)}\OperatorTok{;} - \ControlFlowTok{if}\NormalTok{ (}\OperatorTok{!}\FunctionTok{isNumber}\NormalTok{(n)) \{} - \ControlFlowTok{throw} \KeywordTok{new} \BuiltInTok{TypeError}\NormalTok{(}\StringTok{\textquotesingle{}expected a number\textquotesingle{}}\NormalTok{)}\OperatorTok{;} -\NormalTok{ \}} - \ControlFlowTok{if}\NormalTok{ (}\OperatorTok{!}\BuiltInTok{Number}\OperatorTok{.}\FunctionTok{isInteger}\NormalTok{(n)) \{} - \ControlFlowTok{throw} \KeywordTok{new} \BuiltInTok{Error}\NormalTok{(}\StringTok{\textquotesingle{}expected an integer\textquotesingle{}}\NormalTok{)}\OperatorTok{;} -\NormalTok{ \}} - \ControlFlowTok{if}\NormalTok{ (}\OperatorTok{!}\BuiltInTok{Number}\OperatorTok{.}\FunctionTok{isSafeInteger}\NormalTok{(n)) \{} - \ControlFlowTok{throw} \KeywordTok{new} \BuiltInTok{Error}\NormalTok{(}\StringTok{\textquotesingle{}value exceeds maximum safe integer\textquotesingle{}}\NormalTok{)}\OperatorTok{;} -\NormalTok{ \}} - \ControlFlowTok{return}\NormalTok{ (n }\OperatorTok{\%} \DecValTok{2}\NormalTok{) }\OperatorTok{===} \DecValTok{1}\OperatorTok{;} -\NormalTok{\}}\OperatorTok{;} -\end{Highlighting} -\end{Shaded} - -Voire, son opposé, qui dépend évidemment du premier: - -\begin{Shaded} -\begin{Highlighting}[] -\CommentTok{/*!} -\CommentTok{ * is{-}even }\KeywordTok{\textless{}https:}\OtherTok{//github.com/jonschlinkert/is{-}even}\KeywordTok{\textgreater{}} -\CommentTok{ *} -\CommentTok{ * Copyright (c) 2015, 2017, Jon Schlinkert.} -\CommentTok{ * Released under the MIT License.} -\CommentTok{ */} - -\StringTok{\textquotesingle{}use strict\textquotesingle{}}\OperatorTok{;} - -\KeywordTok{var}\NormalTok{ isOdd }\OperatorTok{=} \PreprocessorTok{require}\NormalTok{(}\StringTok{\textquotesingle{}is{-}odd\textquotesingle{}}\NormalTok{)}\OperatorTok{;} - -\NormalTok{module}\OperatorTok{.}\AttributeTok{exports} \OperatorTok{=} \KeywordTok{function} \FunctionTok{isEven}\NormalTok{(i) \{} - \ControlFlowTok{return} \OperatorTok{!}\FunctionTok{isOdd}\NormalTok{(i)}\OperatorTok{;} -\NormalTok{\}}\OperatorTok{;} -\end{Highlighting} -\end{Shaded} - -Il ne s'agit que d'un simple exemple, mais qui tend à une seule chose: -gardez les choses simples (et, éventuellement, stupides) kiss. Dans -l'exemple ci-dessus, l'utilisation du module \texttt{is-odd} requière -déjà deux dépendances: \texttt{is-even} et \texttt{is-number}. Imaginez -la suite. - -\hypertarget{_dependency_inversion_principle}{% -\subsubsection{Dependency inversion -Principle}\label{_dependency_inversion_principle}} - -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. L'objectif est que les interfaces soient -les plus stables possibles, afin de réduire au maximum les modifications -qui pourraient y être appliquées. De cette manière, toute modification -fonctionnelle pourra être directement appliquée sur le composant de -bas-niveau, sans que l'interface ne soit impactée. - -\begin{quote} -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. -\end{quote} - -cite:{[}clean\_architecture{]} - -L'injection de dépendances est un patron de programmation qui suit le -principe d'inversion de dépendances. - -Django est bourré de ce principe, que ce soit pour les -\emph{middlewares} ou pour les connexions aux bases de données. Lorsque -nous écrivons ceci dans notre fichier de configuration, - -\begin{Shaded} -\begin{Highlighting}[] -\CommentTok{\# [snip]} - -\NormalTok{MIDDLEWARE }\OperatorTok{=}\NormalTok{ [} - \StringTok{\textquotesingle{}django.middleware.security.SecurityMiddleware\textquotesingle{}}\NormalTok{,} - \StringTok{\textquotesingle{}django.contrib.sessions.middleware.SessionMiddleware\textquotesingle{}}\NormalTok{,} - \StringTok{\textquotesingle{}django.middleware.common.CommonMiddleware\textquotesingle{}}\NormalTok{,} - \StringTok{\textquotesingle{}django.middleware.csrf.CsrfViewMiddleware\textquotesingle{}}\NormalTok{,} - \StringTok{\textquotesingle{}django.contrib.auth.middleware.AuthenticationMiddleware\textquotesingle{}}\NormalTok{,} - \StringTok{\textquotesingle{}django.contrib.messages.middleware.MessageMiddleware\textquotesingle{}}\NormalTok{,} - \StringTok{\textquotesingle{}django.middleware.clickjacking.XFrameOptionsMiddleware\textquotesingle{}}\NormalTok{,} -\NormalTok{]} - -\CommentTok{\# [snip]} -\end{Highlighting} -\end{Shaded} - -Django ira simplement récupérer chacun de ces middlewares, qui répondent -chacun à une -\href{https://docs.djangoproject.com/en/4.0/topics/http/middleware/\#writing-your-own-middleware}{interface -clairement définie}, dans l'ordre. Il n'y a donc pas de magie; c'est le -développeur qui va simplement brancher ou câbler des fonctionnalités au -niveau du framework, en les déclarant au bon endroit. Pour créer un -nouveau \emph{middleware}, il suffira d'implémenter le code suivant et -de l'ajouter dans la configuration de l'application: - -\begin{Shaded} -\begin{Highlighting}[] -\KeywordTok{def}\NormalTok{ simple\_middleware(get\_response):} - \CommentTok{\# One{-}time configuration and initialization.} - - \KeywordTok{def}\NormalTok{ middleware(request):} - \CommentTok{\# Code to be executed for each request before} - \CommentTok{\# the view (and later middleware) are called.} - -\NormalTok{ response }\OperatorTok{=}\NormalTok{ get\_response(request)} - - \CommentTok{\# Code to be executed for each request/response after} - \CommentTok{\# the view is called.} - - \ControlFlowTok{return}\NormalTok{ response} - - \ControlFlowTok{return}\NormalTok{ middleware} -\end{Highlighting} -\end{Shaded} - -Dans d'autres projets écrits en Python, ce type de mécanisme peut être -implémenté relativement facilement en utilisant les modules -\href{https://docs.python.org/3/library/importlib.html}{importlib} et la -fonction \texttt{getattr}. - -Un autre exemple concerne les bases de données: pour garder un maximum -de flexibilité, Django ajoute une couche d'abstraction en permettant de -spécifier le moteur de base de données que vous souhaiteriez utiliser, -qu'il s'agisse d'SQLite, MSSQL, Oracle, PostgreSQL ou MySQL/MariaDB -\footnote{\url{http://howfuckedismydatabase.com/}}. - -\begin{quote} -The database is really nothing more than a big bucket of bits where we -store our data on a long term basis. -\end{quote} - -cite:{[}clean\_architecture(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, \ldots\hspace{0pt} en fait, on ne devrait même pas -savoir s'il y a un disque du tout. Et Django le fait très bien pour -nous. - -En termes architecturaux, ce principe autorise une définition des -frontières, et en permettant une séparation claire en inversant le flux -de dépendances et en faisant en sorte que les règles métiers n'aient -aucune connaissance des interfaces graphiques qui les exploitent ou des -moteurs de bases de données qui les stockent. Ceci autorise une forme -d'immunité entre les composants. - \hypertarget{_sources}{% \subsubsection{Sources}\label{_sources}} diff --git a/chapters/maintenability.tex b/chapters/maintenability.tex index 41380cd..02bc504 100644 --- a/chapters/maintenability.tex +++ b/chapters/maintenability.tex @@ -625,12 +625,9 @@ Lorsque nous ajouterons notre nouveau type de rendu, nous ajouterons simplement \subsection{Liskov Substitution} - -Dans Clean Architecture, ce chapitre ci (le 9) est sans doute celui qui est le moins complet. -Je suis d'accord avec les exemples donnés, dans la mesure où la définition concrète d'une classe doit dépendre d'une interface correctement définie (et que donc, faire hériter un carré d'un rectangle, n'est pas adéquat dans le mesure où cela induit l'utilisateur en erreur), mais il y est aussi question de la définition d'un style architectural pour une interface REST, mais sans donner de solution... - Le principe de substitution fait qu'une classe héritant d'une autre classe doit se comporter de la même manière que cette dernière. -Il n'est pas question que la sous-classe n'implémente pas certaines méthodes, alors que celles-ci sont disponibles sa classe parente. +Il n'est pas question que la sous-classe n'implémente pas ou n'ait pas besoin de certaines méthodes, alors que celles-ci sont disponibles sa classe parente. +Mathématiquement, ce principe peut être défini de la manière suivante: \begin{quote} {[}\ldots\hspace{0pt}{]} 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.). @@ -668,9 +665,7 @@ Petit exemple pratique: si nous définissons une méthode \texttt{make\_some\_no \caption{Un lion et un canard sont sur un bateau...} \end{listing} - - -Le principe de substitution de Liskov suggère qu'une classe doit toujours pouvoir être considérée comme une instance de sa classe parent, et \textbf{doit pouvoir s'y substituer}. +Le principe de substitution de Liskov suggère qu'une classe doit toujours pouvoir être considérée comme une instance de sa classe parente, et \textbf{doit pouvoir s'y substituer}. Dans notre exemple, cela signifie que nous pourrons tout à fait accepter qu'un lion se comporte comme un canard et adore manger des plantes, insectes, graines, algues et du poisson. Miam ! 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). @@ -699,14 +694,13 @@ Pour revenir à nos exemples de rendus de documents, nous aurions pu faire héri \caption{Le convertisseur Markdown hérite d'un convertisseur XML...} \end{listing} - Si nous décidons à un moment d'ajouter une méthode d'entête au niveau de notre classe de rendu XML, notre rendu en Markdown héritera irrémédiablement de cette même méthode: \begin{listing}[H] \begin{minted}[tabsize=4]{Python} class XmlRenderer: def header(self): - return """"" + return """""" def render(self, document) return """{} @@ -731,6 +725,308 @@ Si nous décidons à un moment d'ajouter une méthode d'entête au niveau de not Le code ci-dessus ne porte pas à conséquence \footnote{Pas immédiatement, en tout cas...}, mais dès que nous invoquerons la méthode \texttt{header()} sur une instance de type \texttt{MarkdownRenderer}, nous obtiendrons un bloc de déclaration XML (\texttt{\textless{}?xml\ version\ =\ "1.0"?\textgreater{}}) pour un fichier Markdown, ce qui n'aura aucun sens. +En revenant à notre proposition d'implémentation, suite au respect d'Open-Closed, une solution serait de n'implémenter la méthode \texttt{header()} qu'au niveau de la classe \texttt{XmlRenderer}: + +\begin{listing}[H] + \begin{minted}[tabsize=4]{Python} + class Renderer: + def render(self, document): + raise NotImplementedError + + class XmlRenderer(Renderer): + def header(self): + return """""" + + def render(self, document) + return """{} + + {} + {} + {} + """.format( + self.header(), + document.title, + document.content, + document.published_at.isoformat() + ) + + class MarkdownRenderer(Renderer): + def render(self, document): + import markdown + return markdown.markdown(document.content) + \end{minted} + \caption{Définition d'héritage suivant le principe de substitution de Liskov} +\end{listing} + +\subsection{Interface Segregation} + +Le principe de ségrégation d'interface suggère de n'exposer que les opérations nécessaires à l'exécution d'un contexte. +Ceci limite la nécessité de recompiler un module, et évite ainsi d'avoir à redéployer l'ensemble d'une application alors qu'il suffirait de déployer un nouveau fichier JAR ou une DLL au bon endroit. + +\begin{quote} +The lesson here is that depending on something that carries baggage that you don't need can cause you troubles that you didn't except. +\end{quote} + +Plus simplement, plutôt que de dépendre d'une seule et même (grosse) interface présentant un ensemble conséquent de méthodes, il est proposé d'exploser cette interface en plusieurs (plus petites) interfaces. +Ceci permet aux différents consommateurs de n'utiliser qu'un sous-ensemble précis d'interfaces, répondant chacune à un besoin précis, et permet donc à nos clients de ne pas dépendre de méthodes dont ils n'ont pas besoin. + +GNU/Linux Magazine \cite[pp. 37-42]{gnu_linux_mag_hs_104} propose un exemple d'interface permettant d'implémenter une imprimante: + +\begin{listing}[H] + \begin{minted}[tabsize=4]{java} + interface IPrinter + { + public abstract void printPage(); + public abstract void scanPage(); + public abstract void faxPage(); + } + + public class Printer + { + protected string name; + + public Printer(string name) + { + this.name = name; + } + } + \end{minted} + \caption{Une interface représenant une imprimante} +\end{listing} + +L’implémentation d’une imprimante multifonction aura tout son sens: + +\begin{listing}[H] + \begin{minted}[tabsize=4]{java} + public class AllInOnePrinter implements Printer extends IPrinter + { + public AllInOnePrinter(string name) + { + super(name); + } + + public void printPage() + { + System.out.println(this.name + ": Impression"); + } + + public void scanPage() + { + System.out.println(this.name + ": Scan"); + } + + public void faxPage() + { + System.out.println(this.name + ": Fax"); + } + } + \end{minted} + \caption{Une imprimante multi-fonction implémente les fonctionnalités d'une imprimante classique} +\end{listing} + +Tandis que l’implémentation d’une imprimante premier-prix ne servira pas à grand chose: + +\begin{listing}[H] + \begin{minted}[tabsize=4]{java} + public class FirstPricePrinter implements Printer extends IPrinter + { + public FirstPricePrinter(string name) + { + super(name); + } + + public void printPage() + { + System.out.println(this.name + ": Impression"); + } + + public void scanPage() + { + System.out.println(this.name + ": Fonctionnalité absente"); + } + + public void faxPage() + { + System.out.println(this.name + ": Fonctionnalité absente"); + } + } + \end{minted} + \caption{Une imprimante premier prix ne peut qu'imprimer, mais expose malgré tout des fonctions (absentes) de scanner et d'envoi par fax} +\end{listing} + +L’objectif est donc de découpler ces différentes fonctionnalités en plusieurs interfaces bien spécifiques, implémentant chacune une opération isolée: + +\begin{listing}[H] + \begin{minted}[tabsize=4]{java} + interface IPrinterPrinter + { + public abstract void printPage(); + } + + interface IPrinterScanner + { + public abstract void scanPage(); + } + + interface IPrinterFax + { + public abstract void faxPage(); + } + \end{minted} + \caption{Explosion des interfaces d'impression} +\end{listing} + + +Cette réflexion s'applique à n'importe quel composant: votre système d'exploitation, les librairies et dépendances tierces, les variables déclarées, ... +Quel que soit le composant que l'on utilise ou analyse, il est plus qu'intéressant de se limiter uniquement à ce dont nous avons besoin plutôt que d'embarquer le must absolu qui peut faire 1000x fonctions de plus que n'importe quel autre produit, alors que seules deux d'entre elles seront nécessaires. + +En Python, ce comportement est inféré lors de l'exécution, et donc pas vraiment d'application pour ce contexte d'étude: de manière plus générale, les langages dynamiques sont plus flexibles et moins couplés que les langages statiquement typés, pour lesquels l'application de ce principe-ci permettrait de juste mettre à jour une DLL ou un JAR sans que cela n'ait d'impact sur le reste de l'application. + +Il est ainsi possible de trouver quelques horreurs, et ce dans tous les langages: + +\begin{listing}[H] + \begin{minted}[tabsize=4]{javascript} + /*! + * is-odd + * + * Copyright (c) 2015-2017, Jon Schlinkert. + * Released under the MIT License. + */ + 'use strict'; + + const isNumber = require('is-number'); + + module.exports = function isOdd(value) { + + const n = Math.abs(value); + + if (!isNumber(n)) { + throw new TypeError('expected a number'); + } + + if (!Number.isInteger(n)) { + throw new Error('expected an integer'); + } + + if (!Number.isSafeInteger(n)) { + throw new Error('value exceeds maximum safe integer'); + } + + return (n % 2) === 1; + } + \end{minted} + \caption{Le module 'isOdd', en JavaScript} +\end{listing} + +Voire, son opposé, qui dépend évidemment du premier: + +\begin{listing}[H] + \begin{minted}[tabsize=4]{javascript} + /*! + * is-even + * + * Copyright (c) 2015, 2017, Jon Schlinkert. + * Released under the MIT License. + */ + 'use strict'; + + var isOdd = require('is-odd'); + + module.exports = function isEven(i) { + return !isOdd(i); + }; + \end{minted} + \caption{Le module 'isEven', en JavaScript, qui dépend du premier} +\end{listing} + +Il ne s'agit que d'un simple exemple, mais qui tend à une seule chose: gardez les choses simples (et, éventuellement, stupides). \index{KISS} +Dans l'exemple ci-dessus, l'utilisation du module \texttt{is-odd} requière déjà deux dépendances: + +\begin{enumerate} + \item \texttt{is-even} + \item \texttt{is-number} +\end{enumerate} + +Imaginez la suite. + +\subsection{Dependency Inversion} + +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. +L'objectif est que les interfaces soient les plus stables possibles, afin de réduire au maximum les modifications qui pourraient y être appliquées. +De cette manière, toute modification fonctionnelle pourra être directement appliquée sur le composant de bas-niveau, sans que l'interface ne soit impactée. + +\begin{quote} +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. \cite{clean_architecture} +\end{quote} + +L'injection de dépendances est un patron de programmation qui suit le principe d'inversion de dépendances. + +Django est bourré de ce principe, que ce soit pour les \emph{middlewares} ou pour les connexions aux bases de données. +Lorsque nous écrivons ceci dans notre fichier de configuration, + +\begin{listing}[H] + \begin{minted}[tabsize=4]{python} + # [snip] + MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + ] + # [snip] + \end{minted} + \caption{La configuration des middlewares pour une application Django} +\end{listing} + + +Django ira simplement récupérer chacun de ces middlewares, qui répondent chacun à une +\href{https://docs.djangoproject.com/en/4.0/topics/http/middleware/\#writing-your-own-middleware}{interface clairement définie}, dans l'ordre. Il n'y a donc pas de magie; l'interface exige une signature particulière, tandis que l'implémentation effective n'est réalisée qu'au niveau le plus bas. +C'est ensuite le développeur qui va simplement brancher ou câbler des fonctionnalités au niveau du framework, en les déclarant au bon endroit. +Pour créer un nouveau \emph{middleware}, il nous suffirait d'implémenter de nouvelles fonctionnalités au niveau du code code suivant et de l'ajouter dans la configuration de l'application, au niveau de la liste des middlewares actifs: + +\begin{listing}[H] + \begin{minted}[tabsize=4]{python} + def simple_middleware(get_response): + # One-time configuration and initialization. + + def middleware(request): + # Code to be executed for each request before + # the view (and later middleware) are called. + + response = get_response(request) + + # Code to be executed for each request/response after + # the view is called. + + return response + + return middleware + \end{minted} + \caption{Création d'un nouveau middleware pour Django} +\end{listing} + + +Dans d'autres projets écrits en Python, ce type de mécanisme peut être implémenté relativement facilement en utilisant les modules \href{https://docs.python.org/3/library/importlib.html}{importlib} et la fonction \texttt{getattr}. + +Un autre exemple concerne les bases de données: pour garder un maximum de flexibilité, Django ajoute une couche d'abstraction en permettant de +spécifier le moteur de base de données que vous souhaiteriez utiliser, qu'il s'agisse d'SQLite, MSSQL, Oracle, PostgreSQL ou MySQL/MariaDB \footnote{\url{http://howfuckedismydatabase.com/}}. + +\begin{quote} +The database is really nothing more than a big bucket of bits where we store our data on a long term basis \cite[p. 281]{clean_architecture} +\end{quote} + +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 mémoire vive, ... en fait, on ne devrait même pas savoir s'il y a un disque du tout. +Et Django le fait très bien pour nous. + +En termes architecturaux, ce principe autorise une définition des frontières, et en permettant une séparation claire en inversant le flux +de dépendances et en faisant en sorte que les règles métiers n'aient aucune connaissance des interfaces graphiques qui les exploitent ou desmoteurs de bases de données qui les stockent. +Ceci autorise une forme d'immunité entre les composants. + \section{Tests unitaires et d'intégration} \begin{quote}