diff --git a/asciidoc-to-tex.tex b/asciidoc-to-tex.tex index 5d32b32..bbac2be 100644 --- a/asciidoc-to-tex.tex +++ b/asciidoc-to-tex.tex @@ -1,407 +1,4 @@ -\hypertarget{_single_responsibility_principle}{% -\subsubsection{Single Responsibility -Principle}\label{_single_responsibility_principle}} - - - - -\begin{Shaded} -\begin{Highlighting}[] -\KeywordTok{class}\NormalTok{ Document:} - \KeywordTok{def} \FunctionTok{\_\_init\_\_}\NormalTok{(}\VariableTok{self}\NormalTok{, title, content, published\_at):} - \VariableTok{self}\NormalTok{.title }\OperatorTok{=}\NormalTok{ title} - \VariableTok{self}\NormalTok{.content }\OperatorTok{=}\NormalTok{ content} - \VariableTok{self}\NormalTok{.published\_at }\OperatorTok{=}\NormalTok{ published\_at} - - \KeywordTok{def}\NormalTok{ render(}\VariableTok{self}\NormalTok{, format\_type):} - \ControlFlowTok{if}\NormalTok{ format\_type }\OperatorTok{==} \StringTok{"XML"}\NormalTok{:} - \ControlFlowTok{return} \StringTok{"""\textless{}?xml version = "1.0"?\textgreater{}} -\StringTok{ \textless{}document\textgreater{}} -\StringTok{ \textless{}title\textgreater{}}\SpecialCharTok{\{\}}\StringTok{\textless{}/title\textgreater{}} -\StringTok{ \textless{}content\textgreater{}}\SpecialCharTok{\{\}}\StringTok{\textless{}/content\textgreater{}} -\StringTok{ \textless{}publication\_date\textgreater{}}\SpecialCharTok{\{\}}\StringTok{\textless{}/publication\_date\textgreater{}} -\StringTok{ \textless{}/document\textgreater{}"""}\NormalTok{.}\BuiltInTok{format}\NormalTok{(} - \VariableTok{self}\NormalTok{.title,} - \VariableTok{self}\NormalTok{.content,} - \VariableTok{self}\NormalTok{.published\_at.isoformat()} -\NormalTok{ )} - - \ControlFlowTok{if}\NormalTok{ format\_type }\OperatorTok{==} \StringTok{"Markdown"}\NormalTok{:} - \ImportTok{import}\NormalTok{ markdown} - \ControlFlowTok{return}\NormalTok{ markdown.markdown(}\VariableTok{self}\NormalTok{.content)} - - \ControlFlowTok{raise} \PreprocessorTok{ValueError}\NormalTok{(}\StringTok{"Format type \textquotesingle{}}\SpecialCharTok{\{\}}\StringTok{\textquotesingle{} is not known"}\NormalTok{.}\BuiltInTok{format}\NormalTok{(format\_type))} -\end{Highlighting} -\end{Shaded} - -Lorsque nous devrons ajouter un nouveau rendu (Atom, OpenXML, -\ldots\hspace{0pt}), il sera nécessaire de modifier la classe -\texttt{Document}, ce qui n'est ni intuitif (\emph{ce n'est pas le -document qui doit savoir dans quels formats il peut être envoyés}), ni -conseillé (\emph{lorsque nous aurons quinze formats différents à gérer, -il sera nécessaire d'avoir autant de conditions dans cette méthode}). - -Une bonne pratique consiste à créer une nouvelle classe de rendu pour -chaque type de format à gérer: - -\begin{Shaded} -\begin{Highlighting}[] -\KeywordTok{class}\NormalTok{ Document:} - \KeywordTok{def} \FunctionTok{\_\_init\_\_}\NormalTok{(}\VariableTok{self}\NormalTok{, title, content, published\_at):} - \VariableTok{self}\NormalTok{.title }\OperatorTok{=}\NormalTok{ title} - \VariableTok{self}\NormalTok{.content }\OperatorTok{=}\NormalTok{ content} - \VariableTok{self}\NormalTok{.published\_at }\OperatorTok{=}\NormalTok{ published\_at} - -\KeywordTok{class}\NormalTok{ DocumentRenderer:} - \KeywordTok{def}\NormalTok{ render(}\VariableTok{self}\NormalTok{, document):} - \ControlFlowTok{if}\NormalTok{ format\_type }\OperatorTok{==} \StringTok{"XML"}\NormalTok{:} - \ControlFlowTok{return} \StringTok{"""\textless{}?xml version = "1.0"?\textgreater{}} -\StringTok{ \textless{}document\textgreater{}} -\StringTok{ \textless{}title\textgreater{}}\SpecialCharTok{\{\}}\StringTok{\textless{}/title\textgreater{}} -\StringTok{ \textless{}content\textgreater{}}\SpecialCharTok{\{\}}\StringTok{\textless{}/content\textgreater{}} -\StringTok{ \textless{}publication\_date\textgreater{}}\SpecialCharTok{\{\}}\StringTok{\textless{}/publication\_date\textgreater{}} -\StringTok{ \textless{}/document\textgreater{}"""}\NormalTok{.}\BuiltInTok{format}\NormalTok{(} - \VariableTok{self}\NormalTok{.title,} - \VariableTok{self}\NormalTok{.content,} - \VariableTok{self}\NormalTok{.published\_at.isoformat()} -\NormalTok{ )} - - \ControlFlowTok{if}\NormalTok{ format\_type }\OperatorTok{==} \StringTok{"Markdown"}\NormalTok{:} - \ImportTok{import}\NormalTok{ markdown} - \ControlFlowTok{return}\NormalTok{ markdown.markdown(}\VariableTok{self}\NormalTok{.content)} - - \ControlFlowTok{raise} \PreprocessorTok{ValueError}\NormalTok{(}\StringTok{"Format type \textquotesingle{}}\SpecialCharTok{\{\}}\StringTok{\textquotesingle{} is not known"}\NormalTok{.}\BuiltInTok{format}\NormalTok{(format\_type))} -\end{Highlighting} -\end{Shaded} - -A présent, lorsque nous devrons ajouter un nouveau format de prise en -charge, nous irons modifier la classe \texttt{DocumentRenderer}, sans -que la classe \texttt{Document} ne soit impactée. En même temps, le jour -où une instance de type \texttt{Document} sera liée à un champ -\texttt{author}, rien ne dit que le rendu devra en tenir compte; nous -modifierons donc notre classe pour y ajouter le nouveau champ sans que -cela n'impacte nos différentes manières d'effectuer un rendu. - -En prenant l'exemple d'une méthode qui communique avec une base de -données, ce ne sera pas à cette méthode à gérer l'inscription d'une -exception à un emplacement quelconque. Cette action doit être prise en -compte par une autre classe (ou un autre concept), qui s'occupera de -définir elle-même l'emplacement où l'évènement sera enregistré, que ce -soit dans une base de données, une instance Graylog ou un fichier. - -Cette manière de structurer le code permet de centraliser la -configuration d'un type d'évènement à un seul endroit, ce qui augmente -ainsi la testabilité globale du projet. - -Lorsque nous verrons les composants, le principe de responsabilité -unique deviendra le CCP - Common Closure Principle. Ensuite, lorsque -nous verrons l'architecture de l'application, ce sera la définition des -frontières (boundaries). - -\hypertarget{_open_closed}{% -\subsubsection{Open Closed}\label{_open_closed}} - -\begin{quote} -For software systems to be easy to change, they must be designed to -allow the behavior to change by adding new code instead of changing -existing code. -\end{quote} - -L'objectif est de rendre le système facile à étendre, en évitant que -l'impact d'une modification ne soit trop grand. - -Les exemples parlent d'eux-mêmes: des données doivent être présentées -dans une page web. Et demain, ce seras dans un document PDF. Et après -demain, ce sera dans un tableur Excel. La source de ces données restent -la même (au travers d'une couche de présentation), mais la mise en forme -diffère à chaque fois. - -L'application n'a pas à connaître les détails d'implémentation: elle -doit juste permettre une forme d'extension, sans avoir à appliquer une -modification (ou une grosse modification) sur son cœur. - -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: - -\begin{itemize} -\item - Une classe \texttt{Customer}, pour laquelle la méthode - \texttt{GetDiscount} ne renvoit rien; -\item - Une classe \texttt{SilverCustomer}, pour laquelle la méthode revoit - une réduction de 10\%; -\item - Une classe \texttt{GoldCustomer}, pour laquelle la même méthode - renvoit une réduction de 20\%. -\end{itemize} - -Si nous rencontrons un nouveau type de client, il suffit de créer une -nouvelle sous-classe. Cela évite d'avoir à gérer un ensemble conséquent -de conditions dans la méthode initiale, en fonction d'une autre variable -(ici, le type de client). - -Nous passerions ainsi de: - -\begin{Shaded} -\begin{Highlighting}[] -\KeywordTok{class}\NormalTok{ Customer():} - \KeywordTok{def} \FunctionTok{\_\_init\_\_}\NormalTok{(}\VariableTok{self}\NormalTok{, customer\_type: }\BuiltInTok{str}\NormalTok{):} - \VariableTok{self}\NormalTok{.customer\_type }\OperatorTok{=}\NormalTok{ customer\_type} - - -\KeywordTok{def}\NormalTok{ get\_discount(customer: Customer) }\OperatorTok{{-}\textgreater{}} \BuiltInTok{int}\NormalTok{:} - \ControlFlowTok{if}\NormalTok{ customer.customer\_type }\OperatorTok{==} \StringTok{"Silver"}\NormalTok{:} - \ControlFlowTok{return} \DecValTok{10} - \ControlFlowTok{elif}\NormalTok{ customer.customer\_type }\OperatorTok{==} \StringTok{"Gold"}\NormalTok{:} - \ControlFlowTok{return} \DecValTok{20} - \ControlFlowTok{return} \DecValTok{0} - - -\OperatorTok{\textgreater{}\textgreater{}\textgreater{}}\NormalTok{ jack }\OperatorTok{=}\NormalTok{ Customer(}\StringTok{"Silver"}\NormalTok{)} -\OperatorTok{\textgreater{}\textgreater{}\textgreater{}}\NormalTok{ jack.get\_discount()} -\DecValTok{10} -\end{Highlighting} -\end{Shaded} - -A ceci: - -\begin{Shaded} -\begin{Highlighting}[] -\KeywordTok{class}\NormalTok{ Customer():} - \KeywordTok{def}\NormalTok{ get\_discount(}\VariableTok{self}\NormalTok{) }\OperatorTok{{-}\textgreater{}} \BuiltInTok{int}\NormalTok{:} - \ControlFlowTok{return} \DecValTok{0} - - -\KeywordTok{class}\NormalTok{ SilverCustomer(Customer):} - \KeywordTok{def}\NormalTok{ get\_discount(}\VariableTok{self}\NormalTok{) }\OperatorTok{{-}\textgreater{}} \BuiltInTok{int}\NormalTok{:} - \ControlFlowTok{return} \DecValTok{10} - - -\KeywordTok{class}\NormalTok{ GoldCustomer(Customer):} - \KeywordTok{def}\NormalTok{ get\_discount(}\VariableTok{self}\NormalTok{) }\OperatorTok{{-}\textgreater{}} \BuiltInTok{int}\NormalTok{:} - \ControlFlowTok{return} \DecValTok{20} - - -\OperatorTok{\textgreater{}\textgreater{}\textgreater{}}\NormalTok{ jack }\OperatorTok{=}\NormalTok{ SilverCustomer()} -\OperatorTok{\textgreater{}\textgreater{}\textgreater{}}\NormalTok{ jack.get\_discount()} -\DecValTok{10} -\end{Highlighting} -\end{Shaded} - -En anglais, dans le texte : "\emph{Putting in simple words, the -``Customer'' class is now closed for any new modification but it's open -for extensions when new customer types are added to the project.}". - -\textbf{En résumé}: nous fermons la classe \texttt{Customer} à toute -modification, mais nous ouvrons la possibilité de créer de nouvelles -extensions en ajoutant de nouveaux types {[}héritant de -\texttt{Customer}{]}. - -De cette manière, nous simplifions également la maintenance de la -méthode \texttt{get\_discount}, dans la mesure où elle dépend -directement du type dans lequel elle est implémentée. - -Nous pouvons également appliquer ceci à notre exemple sur les rendus de -document, où le code suivant: - -\begin{Shaded} -\begin{Highlighting}[] -\KeywordTok{class}\NormalTok{ DocumentRenderer:} - \KeywordTok{def}\NormalTok{ render(}\VariableTok{self}\NormalTok{, document):} - \ControlFlowTok{if}\NormalTok{ format\_type }\OperatorTok{==} \StringTok{"XML"}\NormalTok{:} - \ControlFlowTok{return} \StringTok{"""\textless{}?xml version = "1.0"?\textgreater{}} -\StringTok{ \textless{}document\textgreater{}} -\StringTok{ \textless{}title\textgreater{}}\SpecialCharTok{\{\}}\StringTok{\textless{}/title\textgreater{}} -\StringTok{ \textless{}content\textgreater{}}\SpecialCharTok{\{\}}\StringTok{\textless{}/content\textgreater{}} -\StringTok{ \textless{}publication\_date\textgreater{}}\SpecialCharTok{\{\}}\StringTok{\textless{}/publication\_date\textgreater{}} -\StringTok{ \textless{}/document\textgreater{}"""}\NormalTok{.}\BuiltInTok{format}\NormalTok{(} -\NormalTok{ document.title,} -\NormalTok{ document.content,} -\NormalTok{ document.published\_at.isoformat()} -\NormalTok{ )} - - \ControlFlowTok{if}\NormalTok{ format\_type }\OperatorTok{==} \StringTok{"Markdown"}\NormalTok{:} - \ImportTok{import}\NormalTok{ markdown} - \ControlFlowTok{return}\NormalTok{ markdown.markdown(document.content)} - - \ControlFlowTok{raise} \PreprocessorTok{ValueError}\NormalTok{(}\StringTok{"Format type \textquotesingle{}}\SpecialCharTok{\{\}}\StringTok{\textquotesingle{} is not known"}\NormalTok{.}\BuiltInTok{format}\NormalTok{(format\_type))} -\end{Highlighting} -\end{Shaded} - -devient le suivant: - -\begin{Shaded} -\begin{Highlighting}[] -\KeywordTok{class}\NormalTok{ Renderer:} - \KeywordTok{def}\NormalTok{ render(}\VariableTok{self}\NormalTok{, document):} - \ControlFlowTok{raise} \PreprocessorTok{NotImplementedError} - -\KeywordTok{class}\NormalTok{ XmlRenderer(Renderer):} - \KeywordTok{def}\NormalTok{ render(}\VariableTok{self}\NormalTok{, document)} - \ControlFlowTok{return} \StringTok{"""\textless{}?xml version = "1.0"?\textgreater{}} -\StringTok{ \textless{}document\textgreater{}} -\StringTok{ \textless{}title\textgreater{}}\SpecialCharTok{\{\}}\StringTok{\textless{}/title\textgreater{}} -\StringTok{ \textless{}content\textgreater{}}\SpecialCharTok{\{\}}\StringTok{\textless{}/content\textgreater{}} -\StringTok{ \textless{}publication\_date\textgreater{}}\SpecialCharTok{\{\}}\StringTok{\textless{}/publication\_date\textgreater{}} -\StringTok{ \textless{}/document\textgreater{}"""}\NormalTok{.}\BuiltInTok{format}\NormalTok{(} -\NormalTok{ document.title,} -\NormalTok{ document.content,} -\NormalTok{ document.published\_at.isoformat()} -\NormalTok{ )} - -\KeywordTok{class}\NormalTok{ MarkdownRenderer(Renderer):} - \KeywordTok{def}\NormalTok{ render(}\VariableTok{self}\NormalTok{, document):} - \ImportTok{import}\NormalTok{ markdown} - \ControlFlowTok{return}\NormalTok{ markdown.markdown(document.content)} -\end{Highlighting} -\end{Shaded} - -Lorsque nous ajouterons notre nouveau type de rendu, nous ajouterons -simplement une nouvelle classe de rendu qui héritera de -\texttt{Renderer}. - -Ce point sera très utile lorsque nous aborderons les -\href{https://docs.djangoproject.com/en/stable/topics/db/models/\#proxy-models}{modèles -proxy}. - -\hypertarget{_liskov_substitution}{% -\subsubsection{Liskov Substitution}\label{_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\ldots\hspace{0pt} - -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. - -\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.). (Source: -\href{http://en.wikipedia.org/wiki/Liskov_substitution_principle}{Wikipédia}). -\end{quote} - -\begin{quote} -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: -\href{http://en.wikipedia.org/wiki/Liskov_substitution_principle}{Wikipédia -aussi}) -\end{quote} - -Ce n'est donc pas parce qu'une classe \textbf{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. - -Petit exemple pratique: si nous définissons une méthode \texttt{walk} et -une méthode \texttt{eat} sur une classe \texttt{Duck}, et qu'une -réflexion avancée (et sans doute un peu alcoolisée) nous dit que -"\emph{Puisqu'un \texttt{Lion} marche aussi, faisons le hériter de notre -classe `Canard`"}, nous allons nous retrouver avec ceci: - -\begin{Shaded} -\begin{Highlighting}[] -\KeywordTok{class}\NormalTok{ Duck:} - \KeywordTok{def}\NormalTok{ walk(}\VariableTok{self}\NormalTok{):} - \BuiltInTok{print}\NormalTok{(}\StringTok{"Kwak"}\NormalTok{)} - - \KeywordTok{def}\NormalTok{ eat(}\VariableTok{self}\NormalTok{, thing):} - \ControlFlowTok{if}\NormalTok{ thing }\KeywordTok{in}\NormalTok{ (}\StringTok{"plant"}\NormalTok{, }\StringTok{"insect"}\NormalTok{, }\StringTok{"seed"}\NormalTok{, }\StringTok{"seaweed"}\NormalTok{, }\StringTok{"fish"}\NormalTok{):} - \ControlFlowTok{return} \StringTok{"Yummy!"} - - \ControlFlowTok{raise}\NormalTok{ IndigestionError(}\StringTok{"Arrrh"}\NormalTok{)} - -\KeywordTok{class}\NormalTok{ Lion(Duck):} - \KeywordTok{def}\NormalTok{ walk(}\VariableTok{self}\NormalTok{):} - \BuiltInTok{print}\NormalTok{(}\StringTok{"Roaaar!"}\NormalTok{)} -\end{Highlighting} -\end{Shaded} - -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}. 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). - -Pour revenir à nos exemples de rendus de documents, nous aurions pu -faire hériter notre \texttt{MarkdownRenderer} de la classe -\texttt{XmlRenderer}: - -\begin{Shaded} -\begin{Highlighting}[] -\KeywordTok{class}\NormalTok{ XmlRenderer:} - \KeywordTok{def}\NormalTok{ render(}\VariableTok{self}\NormalTok{, document)} - \ControlFlowTok{return} \StringTok{"""\textless{}?xml version = "1.0"?\textgreater{}} -\StringTok{ \textless{}document\textgreater{}} -\StringTok{ \textless{}title\textgreater{}}\SpecialCharTok{\{\}}\StringTok{\textless{}/title\textgreater{}} -\StringTok{ \textless{}content\textgreater{}}\SpecialCharTok{\{\}}\StringTok{\textless{}/content\textgreater{}} -\StringTok{ \textless{}publication\_date\textgreater{}}\SpecialCharTok{\{\}}\StringTok{\textless{}/publication\_date\textgreater{}} -\StringTok{ \textless{}/document\textgreater{}"""}\NormalTok{.}\BuiltInTok{format}\NormalTok{(} -\NormalTok{ document.title,} -\NormalTok{ document.content,} -\NormalTok{ document.published\_at.isoformat()} -\NormalTok{ )} - -\KeywordTok{class}\NormalTok{ MarkdownRenderer(XmlRenderer):} - \KeywordTok{def}\NormalTok{ render(}\VariableTok{self}\NormalTok{, document):} - \ImportTok{import}\NormalTok{ markdown} - \ControlFlowTok{return}\NormalTok{ markdown.markdown(document.content)} -\end{Highlighting} -\end{Shaded} - -Mais lorsque nous ajouterons une fonction d'entête, notre rendu en -Markdown héritera irrémédiablement de cette même méthode: - -\begin{Shaded} -\begin{Highlighting}[] -\KeywordTok{class}\NormalTok{ XmlRenderer:} - \KeywordTok{def}\NormalTok{ header(}\VariableTok{self}\NormalTok{):} - \ControlFlowTok{return} \StringTok{"""\textless{}?xml version = "1.0"?\textgreater{}"""} - - \KeywordTok{def}\NormalTok{ render(}\VariableTok{self}\NormalTok{, document)} - \ControlFlowTok{return} \StringTok{"""}\SpecialCharTok{\{\}} -\StringTok{ \textless{}document\textgreater{}} -\StringTok{ \textless{}title\textgreater{}}\SpecialCharTok{\{\}}\StringTok{\textless{}/title\textgreater{}} -\StringTok{ \textless{}content\textgreater{}}\SpecialCharTok{\{\}}\StringTok{\textless{}/content\textgreater{}} -\StringTok{ \textless{}publication\_date\textgreater{}}\SpecialCharTok{\{\}}\StringTok{\textless{}/publication\_date\textgreater{}} -\StringTok{ \textless{}/document\textgreater{}"""}\NormalTok{.}\BuiltInTok{format}\NormalTok{(} - \VariableTok{self}\NormalTok{.header(),} -\NormalTok{ document.title,} -\NormalTok{ document.content,} -\NormalTok{ document.published\_at.isoformat()} -\NormalTok{ )} - -\KeywordTok{class}\NormalTok{ MarkdownRenderer(XmlRenderer):} - \KeywordTok{def}\NormalTok{ render(}\VariableTok{self}\NormalTok{, document):} - \ImportTok{import}\NormalTok{ markdown} - \ControlFlowTok{return}\NormalTok{ markdown.markdown(document.content)} -\end{Highlighting} -\end{Shaded} - -A nouveau, lorsque 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. \hypertarget{_interface_segregation_principle}{% \subsubsection{Interface Segregation diff --git a/chapters/maintenability.tex b/chapters/maintenability.tex index d2a75ff..41380cd 100644 --- a/chapters/maintenability.tex +++ b/chapters/maintenability.tex @@ -355,38 +355,36 @@ Des équivalents à ces directives existent au niveau des composants, puis au ni \item Reuse/release équivalence principle, \item - Common Closure Principle, + \textbf{CCP} - Common Closure Principle, \item - Common Reuse Principle. + \textbf{CRP} - Common Reuse Principle. \end{enumerate} \includegraphics{images/arch-comp-modules.png} \subsection{Single Responsility Principle} - Le principe de responsabilité unique conseille de disposer de concepts ou domaines d'activité qui ne s'occupent chacun que d'une et une seule chose. Ceci rejoint (un peu) la \href{https://en.wikipedia.org/wiki/Unix_philosophy}{Philosophie Unix}, documentée par Doug McIlroy et qui demande de "\emph{faire une seule chose, mais de le faire bien}" \cite{unix_philosophy}. -Une classe ou un élément de programmation ne doit donc pas avoir plus d'une raison de changer. - -Il est également possible d'étendre ce principe en fonction d'acteurs: - -\begin{quote} -A module should be responsible to one and only one actor. \cite{clean_architecture} - ---- Robert C. Martin -\end{quote} +Selon ce principe, une classe ou un élément de programmation ne doit donc pas avoir plus d'une seule raison de changer. Plutôt que de centraliser le maximum de code à un seul endroit ou dans une seule classe par convenance ou commodité \footnote{Aussi appelé \emph{God-Like object}}, le principe de responsabilité unique suggère que chaque classe soit responsable d'un et un seul concept. -Une manière de voir les choses consiste à différencier les acteurs ou les intervenants: imaginez disposer d'une classe représentant des données de membres du personnel. -Ces données pourraient être demandées par trois acteurs, le CFO, le CTO et le COO. Ceux-ci ont tous besoin de données et d'informations relatives à une même base de données centralisées, mais ont chacun besoin d'une représentation différente ou de traitements distincts. \cite{clean_architecture} +Une manière de voir les choses consiste à différencier les acteurs ou les intervenants: imaginez disposer d'une classe représentant des données de membres du personnel; ces données pourraient être demandées par trois acteurs: -Nous sommes d'accord qu'il s'agit à chaque fois de données liées aux employés, mais elles vont un cran plus loin et pourraient nécessiter des -ajustements spécifiques en fonction de l'acteur concerné et de la manière dont il souhaite disposer des données. +\begin{enumerate} + \item Le CFO (Chief Financial Officer) + \item Le CTO (Chief Technical Officer) + \item Le COO (Chief Operating Officer) +\end{enumerate} + +Chacun d'entre eux aura besoin de données et d'informations relatives à ces membres du personnel, et provenant donc d'une même source de données centralisée. +Mais chacun d'entre eux également besoin d'une représentation différente ou de traitements distincts. \cite{clean_architecture} + +Nous sommes d'accord qu'il s'agit à chaque fois de données liées aux employés; celles-ci vont cependant un cran plus loin et pourraient nécessiter des ajustements spécifiques en fonction de l'acteur concerné et de la manière dont il souhaite disposer des données. Dès que possible, identifiez les différents acteurs et demandeurs, en vue de prévoir les modifications qui pourraient être demandées par l'un d'entre eux. Dans le cas d'un élément de code centralisé, une modification induite par un des acteurs pourrait ainsi avoir un impact sur les données utilisées par les autres. @@ -426,6 +424,313 @@ Une méthode \texttt{render} permet également de proposer (très grossièrement \caption{Un convertisseur de document un peu bateau} \end{listing} + +Lorsque nous devrons ajouter un nouveau rendu (Atom, OpenXML, ...), il sera nécessaire de modifier la classe \texttt{Document}. +Ceci n'est: + +\begin{enumerate} + \item Ni intuitif: \emph{ce n'est pas le document qui doit savoir dans quels formats il peut être converti} + \item Ni conseillé: \emph{lorsque nous aurons quinze formats différents à gérer, il sera nécessaire d'avoir autant de conditions dans cette méthode} +\end{enumerate} + +En suivant le principe de responsabilité unique, une bonne pratique consiste à créer une nouvelle classe de rendu pour chaque type de format à gérer: + +\begin{listing}[H] + \begin{minted}[tabsize=4]{Python} + class Document: + def __init__(self, title, content, published_at): + self.title = title + self.content = content + self.published_at = published_at + + class DocumentRenderer: + def render(self, document): + if format_type == "XML": + return """ + + {} + {} + {} + """.format( + self.title, + self.content, + self.published_at.isoformat() + ) + + if format_type == "Markdown": + import markdown + return markdown.markdown(self.content) + + raise ValueError("Format type '{}' is not known".format(format_type)) + \end{minted} + \caption{Isolation du rendu d'un document par rapport à sa modélisation} +\end{listing} + + +A présent, lorsque nous devrons ajouter un nouveau format de prise en charge, il nous suffira de modifier la classe \texttt{DocumentRenderer}, sans que la classe \texttt{Document} ne soit impactée. +En même temps, le jour où une instance de type \texttt{Document} sera liée à un champ \texttt{author}, rien ne dit que le rendu devra en tenir compte; nous modifierons donc notre classe pour y ajouter le nouveau champ sans que cela n'impacte nos différentes manières d'effectuer un rendu. + +Un autre exemple consiterait à faire communiquer une méthode avec une base de données: ce ne sera pas à cette méthode à gérer l'inscription d'une exception à un emplacement spécifique (emplacement sur un disque, ...): cette action doit être prise en compte par une autre classe (ou un autre concept ou composant), qui s'occupera de définir elle-même l'emplacement où l'évènement sera enregistré, que ce soit dans une base de données, une instance Graylog ou un fichier. +Cette manière de structurer le code permet de centraliser la configuration d'un type d'évènement à un seul endroit, ce qui augmente ainsi la testabilité globale du projet. + +L'équivalent du principe de responsabilité unique au niveau des composants sera le \texttt{Common Closure Principle} \index{CCP}. +Au niveau architectural, cet équivalent correspondra aux frontières. + +\subsection{Open-Closed} + +\begin{quote} + For software systems to be easy to change, they must be designed to allow the behavior to change by adding new code instead of changing existing code. +\end{quote} + +L'objectif est de rendre le système facile à étendre, en limitant l'impact qu'une modification puisse avoir. + +Reprendre notre exemple de modélisation de documents parle de lui-même: + +\begin{enumerate} + \item Des données que nous avons converties dans un format spécifique pourraient à présent devoir être présentées dans une page web. + \item Et demain, ce sera dans un document PDF. + \item Et après demain, dans un tableur Excel. +\end{enumerate} + +La source de ces données reste la même (au travers d'une couche de présentation): c'est leur mise en forme qui diffère à chaque fois. +L'application n'a pas à connaître les détails d'implémentation: elle doit juste permettre une forme d'extension, sans avoir à appliquer quelconque modification en son cœur. + +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 comme nous l'avons initisée plus tôt 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 prendre un nouvel exemple, nous pourrions ainsi définir trois classes: + +\begin{itemize} +\item + Une classe \texttt{Customer}, pour laquelle la méthode \texttt{GetDiscount} ne renvoit rien; +\item + Une classe \texttt{SilverCustomer}, pour laquelle la méthode revoit une réduction de 10\%; +\item + Une classe \texttt{GoldCustomer}, pour laquelle la même méthode renvoit une réduction de 20\%. +\end{itemize} + +Si nous devions rencontrer un nouveau type de client, il nous suffira de créer une nouvelle sous-classe, implémentant la réduction que nous souhaitons lui offrir. +Ceci évite d'avoir à gérer un ensemble conséquent de conditions dans la méthode initiale, en fonction d'une variable ou d'un paramètre - ici, le type de client. + +Nous passerions ainsi de ceci: + +\begin{listing}[H] + \begin{minted}{Python} + class Customer(): + def __init__(self, customer_type: str): + self.customer_type = customer_type + + def get_discount(customer: Customer) -> int: + if customer.customer_type == "Silver": + return 10 + elif customer.customer_type == "Gold": + return 20 + return 0 + + >>> jack = Customer("Silver") + >>> jack.get_discount() + 10 + \end{minted} +\end{listing} + +A ceci: + +\begin{listing}[H] + \begin{minted}{Python} + class Customer(): + def get_discount(self) -> int: + return 0 + + class SilverCustomer(Customer): + def get_discount(self) -> int: + return 10 + + class GoldCustomer(Customer): + def get_discount(self) -> int: + return 20 + + >>> jack = SilverCustomer() + >>> jack.get_discount() + 10 + \end{minted} +\end{listing} + +En anglais, dans le texte : "\emph{Putting in simple words, the ``Customer'' class is now closed for any new modification but it's open for extensions when new customer types are added to the project.}". + +\textbf{En résumé}: nous fermons la classe \texttt{Customer} à toute modification, mais nous ouvrons la possibilité de créer de nouvelles extensions en ajoutant de nouveaux types héritant de \texttt{Customer}. + +De cette manière, nous simplifions également la maintenance de la méthode \texttt{get\_discount}, dans la mesure où elle dépend directement du type dans lequel elle est implémentée. + +Nous pouvons également appliquer ceci à notre exemple sur les rendus de document, où le code suivant: + +\begin{listing}[H] + \begin{minted}[tabsize=4]{Python} + class Document: + def __init__(self, title, content, published_at): + self.title = title + self.content = content + self.published_at = published_at + + def render(self, format_type): + if format_type == "XML": + return """ + + {} + {} + {} + """.format( + self.title, + self.content, + self.published_at.isoformat() + ) + + if format_type == "Markdown": + import markdown + return markdown.markdown(self.content) + + raise ValueError( + "Format type '{}' is not known".format(format_type) + ) + \end{minted} +\end{listing} + +devient le suivant: + +\begin{listing}[H] + \begin{minted}[tabsize=4]{Python} + class Renderer: + def render(self, document): + raise NotImplementedError + + class XmlRenderer(Renderer): + def render(self, document) + return """ + + {} + {} + {} + """.format( + 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{Notre convertisseur suit le principe Open-Closed} +\end{listing} + +Lorsque nous ajouterons notre nouveau type de rendu, nous ajouterons simplement une nouvelle classe de rendu qui héritera de \texttt{Renderer}. + +\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. + +\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.). + +--- \href{http://en.wikipedia.org/wiki/Liskov_substitution_principle}{Wikipédia}. +\end{quote} + +\begin{quote} +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. + +--- \href{http://en.wikipedia.org/wiki/Liskov_substitution_principle}{Wikipédia aussi} +\end{quote} + +Ce n'est donc pas parce qu'une classe \textbf{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. + +Petit exemple pratique: si nous définissons une méthode \texttt{make\_some\_noise} et une méthode \texttt{eat} sur une classe \texttt{Duck}, et qu'une réflexion avancée (et sans doute un peu alcoolisée) nous dit que "\emph{Puisqu'un \texttt{Lion} fait aussi du bruit, faisons le hériter de notre classe `Canard`"}, nous allons nous retrouver avec ceci: + +\begin{listing}[H] + \begin{minted}[tabsize=4]{Python} + class Duck: + def make_some_noise(self): + print("Kwak") + + def eat(self, thing): + if thing in ("plant", "insect", "seed", "seaweed", "fish"): + return "Yummy!" + raise IndigestionError("Arrrh") + + class Lion(Duck): + def make_some_noise(self): + print("Roaaar!") + \end{minted} + \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}. +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). + +Pour revenir à nos exemples de rendus de documents, nous aurions pu faire hériter notre \texttt{MarkdownRenderer} de la classe \texttt{XmlRenderer}: + +\begin{listing}[H] + \begin{minted}[tabsize=4]{Python} + class XmlRenderer: + def render(self, document) + return """ + + {} + {} + {} + """.format( + document.title, + document.content, + document.published_at.isoformat() + ) + + class MarkdownRenderer(XmlRenderer): + def render(self, document): + import markdown + return markdown.markdown(document.content) + \end{minted} + \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 """"" + + def render(self, document) + return """{} + + {} + {} + {} + """.format( + self.header(), + document.title, + document.content, + document.published_at.isoformat() + ) + + class MarkdownRenderer(XmlRenderer): + def render(self, document): + import markdown + return markdown.markdown(document.content) + \end{minted} + \caption{... et il a mal à l'entête} +\end{listing} + +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. + \section{Tests unitaires et d'intégration} \begin{quote} diff --git a/chapters/python.tex b/chapters/python.tex index bc3efda..fdd6a67 100644 --- a/chapters/python.tex +++ b/chapters/python.tex @@ -144,7 +144,7 @@ protocoles du langage. \section{The Zen of Python} -\begin{listing}[!h] +\begin{listing}[H] \begin{verbatim} >>> import this The Zen of Python, by Tim Peters