De nombreuses applications financières doivent persister des données après leurs traitements algorithmiques. Par exemple, la gestion des portefeuilles demande d’enregistrer localement les résultats des ordres venant de différentes sources externes après le nettoyage des données dans les systèmes de stockage très variés : le système de gestion de base de données relationnelles (SGBDR), le système de gestion de base de données orienté objet (OODBMS), les documents XML, les fichiers Excel, le service Web, LDAP, etc. L’utilisateur pourrait appeler une méthode dans un module commun, qui contient tous les types d’enregistrement, en choisissant une manière de stockage désirée au gré du besoin. Néanmoins, si elle comprend tous ces types d’implémentations concrètes dont chacune propose une interface spécifique, elle ne sera pas facile à être maintenue ni étendue aux nouvelles fonctionnalités de persistance chez le client ultérieurement. Car cela viole le principe de responsabilité unique dans SOLID [1], qui stipule qu'un module, une classe ou même une méthode doit avoir une responsabilité unique et bien définie. Il ne devrait faire qu'une seule chose et n'avoir qu'une seule raison de changer. Sinon, quoi qu’elle soit dérisoire ou importante, une mise à jour sur une seule façon de stockage pourrait introduire la modification de toute la classe, voire tout le module, en risquant d’introduire les erreurs désastreuses.
Du coup en face de tel défi, le patron de conception dans la famille créatrice [2] « l’usine » : la fabrique (factory method en anglais) joue un rôle majeur et efficace en construisant tous les types d’objet qui s’occupe d’une persistance particulière à la sortie de résultat. Du fait, il y a une super classe abstraite de persistance avec plusieurs sous-classes concrètes et en fonction de l'argument d’entrée, la méthode de création de la classe factory va déterminer et retourner l'une des sous-classes. Cette conception retire la responsabilité de l'instanciation du programme chez le client aux sous-classes dans la fabrique. Sinon, au gré des contextes, elle pourrait renvoyer plusieurs fois la même instance [3, 4], ou renvoyer une sous-classe plutôt qu'un objet de ce type exact.
Conception
Le diagramme ULM illustre deux composants principaux dans le patron de conception Fabrique en Python 3, dont le 1e est un point d’accès de classe Factory qui décide et retourne l’objet final et le 2e comprend plusieurs classes concrètes dérivant d’une même interface, qui s’occupent d’instancier les objets.
Le client qui cherche à créer un objet accède à la classe Factory, dans laquelle la méthode statique make est appelée avec le nom de l’objet désiré. Cette méthode statique connaît tous les types d’instance des classes dérivant de l’interface en commun. Dès son analyse du besoin du client, la méthode renvoie directement la référence de l’objet demandé, lorsque cet objet est construit par la méthode de classe create implémentée dans une sous-classe concrète de l’interface abstraite.
Par conséquent, le principe de responsabilité unique est bien satisfait en faveur de la conception.
Développement
En Python 3 afin de distinguer clairement les types d’objet lors des appels dans le code, on met en place une classe énumérée PersistType qui donne tous les noms de persistance. Par exemple, dans cette application rudimentaire on a trois types de persistance dont les libraires en Python sont disponibles pour les ordres : XML [5], SGBDR [6], OODBMS [7].
Afin de simplifier la démonstration, on garde simplement les attributs basiques dans la classe Order pour les instances d’ordres. Du fait, à part les identifiants uniques pour chacun, on définit la date d’exécution, le montant d’exécution et la devise chez l’ordre. En plus, dans cet exemple le portefeuille ne contient que dix ordres exécutés il y a un jour. Les montants des ordres à la source sont générés de la façon aléatoire à la distribution uniforme [8] entre -100000,0 et 100000,0, lorsque leurs devises sont toujours en euro. Sinon, comme OODBMS demande un héritage de persistance dans sa librairie pour les ordres, on conçoit la classe OrderZo pour s’adapter à son objet d’enregistrement.
Dans un premier temps, on développe le 1e composant dans le schéma de la conception. En tant qu’un point d’accès, la classe PersistanceFactory est mise au point pour fournir les objets de persistance dont on a besoin. En sachant le nom du type de persistance, on y appelle la méthode statique make qui vérifie les conditions. Grâce à cette méthode, on peut trouver la classe de persistance pertinente à poursuivre l’instanciation concrète chez le 2e composant de la conception.
En effet, toutes ces classes de persistance ayant une méthode de classe create pour créer ses propres objets sont concrètement implémentées à partir de la interface abstraite IPersistance. Sinon, on y met en œuvre une méthode persist qui est responsable d’enregistrer les données des ordres pendant nos tests. Dès qu’une persistance instanciée sera été retournée par la méthode make chez la classe PersistanceFactory, la mise en persistance des données s’apprête à avoir lieu si la méthode persist est appelée.
Les tests unitaires se valident par les types des objets créés au gré du besoin, tandis que les affichages des différentes phases de persistance pour dix ordres sont mis en lumière en-dessous :
Comme tous les types de persistance sont testés par trois itérations dans le programme, les objets de persistance XML, SGBDR et OODBMS sont instanciés l’un après l’autre avant leurs enregistrements de données des ordres.
Le ficher XML.xml stocke les ordres ainsi :
Dans la base de Sqlite3, nous pouvons saisir toutes les données dans la table ORDERS :
Les résultats gérés chez OODBMS peuvent être décodés ainsi :
De cette façon, on réussit à réaliser les deux composants principaux dans le patron de conception Fabrique en Python 3, qui surmonte la violation du principe de responsabilité unique pour la persistance des ordres chez l’utilisateur montrée au début de l’article. Désormais on pourrait enrichir à l’aise autant de types d’objet que l’on veut aux contextes plus compliqués, tout en ne rajoutant les conditions de justification et les classes dérivées que dans les deux composants respectivement, lorsque les anciennes fonctionnalités opérationnelles ne risquent pas d’être impactées dans les applications.
Conclusion
Dans cet article, on met en évidence la nécessité du patron de conception Fabrique contre l’issue de persistance dans notre contexte en question. Son diagramme ULM comprend deux principaux composants qui respectent le principe de responsabilité unique. Ce patron de conception créateur est implémenté et testé en Python 3 pour une application rudimentaire, lorsque l’enrichissement des nouvelles créations pourrait passer à l’échelle dans le code en sécurité.
Références
[1] https://www.invivoo.com/blog/lart-clean-code-environnement-java/
[2] https://www.invivoo.com/blog/design-patterns-patrons-conception/
[3] https://www.invivoo.com/blog/singleton-design-patterns-part1/
[4] https://www.invivoo.com/blog/singletons-design-patterns-partie-2/
[5] https://www.tutorialspoint.com/python/python_xml_processing.htm
[6] https://docs.python.org/3/library/sqlite3.html
[7] https://zodb.org/en/latest/
[8] https://docs.python.org/fr/3/library/random.html