Prototype, Pourquoi utiliser ce patron de conception ?

Dans ce tutoriel, nous appendrons pourquoi utiliser Prototype.

Nous le définirons puis l’analyserons au travers d’un exemple pratique.

Pré-requis : Avoir vu Matrix

prototype-agent-smith-replicas

Définition :

Prototype : Patron de conception utilisé lorsque la création d’instance est complexe ou consommatrice de temps. Plutôt que de créer plusieurs instances d’une même classe, on duplique la première en la modifiant de manière appropriée.

Exemple pratique :

On souhaite créer une armée d’agent Smith. Chacun des agents est identifié par un numéro.
Deux choix s’offre à nous : Créer chaque agent un par un ou en créer un et le dupliquer.

Création des agents un par un

plans_montage

Le code source ci-dessous nous permet de créer une armée d’agents Smith un par un.

from datetime import datetime
import time

startTime = datetime.now()


class Prototype:

    def __init__(self, **attrs):
        print("Agent creating ...")
        time.sleep(1)
        self.__dict__.update(attrs)

    def __repr__(self):
        return "Agent {}".format(self.name)


class PrototypeDispatcher:

    def __init__(self):
        self._objects = {}

    def get_objects(self):
        """Get all objects"""
        return self._objects

    def register_object(self, name, obj):
        """Register an object"""
        self._objects[name] = obj

    def unregister_object(self, name):
        """Unregister an object"""
        del self._objects[name]


def main():

    print("Test with agent creation ****")
    dispatcher = PrototypeDispatcher()

    names = ['Smith', 'Johnson', 'Lopez', 'Anderson']

    for i, name in enumerate(names):
        agent = Prototype(
            name=name,
            category="agent",
            mission="kill neo"
        )

        dispatcher.register_object('agent-' + str(i), agent)

    objects = dispatcher.get_objects()
    print("Before name change for agent 2".center(50, "*"))
    print(id(objects['agent-2']))
    print(objects['agent-2'])
    print(id(objects['agent-3']))
    print(objects['agent-3'])
    objects['agent-2'].name = "Jenkins"
    print("After name change for agent 2".center(50, "*"))
    print(id(objects['agent-2']))
    print(objects['agent-2'])
    print(id(objects['agent-3']))
    print(objects['agent-3'])


if __name__ == "__main__":
    main()

    # Get time for creating objects
    print(datetime.now() - startTime)

Création par clonage

cloning_machine

Le code source suivant crée une armée d’agent Smith par clonage.

import copy
from datetime import datetime
import time
import random

startTime = datetime.now()


class Prototype:

    def __init__(self, **attrs):
        print("Agent creating ...")
        time.sleep(1)
        self.__dict__.update(attrs)

    def clone(self, **attrs):
        """Clone an agent and update inner attributes dictionary"""

        print("Agent creating ...")
        obj = copy.deepcopy(self)
        obj.__dict__.update(attrs)
        return obj

    def __repr__(self):
        return "Agent {}".format(self.name)


class PrototypeDispatcher:

    def __init__(self):
        self._objects = {}

    def get_objects(self):
        """Get all objects"""
        return self._objects

    def register_object(self, name, obj):
        """Register an object"""
        self._objects[name] = obj

    def unregister_object(self, name):
        """Unregister an object"""
        del self._objects[name]


def main():

    print("Test with agent cloning ****")
    dispatcher = PrototypeDispatcher()
    prototype = Prototype()

    names = ['Smith', 'Johnson', 'Lopez', 'Anderson']

    for i, name in enumerate(names):
        agent = prototype.clone(
            name=name,
            category="agent",
            mission="kill neo"
        )

        dispatcher.register_object('agent-' + str(i), agent)

    objects = dispatcher.get_objects()
    print("Before name change for agent 2".center(50, "*"))
    print(id(objects['agent-2']))
    print(objects['agent-2'])
    print(id(objects['agent-3']))
    print(objects['agent-3'])
    objects['agent-2'].name = "Jenkins"
    print("After name change for agent 2".center(50, "*"))
    print(id(objects['agent-2']))
    print(objects['agent-2'])
    print(id(objects['agent-3']))
    print(objects['agent-3'])


if __name__ == "__main__":
    main()
    # Get time for creating objects
    print(datetime.now() - startTime)

Félicitations, le tutoriel se termine, vous savez à présent pourquoi et comment utiliser ce patron de conception.

2 réflexions au sujet de “Prototype, Pourquoi utiliser ce patron de conception ?”

  1. Intéressant. Attention, les 2 exemples sont inversés : clonage en 1er et instanciations en 2nd.

    Je ne l’ai pas fait tourner pour voir les temps d’exécutions. Est-ce que deepcopy est plus rapide que la création d’une instance ?

    • Merci pour ton observation.

      L’exemple pratique a été modifié pour faciliter la compréhension de Prototype.

      Dans cette exemple, la réutilisation de l’instance déjà créée s’avère plus rapide que deepcopy.
      Cependant, il y a une différence majeure.

      Avec deepcopy :
      Chaque agent créé est stocké en mémoire à une adresse unique.
      ex : Anderson => 19878999, Lopez => 19878977
      La modification du nom d’un agent affectera seulement l’agent concerné.
      ex : Anderson -> Jenkins, Jenkins => 19878999, Lopez => 19878977

      Réutilisation d’instance :
      Chaque agent créé avec l’agent original pointe vers un espace mémoire commun.
      La modification du nom d’un agent affectera les autres agents.

Laisser un commentaire