Patrones Avanzados de Fábrica en C++: Implementaciones con Plantillas y Singleton

Introducción

En artículos anteriores, exploramos los patrones de fábrica simple, método de fábrica y fábrica abstracta, analizando sus estructuras, características y limitaciones. Estos tres enfoques requieren modificar las clases de fábrica o agregar nuevas clases de fábrica específicas al introducir nuevos productos, lo que indica que la encapsulación de las clases de fábrica no es óptima.

Este artículo avanzado se centra en mejorar la encapsulación de las clases de fábrica, permitiendo agregar nuevos productos sin modificar las clases de fábrica existentes ni agregar nuevas clases de fábrica específicas. Una clase de fábrica con alta encapsulación tiene alta extensibilidad y reutilización.

Fábrica con Plantillas

Al encapsular el patrón de método de fábrica en una clase de fábrica con plantillas, podemos introducir nuevos productos sin necesidad de crear nuevas clases de fábrica específicas, reduciendo la cantidad de código necesario.

Código de la fábrica con plantillas:

  • Calzado y Ropa, son clases abstractas (base) para zapatos y ropa respectivamente
  • ZapatillasNike y PrendaUniqlo, son clases de productos específicas para zapatillas Nike y ropa Uniqlo
// Clase base Calzado
class Calzado
{
public:
    virtual void Mostrar() = 0;
    virtual ~Calzado() {}
};

// Zapatillas Nike
class ZapatillasNike : public Calzado
{
public:
    void Mostrar() override
    {
        std::cout << "Soy zapatillas Nike, eslogan: Just do it" << std::endl;
    }
};

// Clase base Ropa
class Ropa
{
public:
    virtual void Mostrar() = 0;
    virtual ~Ropa() {}
};

// Ropa Uniqlo
class PrendaUniqlo : public Ropa
{
public:
    void Mostrar() override
    {
        std::cout << "Soy ropa Uniqlo, eslogan: I am Uniqlo" << std::endl;
    }
};

  • AbstractaFabrica es la clase abstracta de fábrica con plantillas, donde el parámetro de plantilla: ProductoAbstracto_t es la clase abstracta del producto, como Calzado o Ropa
  • FabricaConcreta es la clase concreta de fábrica con plantillas, donde los parámetros de plantilla son: ProductoAbstracto_t (clase abstracta del producto) y ProductoConcreto_t (clase concreta del producto)
// Clase abstracta de fábrica con plantillas
// Parámetro de plantilla: ProductoAbstracto_t - clase abstracta del producto
template <class ProductoAbstracto_t>
class AbstractaFabrica
{
public:
    virtual ProductoAbstracto_t *CrearProducto() = 0;
    virtual ~AbstractaFabrica() {}
};

// Clase concreta de fábrica con plantillas
// Parámetros de plantilla: ProductoAbstracto_t - clase abstracta del producto, ProductoConcreto_t - clase concreta del producto
template <class ProductoAbstracto_t, class ProductoConcreto_t>
class FabricaConcreta : public AbstractaFabrica<ProductoAbstracto_t>
{
public:
    ProductoAbstracto_t *CrearProducto() override
    {
        return new ProductoConcreto_t();
    }
};

  • Función main, que según el tipo de producto, construye el objeto de fábrica correspondiente y crea el objeto de producto específico a través de él.
int main()
{
    // Construir objeto de fábrica para zapatillas Nike
    FabricaConcreta<Calzado, ZapatillasNike> fabricaNike;
    // Crear objeto de zapatillas Nike
    Calzado *pZapatillasNike = fabricaNike.CrearProducto();
    // Mostrar eslogan de zapatillas Nike
    pZapatillasNike->Mostrar();

    // Construir objeto de fábrica para ropa Uniqlo
    FabricaConcreta<Ropa, PrendaUniqlo> fabricaUniqlo;
    // Crear objeto de ropa Uniqlo
    Ropa *pPrendaUniqlo = fabricaUniqlo.CrearProducto();
    // Mostrar eslogan de Uniqlo
    pPrendaUniqlo->Mostrar();

    // Liberar recursos
    delete pZapatillasNike;
    pZapatillasNike = nullptr;

    delete pPrendaUniqlo;
    pPrendaUniqlo = nullptr;

    return 0;
}

  • Resultado de ejecución:
[root@lincoding factory]# ./fabricaPlantilla 
Soy zapatillas Nike, eslogan: Just do it
Soy ropa Uniqlo, eslogan: I am Uniqlo

Clase de Registro de Productos con Plantillas + Clase de Fábrica Singleton con Plantillas

La fábrica con plantillas anterior, aunque no requiere nuevas clases de fábrica específicas al agregar nuevos productos, carece de una clase que permita obtener uniformemente objetos de productos especificados en cualquier momento.

Existe espacio para mejorar. Podemos guardar los objetos registrados utilizando std::map, permitiendo obtener fácilmente instancias de objetos de producto correspondientes mediante un enfoque clave-valor.

La implementación general sigue estos pasos:

  • Encapsular la función de registro de productos en una clase de registro con plantillas. Los objetos de producto registrados se guardan en std::map de la clase de fábrica con plantillas para facilitar su obtención.
  • Encapsular la función de obtención de objetos de producto en una clase de fábrica con plantillas. Para poder obtener objetos de producto especificados en cualquier momento, se diseña la fábrica como un singleton.

Código de la clase de registro de productos con plantillas + clase de fábrica singleton con plantillas:

  • IRegistradorProducto es la clase abstracta de registro de productos, con parámetro de plantilla TipoProducto_t que representa la clase abstracta del producto (como Calzado, Ropa). Proporciona una función virtual pura CrearProducto para la creación de objetos de producto.
  • FabricaProducto es la clase de fábrica con plantillas, con parámetro de plantilla TipoProducto_t que representa la clase abstracta del producto (como Calzado, Ropa). Se utiliza para guardar objetos de producto registrados en std::map y obtener el objeto de producto correspondiente.
  • RegistradorProducto es la clase de registro de productos con plantillas, con parámetros de plantilla TipoProducto_t (clase abstracta del producto) y ImplProducto_t (clase concreta del producto). Se utiliza para registrar productos en la clase de fábrica y crear instancias de objetos de producto.
// Clase base, interfaz abstracta de registro de productos con plantillas
// Parámetro de plantilla TipoProducto_t representa la clase abstracta del producto
template <class TipoProducto_t>
class IRegistradorProducto
{
public:
   // Interfaz abstracta para obtener objeto de producto
   virtual TipoProducto_t *CrearProducto() = 0;

protected:
   // Prohibir construcción y destrucción externas, otras funciones "internas" de la subclase pueden llamar
   IRegistradorProducto() = default;
   virtual ~IRegistradorProducto() = default;

private:
   // Prohibir operaciones de copia y asignación externas
   IRegistradorProducto(const IRegistradorProducto &) = delete;
   const IRegistradorProducto &operator=(const IRegistradorProducto &) = delete;
};

// Clase de fábrica con plantillas, para obtener y registrar objetos de producto
// Parámetro de plantilla TipoProducto_t representa la clase abstracta del producto
template <class TipoProducto_t>
class FabricaProducto
{
public:
   // Obtener el singleton de la fábrica, la instancia de la fábrica es única
   static FabricaProducto<TipoProducto_t> &Instancia()
   {
      static FabricaProducto<TipoProducto_t> instancia;
      return instancia;
   }

   // Registro de producto
   void RegistrarProducto(IRegistradorProducto<TipoProducto_t> *registrador, const std::string &nombre)
   {
      m_RegistroProducto[nombre] = registrador;
   }

   // Obtener objeto de producto específico según el nombre
   TipoProducto_t *ObtenerProducto(const std::string &nombre)
   {
      // Buscar en el mapa los productos ya registrados y devolver el objeto de producto
      auto it = m_RegistroProducto.find(nombre);
      if (it != m_RegistroProducto.end())
      {
         return it->second->CrearProducto();
      }

      // Si el producto no está registrado, mostrar error
      std::cout << "No se encontró producto para " << nombre << std::endl;

      return nullptr;
   }

private:
   // Prohibir construcción y destrucción externas
   FabricaProducto() = default;
   ~FabricaProducto() = default;

   // Prohibir operaciones de copia y asignación externas
   FabricaProducto(const FabricaProducto &) = delete;
   const FabricaProducto &operator=(const FabricaProducto &) = delete;

   // Mapa para guardar productos registrados, clave: nombre del producto, valor: tipo de producto
   std::map<std::string, IRegistradorProducto<TipoProducto_t> *> m_RegistroProducto;
};

// Clase de registro de productos con plantillas, para crear productos concretos y registrarlos en la fábrica
// Parámetros de plantilla TipoProducto_t (clase abstracta del producto), ImplProducto_t (clase concreta del producto)
template <class TipoProducto_t, class ImplProducto_t>
class RegistradorProducto : public IRegistradorProducto<TipoProducto_t>
{
public:
   // Constructor para registrar productos en la fábrica, solo llamada explícitamente
   explicit RegistradorProducto(const std::string &nombre)
   {
      // Registrar el producto en la fábrica a través del singleton
      FabricaProducto<TipoProducto_t>::Instancia().RegistrarProducto(this, nombre);
   }

   // Crear objeto de producto concreto
   TipoProducto_t *CrearProducto() override
   {
      return new ImplProducto_t();
   }
};

  • Función main, que a través de RegistradorProducto registra diferentes tipos de productos y luego obtiene objetos de producto específicos mediante el singleton FabricaProducto.
int main()
{
   // ========================== Proceso de creación de zapatillas Nike ===========================//
   // Registrar tipo de producto Calzado (base), producto ZapatillasNike (derivado) en la fábrica con nombre nike
   RegistradorProducto<Calzado, ZapatillasNike> registradorNike("nike");
   // Obtener objeto de producto de tipo Calzado con nombre nike de la fábrica
   Calzado *pZapatillasNike = FabricaProducto<Calzado>::Instancia().ObtenerProducto("nike");
   // Mostrar eslogan del producto
   pZapatillasNike->Mostrar();
   // Liberar recursos
   if (pZapatillasNike)
   {
      delete pZapatillasNike;
      pZapatillasNike = nullptr;
   }

   // ========================== Proceso de creación de ropa Uniqlo ===========================//
   // Registrar tipo de producto Ropa (base), producto PrendaUniqlo (derivado) en la fábrica con nombre uniqlo
   RegistradorProducto<Ropa, PrendaUniqlo> registradorUniqlo("uniqlo");
   // Obtener objeto de producto de tipo Ropa con nombre uniqlo de la fábrica
   Ropa *pPrendaUniqlo = FabricaProducto<Ropa>::Instancia().ObtenerProducto("uniqlo");
   // Mostrar eslogan del producto
   pPrendaUniqlo->Mostrar();
   // Liberar recursos
   if (pPrendaUniqlo)
   {
      delete pPrendaUniqlo;
      pPrendaUniqlo = nullptr;
   }

   return 0;
}

  • Resultado de ejecución:
[root@lincoding factory]# ./fabricaSingleton 
Soy zapatillas Nike, eslogan: Just do it
Soy ropa Uniqlo, eslogan: I am Uniqlo

Conclusión

Al mejorar el patrón de método de fábrica en una fábrica con plantillas, aunque se resuelve el problema de no requerir nuevas clases de fábrica específicas al agregar nuevos productos, falta una forma de obtener objetos de producto en cualquier momento, lo que indica que aún existe espacio para mejorar.

Al mejorar la fábrica con plantillas en una combinación de clase de registro de productos con plantillas + clase de fábrica singleton con plantillas, la clase de registro se utiliza para registrar diferentes tipos de productos y la clase de fábrica singleton para obtener objetos de producto específicos ya registrados. Este enfoque permite abstraer eficazmente las funciones principales de registro y obtención de productos del patrón de fábrica en dos clases, y utiliza el patrón singleton para que la fábrica pueda obtener objetos de producto registrados en cualquier momento.

Por lo tanto, la combinación de clase de registro de productos con plantillas + clase de fábrica singleton con plantillas cumple con el principio de abierto-cerrado, y tiene alta extensibilidad y encapsulación.

Etiquetas: patrones de diseño fábrica Plantillas C++ Singleton Programación Orientada a Objetos

Publicado el 8-15 06:36