Accédez en illimité à
tous nos livres & vidéos, sur l'IA, le dev, les réseaux... Cliquez ici
Bibliothèque Numérique ENI :
tous nos livres & vidéos, en accès illimité 24h/24. Cliquez ici
  1. Livres & vidéos
  2. Apprendre la Programmation Orientée Objet avec le langage Python
  3. Un aperçu de quelques design patterns
Extrait - Apprendre la Programmation Orientée Objet avec le langage Python (avec exercices pratiques et corrigés) (3e édition)
Extraits du livre
Apprendre la Programmation Orientée Objet avec le langage Python (avec exercices pratiques et corrigés) (3e édition) Revenir à la page d'achat du livre

Un aperçu de quelques design patterns

Introduction

Les notions de la programmation orientée objet qui ont été vues jusqu’ici sont des briques élémentaires. Il s’agit du matériau de base pour bâtir avec robustesse et efficacité des solutions afin de résoudre des problèmes courants de programmation : comment effectuer un traitement sur une hiérarchie de classes sans avoir à modifier leur implémentation, comment garantir de n’avoir qu’une seule et unique instance de telle classe dans tout le programme, comment changer la façon dont telle donnée est représentée en utilisant le moins de code possible... ?

En combinant les briques élémentaires de la POO selon certains schémas déjà éprouvés, les classes et les relations entre elles peuvent former de fantastiques outils répondant avec fiabilité et élégance à ces problèmes courants de programmation. Ces combinaisons sont appelées des « design patterns » ou « patrons de conception » en français. Le terme anglais étant cependant le plus répandu, c’est celui-là qui sera utilisé dans la suite de ce chapitre.

Un design pattern n’est pas un morceau de code que l’on peut copier-coller dans un programme afin de le faire fonctionner...

Singleton

L’utilisation de variables globales est une pratique à éviter : elles polluent l’espace de noms global, elles occupent des ressources tout le long de la vie du programme, elles sont difficiles à contrôler puisque tout le monde peut y avoir accès. Cependant, dans certains cas précis, il est beaucoup plus commode de pouvoir accéder à des informations de façon globale plutôt que de les transférer d’objet en objet.

class Toulouse:  
 
    def __init__(self, sol):  
        self.sol = sol  
 
    def meteo(self):  
        if (self.sol):  
           return "Ensoleillé"  
        return "Nuageux"  
 
class France:  
 
    def __init__(self, sol):  
        self.sol = sol  
        self.villes = [Toulouse(sol)]  
 
class Europe:  
 
    def __init__(self, sol):  
        self.sol = sol  
        self.pays = [France(sol)]  
 
class Terre:  
 
    def __init__(self, sol):  
        self.sol = sol  
        self.continents = [Europe(sol)]  
 
class SystemeSolaire:  
 
    def __init__(self, sol):  
        self.sol = Soleil()  
        self.terre = Terre(sol) 

Dans cet exemple, l’instance du Soleil appartient en toute logique au système solaire. On veut également que chaque lieu représenté par les classes déclarées dans l’exemple puisse avoir accès à cette instance - unique - du Soleil pour effectuer d’éventuels traitements (affichage de la météo dans une ville par exemple). Or, si l’on veut éviter d’avoir une instance globale, il faut impérativement que chaque classe qui doit accéder au Soleil reçoive cette instance dans son constructeur pour la stocker et pouvoir l’utiliser. D’où la ribambelle de constructeurs prenant le Soleil comme deuxième argument. L’utilisation d’un objet global simplifierait grandement...

Visiteur

1. Présentation

Les structures de données sont omniprésentes lorsqu’il s’agit de manipuler des informations (listes, dictionnaires, arbres, tables, etc.). De nombreux containers existent pour offrir différentes performances et fonctionnalités en accord avec la nature des données stockées. Le besoin de parcourir ces données et d’effectuer des opérations sur celles-ci est très classique.

Un fichier XML par exemple peut être assimilé à une structure arborescente (relation parent-enfants) dont chaque élément ou nœud est une instance de classe. Par exemple :

<zoo>  
  <animaux>  
    <lion nom="Lawrence" age="10" sexe="M"/>  
    <serpent nom="Severus" age="2" sexe="F"/>  
    <pingouin nom="Tux" age="5" sexe="F"/>  
  </animaux>  
</zoo> 

On peut transposer cet arbre en objets selon le design suivant :

images/06RI02.png

Transformer un contenu textuel en structure objet permet une manipulation plus facile et plus intuitive si l’on désire effectuer des traitements métier sur les entités. Ces traitements peuvent être divers et variés, comme sauvegarder ces objets dans un fichier d’un certain format, ou les représenter dans une interface graphique, ou bien encore modifier les attributs de chacun en fonction d’une certaine condition. Le principe global reste le même :

  • accéder au premier élément ;

  • effectuer un traitement dessus ;

  • passer à l’élément suivant ;

  • recommencer.

La difficulté réside dans la différence de traitement en fonction du type concret des éléments. Pour reprendre l’exemple d’un rendu graphique des objets : il serait préférable d’appeler une fonction différente pour chaque type d’objets différent. Dans le cas d’une hiérarchie de classes, il existe déjà un mécanisme pour ce faire : le polymorphisme, qui permet d’appeler une méthode différente selon le type réel de l’objet. Mais ce mécanisme...

Modèle - Vue - Contrôleur (MVC)

1. Présentation

Encore une fois, un concept primordial de la POO est la séparation des responsabilités. Lorsque ces responsabilités sont nombreuses et très décorrélées, il est primordial d’abstraire le plus possible les interfaces entre les classes concernées. Un exemple typique concerne les IHM (interfaces homme-machine). Imaginons un gros projet où chaque modification est coûteuse en temps de tests et de déploiement. Dans ce projet, tout fonctionne parfaitement : des composants graphiques affichant la liste des prochains vols pour telle destination (par exemple) à la communication avec les serveurs des aéroports pour mettre cette liste à jour. Malheureusement, un bogue est découvert sur l’objet qui représente la liste des vols, et il faut donc le remplacer par un autre conteneur, dont les méthodes d’accès sont légèrement différentes.

Si jamais ce logiciel, pourtant fonctionnel, a été modélisé comme un monolithe, à savoir sans séparer les responsabilités, alors absolument tout est à retester et à redéployer. Tandis que si, comme le préconise le design pattern MVC, les données et la façon dont elles sont stockées (le modèle), les composantes de l’interface graphique (la vue) et la couche de communication entre les deux (le contrôleur) sont implémentées dans des modules distincts, seul le module concerné, donc le modèle (celui qui contient effectivement les données), est à retester et à redéployer. On divise donc approximativement la durée des tests par 3.

Ces trois entités ne sont pas forcément des classes à proprement parler, mais des briques du logiciel :

  • Le modèle est responsable du stockage et de la mise à jour des données manipulées par le logiciel. On peut l’imaginer comme une base de données dans laquelle iront piocher les autres classes, bien qu’en réalité, les informations puissent être stockées dans des conteneurs (listes, tableaux, dictionnaires), dans des fichiers, sur un serveur distant, etc. L’important est que, d’un point...

Abstract Factory

1. Présentation

La notion de dispatch dynamique est omniprésente dans la programmation orientée objet, et une grande partie des design patterns en sont des cas d’application. Le design pattern Visiteur, par exemple, montre l’utilité d’un double dispatch. Abstract Factory (fabrique abstraite), pour sa part, applique la méthode du dispatch à la construction des objets.

Mais avant de décrire Abstract Factory, parlons de Factory. Même si ces deux termes sont souvent utilisés de manière interchangeable, il existe à l’origine une différence. Le terme « fabrique » désigne toute méthode, tout pattern reposant sur l’idée d’abstraire la création des objets. Vous connaissez désormais une fabrique : le singleton, qui délègue l’instanciation à une méthode vérifiant si la classe n’a pas déjà été instanciée. L’idée de fabrique est de permettre de séparer la responsabilité d’utilisation d’un objet et celle de sa création.

Imaginons par exemple une brique logicielle chargée de faire l’interface entre un document et plusieurs imprimantes fonctionnant différemment. Il est souhaitable que l’interface vue par le document soit la même quelle que soit l’imprimante ciblée : peu importe comment, peu importe l’imprimante, l’objectif est que le document soit imprimé. Pour ce faire, rien de plus simple : il suffit d’avoir une classe abstraite Imprimante et de représenter chaque imprimante par une classe qui en hérite.

images/06RI06.png

Le problème est que l’on ne veut pas laisser le client créer l’objet ImprimanteA ou ImprimanteB. Dans l’idéal, savoir quel est l’objet exact devrait n’être qu’un...