Blog ENI : Toute la veille numérique !
🎁 Jusqu'au 25/12 : 1 commande de contenus en ligne
= 1 chance de gagner un cadeau*. Cliquez ici
🎁 Jusqu'au 31/12, recevez notre
offre d'abonnement à la Bibliothèque Numérique. Cliquez ici
  1. Livres et vidéos
  2. Python 3
  3. Motifs de conception
Extrait - Python 3 Traitement de données et techniques de programmation (2e édition)
Extraits du livre
Python 3 Traitement de données et techniques de programmation (2e édition) Revenir à la page d'achat du livre

Motifs de conception

Définition

1. Positionnement par rapport à la notion d’objet

Un objet ne fait pas l’application. Ce qui compte autant que les mécanismes permettant de travailler sur les objets, ce sont ceux qui permettent de gérer la manière dont ils interagissent en réponse à une problématique et à destination d’une fonctionnalité.

Pour ce faire, on utilise, consciemment ou non, des motifs de conception qui, chacun, correspondent à une méthode pour faire interagir des objets entre eux. Il y en a de plusieurs sortes, destinées à gérer des problématiques qui peuvent sembler parfois similaires mais qui ont toujours en réalité un contexte ou une destination particulière. Ils sont tellement nombreux qu’il faudrait un livre uniquement pour les présenter tous et cela n’aurait qu’un intérêt limité.

La plupart d’entre eux sont conçus pour répondre à une problématique précise. Ils sont la synthèse de retours d’expérience importants et significatifs et sont décrits avec précision par l’utilisation de plusieurs objets dont on décrit les rôles et les interactions.

Leur connaissance permet de standardiser la conception logicielle, de mettre en place des outils pour reproduire les bonnes solutions et d’améliorer...

Motifs de création

1. Singleton

Un singleton est, en mathématiques, un ensemble ne contenant qu’un seul élément. Avant que les patrons de conception ne soient formalisés, ce vocabulaire existait en Python pour des ensembles, listes ou n-uplets d’un seul élément et le mot singleton est utilisé dans la documentation officielle avec ce sens lié aux mathématiques.

Dans la documentation officielle, le patron de conception Singleton est implémenté en utilisant un module et en positionnant les données partagées en son sein (https://docs.python.org/3/faq/programming.html#how-do-i-share-global-variables-across-modules) :

# config.py: 
x = 0 # Default value of the 'x' configuration setting 
 
# mod.py: 
 
import config 
config.x = 1 

# main.py: 
 
import config 
import mod 
print(config.x) 

En informatique, de manière générale, il s’agit d’une classe ne possédant qu’une seule instance. En Python, il existe le singleton None qui est de la classe NoneType :

>>> NoneType = type(None) 
>>> NoneType() 
None 

Dans le modèle objet, le patron de conception peut être implémenté ainsi :

>>> class Singleton: 
...     _instance = None 
...     def __new__(cls): 
...         if cls._instance is None: 
...             cls._instance = object.__new__(cls) 
...         return cls._instance 
...   
>>> object() is object(), Singleton() is Singleton() 
(False, True) 

Il faut noter que, la plupart du temps, les langages de programmation utilisent le singleton pour pallier le fait que leur modèle objet n’est pas suffisamment souple pour pouvoir gérer ce que Python fait déjà à l’aide de ses méthodes de classes.

Ainsi, l’utilisation d’un singleton en Python est extrêmement rare.

Pour les programmeurs débutant en Python et ne connaissant pas encore la méthode __new__ ou les subtilités du modèle objet Python, ou bien pour ceux qui ont l’habitude d’un autre langage, la solution proposée se rapproche plus de ces autres langages :

>>> class Singleton: 
...     _instance = None ...

Motifs de structuration

1. Adaptateur

Présentation de la problématique

Pour avoir des traitements génériques, lorsque l’on conçoit une architecture, la solution permettant de disposer d’une interface commune et de créer les objets qui vont fournir cette interface est l’idéal.

Seulement, on travaille rarement uniquement avec des objets que l’on a conçus, on travaille également avec des bibliothèques tierces, ou des objets conçus préalablement qui sont adaptés à une problématique autre que la nôtre.

Dans tous les cas, il n’est pas possible de reprendre ces objets pour les faire entrer dans un moule qui satisfait parfaitement nos besoins.

Dans ce cadre-là, une solution largement diffusée est de créer des adaptateurs qui vont adapter le comportement des objets que l’on a à une interface unique.

Solution

Voici un exemple de classes qui sont parfaitement adaptées à une certaine utilisation et que nous souhaitons reprendre, mais utiliser d’une manière générique :

>>> class Chien: 
...     def aboyer(self): 
...         print('Ouaff') 
...  
>>> class Chat: 
...     def miauler(self): 
...         print('Miaou') 
...  
>>> class Cheval: 
...     def hennir(self): 
...         print('Hiiii') 
...  
>>> class Cochon: 
...     def grogner(self): 
...         print('Gruik') 
... 

Notre souhait est de faire « parler » ces animaux d’une manière générique.

Voici une classe, qui correspond à l’interface souhaitée :

>>> import abc 
>>> class Animal(metaclass=abc.ABCMeta): 
...     @abc.abstractmethod 
...     def faireDuBruit(self): 
...         return 
... 

On pourrait alors reprendre les quatre classes précédentes et les réécrire avec la même méthode, mais cela entraînerait une perte au niveau sémantique alors que c’est potentiellement utile pour d’autres utilisations....

Motifs de comportement

1. Chaîne de responsabilité

Présentation de la problématique

Le motif de conception nommé chaîne de responsabilité permet de créer une chaîne entre différents composants qui traitent une donnée. Ainsi, chaque composant reçoit une donnée, la traite s’il le peut, et la transmet au composant suivant dans la chaîne, le tout sans se préoccuper de savoir si le message va intéresser son successeur ou pas.

Solution

Voici un composant autonome qui gère le traitement ou non d’une donnée en fonction de conditions qui lui sont passées à l’initialisation :

>>> class Composant: 
...     def __init__(self, name, conditions): 
...         self.name = name 
...         self.conditions = conditions 
...         self.next = None 
...     def setNext(self, next): 
...         self.next = next 
...     def traitement(self, condition, message): 
...         if condition in self.conditions: 
...             print('Traitement du message %s par %s' % 
(message, self.name)) 
...         if self.next is not None: 
...             self.next.traitement(condition, message) 
... 

Voici comment créer trois composants :

>>> c0 = Composant('c0', [1, 2]) 
>>> c1 = Composant('c1', [1]) 
>>> c2 = Composant('c2', [2]) 

Comment créer la chaîne de dépendance :

>>> c0.setNext(c1) 
>>> c1.setNext(c2) 

Et le résultat lorsque l’on donne une condition et un message :

>>> c0.traitement(1, 'Test 1') 
Traitement du message Test 1 par c0 
Traitement du message Test 1 par c1 
>>> c0.traitement(2, 'Test 2') 
Traitement du message Test 2 par c0 
Traitement du message Test 2 par c2 

Conséquences

Cette méthodologie est un moyen simple de créer un découplage entre fonctionnalités séquentiellement exécutées.

2. Commande

Présentation de la problématique

Le modèle objet présente...

ZCA

1. Rappels

La ZCA est la Zope Component Architecture et est un ensemble de bibliothèques indépendantes qui permettent de créer une architecture entre composants.

2. Adaptateur

Déclaration

Voici, déclarés conformément aux usages de la ZCA, deux interfaces et deux objets :

>>> from zope.interface import Interface 
>>> from zope.interface import Attribute 
>>> from zope.interface import implements 
>>> class Ichien(Interface): 
...     nom = Attribute("""Nom du chien""") 
...     def aboyer(filename) 
...         """Méthode permettant de le faire aboyer"""  
...  
>>> class Chien(object): 
...     implements(IChien) 
...     nom = u'' 
...     def __init__(self, nom): 
...         self.nom = nom 
...     def aboyer(self): 
...         """Méthode permettant de le faire aboyer""" 
...         print('Ouaff') 
...  
>>> class Ichat(Interface): 
...     nom = Attribute("""Nom du chat""") 
...     def miauler(filename): 
...         """Méthode permettant de le faire miauler"""  
...  
>>> class Chat(object): 
...     implements(IChat) 
...     nom = u'' 
...     def __init__(self, nom): 
...         self.nom = nom 
...     def miauler(self): 
...         """Méthode permettant de le faire miauler""" 
...         print('Miaou') 
... 

L’idée de cet exemple est d’adapter ces deux objets au sein d’une seule et même classe dont on va commencer par créer une interface. Cette adaptation est simplement réalisée par l’utilisation de la fonction adapts.

Voici...