I. Contexte
Pour gérer et analyser les portefeuilles des fonds qui contiennent de nombreux ordres chez les clients, on fait d’abord les requêtes via les objets d'accès aux données vers les différentes sources, par exemple, les bases de données sur les ordres, les clients, les comptes, les parts, etc. avant de créer une liste de records instanciés contenant les informations complètes sous les contrôles et vérifications pour chaque portefeuille. En fait, les recherches des sources et les manipulations des données pourraient être très coûteuses à chaque appel d’accès vers leurs sources originales. En effet, un objet d’ordre se composerait par au moins les instances de client, compte et part. On aurait souvent le recours aux caches pour stocker les résultats de ces instances en mémoire, pour que tous les appels lisent les mêmes données mises en cache. Néanmoins, si on veut retoucher localement un champ d’un objet d’accès pendant une simulation, il faut récupérer une copie superficielle ou profonde, afin de ne pas déranger les autres réutilisations de l’application qui demandent toujours les objets originaux. Par exemple, s’il ne fallait chiffrer les données clientèles dans les ordres pendant qu’une seule génération d’un résultat, on aurait dû avoir besoin d’une copie profonde des ordres qui comprennent les objets de client depuis la source, et à la fois les copies superficielles des clients à modifier. Les données de source sont donc protégées grâce au clonage.
En fait, le patron de conception de création : Prototype permet de cloner des objets complexes et coûteux, sans se coupler à leurs classes. Dans cet article, on va apprendre le patron de conception de prototype en guise d’une mise en place d’un portefeuille à dupliquer avec les ordres encapsulant les informations de client, compte et part. Les prototypes servent à préparer les diverses manières à cloner tous ces composants du portefeuille en fonction des besoins. L’héritage multiple et la composition sont introduits pour leurs mises en œuvre en Python 3, tandis que le programme respecte le principe SOLID.
II. Conception de prototype
Le diagramme ULM démontre la conception du Prototype. En général, toutes les classes prototype devraient dériver d’une interface commune IPrototype déclarant une méthode abstraite clone, même sans connaître leur classe concrète de client à copier. Les objets prototype sont capables de créer les copies complètes du client, à condition qu’ils puissent accéder aux attributs privés des autres objets dans la classe de client. Au lieu d'évoquer __new__ ou __init__ de la classe, on appelle directement la méthode clone pour la création. Le client peut exploiter ces prototypes par l’héritage multiple ou la composition.
Selon notre contexte, dans un portefeuille les ordres sont les instances complexes comprenant plusieurs objets élémentaires comme le client, le compte et la part. Leurs informations sont fréquemment manipulées et réutilisées pour les analyses, par exemple, dans différents modèles évaluant les performances du portefeuille. Du coup, si une modification a lieu éventuellement sur un élément, les clones en profondeur devront être demandés chez les ordres ainsi que le portefeuille, afin que leurs informations originales soient toutes intouchables en application. Toutefois, les duplicatas des objets compris dans un ordre n’auront besoin que les duplications superficielles, si l’on appelle leurs méthodes de clone.
III. Développement de prototype
Dans notre exemple, les objets client, compte, part composent une instance ordre, lorsque qu’un portefeuille contient une liste d’ordres instanciés. On va vérifier si grâce au prototype, les données resteront inchangées chez tous les ordres du portefeuille à la source, à la suite d’un chiffrement sur leurs informations clientèles dans une copie.
Afin de simplifier la démonstration, on cherche simplement les attributs basiques dont les valeurs viennent des sources extérieures pour les instances. 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 ; le nom et le pays du client ; le nom et le type du compte ; le nom et l’état de la part. En plus, dans cet exemple le portefeuille ne contient que dix ordres exécutés hier chez un client dans le même compte sur les parts identiques. Les montants des ordres à la source sont générés de la façon aléatoire à la distribution uniforme entre -100000,0 et 100000,0, lorsque leurs devises sont toujours en euro.
Dans un premier temps, on conçoit en Python 3 l’interface de prototype IPrototype avec la méthode de clone, qui est mise en œuvre dans les deux classes dérivées pour obtenir les copies superficielles ou profondes. Les classes client, compte, part, ordre et portefeuille héritent des deux prototypes concrets en fonction de leurs attributs internes. D’ailleurs, on introduit les propiétés et le __slots__ pour rendre les attributs concisément utilisables pour le prototype et optimaux en mémoire. Sinon, en Python 3 les méthodes copy et deepcopy permettraient de fournir aux prototypes une duplication superficielle ou profonde respectivement au gré du besoin. Tandis que l’on veut chiffrer les informations de client par Fernet chez les ordres, on cherche uniquement un duplicata profond de tous les ordres du portefeuille. Par conséquent, lors du prochain appel du clone du portefeuille, on pourra retrouver constamment les informations originales de client dans les ordres.
Les tests unitaires sont bien passés. En effet, on crée un portefeuille par la méthode « couteuse » get_original_portfolio, qui ne devra jamais être changé à la suite. La 1e duplication du portefeuille confirme d’abord que la copie profonde s’applique sur lui-même et ses ordres, et les autres objets sont clonés en guise de la copie superficielle. Ensuite on y chiffre les informations clientèles confidentielles, ce qui ne modifie que le duplicata chez les ordres, lorsque toutes les données dans le portefeuille original restent intactes et disponibles pour une utilisation suivante.
Par ailleurs, la méthode clone pourrait être réalisée d’une manière plus complexe dans le prototype, si l’on ne veut copier que certains attributs d’un objet lors de son appel. Par exemple, au lieu de chiffrer le nom et le pays chez un client, on pourrait enlever ces attributs secrets lors du clonage.
Le code suivant aborde ce sujet. La mise en œuvre de la classe PartialClone dérivant de l’interface IPrototype prend un ensemble de noms d’attribut en propriété (hidden_property_set) à enlever. Grace à l’héritage multiple en Python 3, la classe Client peut dériver de cette classe de prototype et d’une classe OriginalClient sans clone. Avant chaque clonage, on doit définir les propriétés à supprimer dans son hidden_property_set, lorsque l’on remet ce dernier à un ensemble vide, pour que la duplication superficielle redevienne totale.
Les tests unitaires sont bien passés. Le client dérive des deux classes qui s’occupent de fabriquer et cloner l’objet de client respectivement. Quand l’on appelle sa méthode reset_hidden_property_set, la copie superficielle totale est remise pour son clonage. En revanche, dès que hidden_property_set est affecté par un ensemble de noms d’attribut définis dans __slots__, un filtre chez la méthode clone empêche les duplications des attributs concernés. Du coup, les attributs d’un objet sont bien enlevés en copie, lorsque les données originales sont intouchées.
En outre, on aimerait parfois demander le clonage qui se varie dynamiquement au gré du besoin durant l’exécution. A part le héritage multiple en Python 3, on peut s’appuyer sur la composition pour mettre en place le clone partiel dans le code suivant, où le client dérive directement de l’interface IPrototype et il encapsule un objet de clonage (par défaut superficiel total) qui s’occupe de copier son instance. Du coup, justement avant l’appel de la méthode clone chez le client, on pourrait changer cet objet de clonage comme on veut pendant l’exécution, lorsque l’on le remet au clonage initial après l’appel, ce qui rend notre utilisation de clone flexible.
Les tests unitaires sont bien passés. Le client dérivant de l’interface IPrototype met au point la méthode clone en déléguant son instance à un objet de clonage, qui est responsable de la duplication et fourni pendant l’exécution du programme avant l’appel de clone. L’objet de clonage superficiel par défaut chez le client va copier entièrement l’instance du client. Or, si un objet de clonage partiel est affecté au client, la duplication superficielle aura lieu partiellement. Car cet objet prend en compte l’ensemble de noms d’attribut à enlever. Sinon, les données originales sont toujours inchangées.
Quoi que ce soit implémenté par l’héritage ou la composition en Python 3, plusieurs prototypes sont désormais disponibles pour fabriquer les duplicatas des différents objets chez un portefeuille complexe selon les besoins. D’ailleurs, par l’héritage, il faudrait choisir en avance une meilleure méthode clone qui s’adapterait aux attributs de l’instance à la phase du développement, lorsque la composition permettrait d’instancier dynamiquement un clonage qui fait partie de l’objet à copier justement avant la demande du clone.
Du fait, le patron de conception Prototype découple la génération d’un objet et son clonage. D’ores et déjà, on ne pourrait construire qu’une seule fois les objets originaux complexes dont on profiterait pour diverses copies nécessaires à la suite.
IV. Conclusion
Dans cet article, on met en évidence la nécessité du patron de conception Prototype, qui répond à nos besoins de la réutilisation des instances coûteuses durant leur première fabrication de la source. Il respecte le principe de responsabilité unique en découplant la génération et le clonage. Les implémentations s’appuient sur l’héritage multiple ou la composition en Python 3, ce qui permet de mettre les divers types de duplication dehors les objets à cloner. Le patron de conception prototype est mis en place et testé pour les copies superficielles, profondes et partielles, ce qui pourrait facilement s’étendre aux autres façons du clonage.