Utilisation Conjointe de Singleton et Fabrique Méthode en Python

Introduction

Dans le développement logiciel, combiner des patrons de conception permet de créer des architectures robustes et flexibles. Une interrogation fréquente en Python est de savoir si l'on peut associer le patron de conception Singleton avec le patron de la Méthode de Fabrique. L'objectif serait de garantir qu'il n'existe qu'une seule instance de chaque type de fabrique spécifique à un scénario, tout en tirant parti de la flexibilité de la méthode de fabrique pour la création d'objets.

Cette approche est non seulement possible mais également bénéfique. Elle assure qu'une seule instance de chaque fabrique est créée et utilisée tout au long du cycle de vie de l'application, optimisant ainsi l'utilisation des ressources et évitant la duplication inutile d'objets. Nous allons explorer comment implémenter cette combinaison en Python.

Implémentation du Patron Singleton avec une Métaclasse

Pour garantir qu'une classe n'ait qu'une seule instance, nous pouvons utiliser une métaclasse. Cette métaclasse interceptera la création d'objets et s'assurera de renvoyer toujours la même instance pour une classe donnée.

class MetaclasseMonofonctionnelle(type):
    """
    Métaclasse pour garantir qu'une classe ne peut être instanciée qu'une seule fois.
    Chaque classe utilisant cette métaclasse aura sa propre instance unique.
    """
    _registre_instances = {}

    def __call__(cls, *args, **kwargs):
        """
        Cette méthode est appelée lorsque la classe est "appelée" (par exemple, Classe()).
        Elle vérifie si une instance existe déjà pour la classe et la crée si nécessaire.
        """
        if cls not in cls._registre_instances:
            # Crée une nouvelle instance si elle n'existe pas encore
            instance_creee = super().__call__(*args, **kwargs)
            cls._registre_instances[cls] = instance_creee
        return cls._registre_instances[cls]


Définition des Produits

Avant de créer nos fabriques, définissons une interface pour les produits qu'elles vont créer, ainsi que quelques implémentations concrètes. Cela clarifiera le rôle des fabriques.

class ProduitAbstrait:
    """Interface abstraite pour tous les produits."""
    def operation(self):
        """Méthode abstraite représentant une opération du produit."""
        raise NotImplementedError("La méthode 'operation' doit être implémentée par les sous-classes de ProduitAbstrait.")

class ProduitConcretA(ProduitAbstrait):
    """Implémentation concrète d'un ProduitA."""
    def operation(self):
        return "Exécution de l'opération du Produit Concret A."

class ProduitConcretB(ProduitAbstrait):
    """Implémentation concrète d'un ProduitB."""
    def operation(self):
        return "Exécution de l'opération du Produit Concret B."

La Fabrique Abstraite

Nous définissons une classe de base abstraite pour nos fabriques. Cette classe utilsiera notre MetaclasseMonofonctionnelle pour garantir que chaque fabrique concrète héritent de celle-ci sera un Singleton.

class FabriqueAbstraite(metaclass=MetaclasseMonofonctionnelle):
    """
    Classe de base abstraite pour toutes les fabriques, utilisant la métaclasse Singleton.
    Chaque fabrique concrète dérivée de celle-ci sera un Singleton.
    """
    def fabriquer_produit(self):
        """
        Méthode abstraite que les sous-classes devront implémenter pour créer un produit spécifique.
        """
        raise NotImplementedError("La méthode 'fabriquer_produit' doit être implémentée par les fabriques concrètes.")

Fabriques Concrètes

Maintenant, nous pouvons créer des fabriques concrètes pour chaque type de produit. Chacune de ces fabriques héritera de FabriqueAbstraite et, par extension, bénéficiera de la propriété Singleton via sa métaclasse.

class FabriqueProduitsA(FabriqueAbstraite):
    """
    Fabrique concrète qui crée des instances de ProduitConcretA.
    Elle est un Singleton grâce à la métaclasse de FabriqueAbstraite.
    """
    def fabriquer_produit(self):
        print("Initialisation de ProduitConcretA via FabriqueProduitsA.")
        return ProduitConcretA()

class FabriqueProduitsB(FabriqueAbstraite):
    """
    Fabrique concrète qui crée des instances de ProduitConcretB.
    Elle est un Singleton grâce à la métaclasse de FabriqueAbstraite.
    """
    def fabriquer_produit(self):
        print("Initialisation de ProduitConcretB via FabriqueProduitsB.")
        return ProduitConcretB()

Utilisation des Fabriques

Lorsqu'une instance de l'une de ces fabriques concrètes est demandée, notre métaclasse s'assurera que la même instance est toujours renvoyée. Ceci garantit que, par exemple, il n'y aura jamais qu'une seule instance de FabriqueProduitsA en mémoire, tout en nous permettant de créer différents types de produits via différentes fabriques.

# Obtenir la première instance de FabriqueProduitsA
print("--- Test de FabriqueProduitsA ---")
fabrique_a_premiere = FabriqueProduitsA()
produit1_a = fabrique_a_premiere.fabriquer_produit()
print(produit1_a.operation())

# Obtenir une seconde "instance" de FabriqueProduitsA
fabrique_a_seconde = FabriqueProduitsA()
produit2_a = fabrique_a_seconde.fabriquer_produit()
print(produit2_a.operation())

# Vérifier que les deux "instances" de la fabrique sont en réalité la même
print(f"Les deux références de FabriqueProduitsA sont-elles identiques ? {fabrique_a_premiere is fabrique_a_seconde}") # Attendu: True

print("\n--- Test de FabriqueProduitsB ---")
# Obtenir la première instance de FabriqueProduitsB
fabrique_b_premiere = FabriqueProduitsB()
produit1_b = fabrique_b_premiere.fabriquer_produit()
print(produit1_b.operation())

# Obtenir une seconde "instance" de FabriqueProduitsB
fabrique_b_seconde = FabriqueProduitsB()
produit2_b = fabrique_b_seconde.fabriquer_produit()
print(produit2_b.operation())

# Vérifier que les deux "instances" de la fabrique sont en réalité la même
print(f"Les deux références de FabriqueProduitsB sont-elles identiques ? {fabrique_b_premiere is fabrique_b_seconde}") # Attendu: True

print("\n--- Comparaison des différentes fabriques ---")
# Vérifier que FabriqueProduitsA et FabriqueProduitsB sont des singletons distincts
print(f"Les instances de FabriqueProduitsA et FabriqueProduitsB sont-elles identiques ? {fabrique_a_premiere is fabrique_b_premiere}") # Attendu: False

Comme démontré, cette combinaison de patrons permet d'avoir une instance unique par fabrique spécifique (Singleton), tout en utilisant la structure flexible de la méthode de fabrique pour créer différents types de produits. C'est une stratégie puissante pour gérer la création d'objets dans des systèmes complexes.

Étiquettes: Python Design Patterns Singleton Factory Method POO

Publié le 13 juillet à 23h21