Introducción al Patrón Prototype
El patrón Prototype es una técnica de creación que se centra en la clonación de objetos existentes para generar nuevas instancias. En lugar de construir objetos desde cero, este patrón nos permite crear copias exactas o modificadas de un objeto base, conocido como prototipo.
Su definición formal es: especificar los tipos de objetos a crear utilizando una instancia prototipo, y crear nuevos objetos copiando este prototipo.
Funcionalidades Clave del Prototype:
- Crea nuevas instancias de objetos a través de un proceso de clonación.
- Replica los valores de los atributos del objeto prototipo a la nueva instancia clonada.
Tipos de Clonación:
- Copia Superficial (Shallow Copy): Solo clona los datos de tipo valor (como números enteros, cadenas de texto, etc.). Para los tipos de referencia (otros objetos), la copia superficial replica la referencia, lo que significa que tanto el objeto original como el clonado apuntarán a la misma instancia de los objetos anidados.
- Copia Profunda (Deep Copy): Además de los datos de tipo valor, también clona recursivamente los objetos de tipo referencia. Esto asegura que el objeto clonado y todas sus dependencias anidadas sean completamente independientes del objeto original.
Ventajas y Desventajas del Patrón Prototype
| Ventajas del Prototype | Desventajas del Prototype |
|---|---|
| Oculta la implementación concreta al cliente: El código cliente interactúa solo con la interfaz del prototipo, sin necesidad de conocer los tipos concretos subyacentes, lo que reduce la dependencia. | Cada subclase de prototipo debe implementar su operación de clonación. Esto puede ser complejo y propenso a errores, especialmente cuando los objetos contienen tipos de reefrencia que requieren una clonación recursiva y profunda. |
| Permite cambiar dinámicamente el tipo de objeto en tiempo de ejecución: El cliente puede registrar nuevas implementaciones de prototipos o cambiar las existentes, y el sistema puede generar nuevas instancias sin modificar su lógica de creación, ya que el proceso es una "clonación" de un objeto ya existente. |
¿Cuándo utilizar el Patrón Prototype?
- Cuando el sistema necesita ser independiente de cómo se crean sus objetos, permitiendo que la lógica de creación se base en interfaces y la obtención de nuevas instancias sea a través de la clonación de prototipos.
- Cuando los tipos de objetos a instanciar se especifican dinámicamente en tiempo de ejecución, el prototipo ofrece una manera flexible de obtener nuevas instancias.
- Cuando la creación de un objeto es costosa o compleja, y existen instancias similares que pueden ser copiadas de manera más eficiente.
Ejemplo Práctico del Patrón Prototype
Contexto del Negocio:
Imaginemos un sistema de procesamiento de pedidos. Si la cantidad de artículos en un pedido excede un límite (por ejemplo, 1000 unidades), el pedido debe dividirse en múltiples pedidos más pequeños hasta que ninguno exceda el límite. Existen dos tipos de pedidos: "Pedido Individual" y "Pedido Corporativo", y ambos deben adherirse a esta regla de división.
1. Implementación sin Patrón de Diseño
Para gestionar los diferentes tipos de pedidos, podríamos definir una interfaz común y luego implementar clases concretas para cada tipo.
1.1. Interfaz de Pedido
using System;
namespace EjemplosPrototype.SinPatron
{
/// <summary>
/// Interfaz base para cualquier tipo de pedido.
/// </summary>
internal interface IDetallePedido
{
int ObtenerCantidadArticulos();
void EstablecerCantidadArticulos(int cantidad);
}
}
1.2. Clases Concretas: PedidoIndividual y PedidoCorporativo
using System;
namespace EjemplosPrototype.SinPatron
{
/// <summary>
/// Representa un pedido realizado por un cliente individual.
/// </summary>
internal class PedidoIndividual : IDetallePedido
{
public string? NombreCliente { get; set; }
public string? CodigoProducto { get; set; }
private int _cantidadProducto = 0;
public int ObtenerCantidadArticulos()
{
return _cantidadProducto;
}
public void EstablecerCantidadArticulos(int cantidad)
{
// Validaciones de lógica de negocio (omitidas para brevedad)
this._cantidadProducto = cantidad;
}
public override string ToString()
{
return $"Pedido Individual: Cliente: '{NombreCliente}', Producto: '{CodigoProducto}', Cantidad: '{_cantidadProducto}'";
}
}
}
using System;
namespace EjemplosPrototype.SinPatron
{
/// <summary>
/// Representa un pedido realizado por una entidad corporativa.
/// </summary>
internal class PedidoCorporativo : IDetallePedido
{
public string? NombreEmpresa { get; set; }
public string? CodigoProducto { get; set; }
private int _cantidadProducto = 0;
public int ObtenerCantidadArticulos()
{
return _cantidadProducto;
}
public void EstablecerCantidadArticulos(int cantidad)
{
// Validaciones de lógica de negocio (omitidas para brevedad)
this._cantidadProducto = cantidad;
}
public override string ToString()
{
return $"Pedido Corporativo: Empresa: '{NombreEmpresa}', Producto: '{CodigoProducto}', Cantidad: '{_cantidadProducto}'";
}
}
}
1.3. Lógica de Negocio para Guardar Pedidos
using System;
namespace EjemplosPrototype.SinPatron
{
/// <summary>
/// Objeto de negocio para gestionar el procesamiento y guardado de pedidos.
/// </summary>
internal class GestorPedidos
{
private const int CantidadMaximaPorPedido = 1000;
/// <summary>
/// Guarda un pedido, dividiéndolo si su cantidad excede el límite.
/// </summary>
/// <param name="pedido">El pedido a procesar.</param>
public void ProcesarYGuardarPedido(IDetallePedido pedido)
{
Console.WriteLine($"\n--- Procesando pedido inicial: {pedido.ObtenerCantidadArticulos()} unidades ---");
while (pedido.ObtenerCantidadArticulos() > CantidadMaximaPorPedido)
{
IDetallePedido nuevoPedidoParcial = null;
if (pedido is PedidoIndividual pedidoIndividual)
{
var nuevoPI = new PedidoIndividual
{
NombreCliente = pedidoIndividual.NombreCliente,
CodigoProducto = pedidoIndividual.CodigoProducto
};
nuevoPI.EstablecerCantidadArticulos(CantidadMaximaPorPedido);
nuevoPedidoParcial = nuevoPI;
}
else if (pedido is PedidoCorporativo pedidoCorporativo)
{
var nuevoPC = new PedidoCorporativo
{
NombreEmpresa = pedidoCorporativo.NombreEmpresa,
CodigoProducto = pedidoCorporativo.CodigoProducto
};
nuevoPC.EstablecerCantidadArticulos(CantidadMaximaPorPedido);
nuevoPedidoParcial = nuevoPC;
}
pedido.EstablecerCantidadArticulos(pedido.ObtenerCantidadArticulos() - CantidadMaximaPorPedido);
Console.WriteLine($"Pedido parcial generado: {nuevoPedidoParcial}");
}
Console.WriteLine($"Pedido final procesado: {pedido}");
}
}
}
1.4. Código Cliente para Probar
using System;
namespace EjemplosPrototype
{
internal class Programa
{
static void Main(string[] args)
{
EjemploSinPatron();
Console.ReadLine();
}
private static void EjemploSinPatron()
{
Console.WriteLine("--- PRUEBA SIN EL PATRÓN PROTOTYPE ---");
// Pedido Individual
var pedidoCliente = new SinPatron.PedidoIndividual
{
NombreCliente = "Ana López",
CodigoProducto = $"PROD{new Random().Next(100, 999)}"
};
pedidoCliente.EstablecerCantidadArticulos(2966);
var gestor1 = new SinPatron.GestorPedidos();
gestor1.ProcesarYGuardarPedido(pedidoCliente);
// Pedido Corporativo
var pedidoEmpresa = new SinPatron.PedidoCorporativo
{
NombreEmpresa = "Tech Solutions S.A.",
CodigoProducto = $"PROD{new Random().Next(100, 999)}"
};
pedidoEmpresa.EstablecerCantidadArticulos(3001);
var gestor2 = new SinPatron.GestorPedidos();
gestor2.ProcesarYGuardarPedido(pedidoEmpresa);
}
}
}
1.5. Problemas de esta Aproximación:
Aunque funciona, esta implementación presenta dos problemas importantes:
- Acoplamiento Fuerte: La clase
GestorPedidosdepende directamente de los tipos concretosPedidoIndividualyPedidoCorporativo. Esto se ve en las sentenciasif (pedido is ...). Idealmente, el gestor debería operar solo con la interfazIDetallePedido. - Falta de Extensibilidad: Si se introduce un nuevo tipo de pedido (por ejemplo,
PedidoInternacional), tendríamos que modificar la lógica dentro deGestorPedidos.ProcesarYGuardarPedido()para incluir el manejo del nuevo tipo, lo que viola el principio Abierto/Cerrado.
2. Implementación con el Patrón Prototype
El patrón Prototype resuelve los problemas anteriores al permitir que el objeto de negocio cree nuevas instancias sin conocer su tipo concreto, simplemente clonando un prototipo.
2.1. Interfaz de Pedido con Capacidad de Clonación
Ahora, nuestra interfaz IOrdenClonable incluirá un método Clonar().
using System;
namespace EjemplosPrototype.ConPatron
{
/// <summary>
/// Interfaz base para un pedido que puede ser clonado.
/// </summary>
internal interface IOrdenClonable
{
int ObtenerCantidadArticulos();
void EstablecerCantidadArticulos(int cantidad);
IOrdenClonable Clonar(); // Método de clonación
}
}
2.2. Clases Concretas Implementando Clonación Manual
Cada clase concreta ahora implementará su propia lógica de clonación, creando una nueva instancia y copiando sus propios datos. Es crucial no usar return this;, ya que eso devolvería el mismo objeto, no una copia independiente.
using System;
namespace EjemplosPrototype.ConPatron
{
/// <summary>
/// Representa un pedido individual, ahora con capacidad de clonación.
/// </summary>
internal class PedidoCliente : IOrdenClonable
{
public string? NombreComprador { get; set; }
public string? IdArticulo { get; set; }
private int _unidadesPedido = 0;
public IOrdenClonable Clonar()
{
// Realiza una copia superficial manual creando una nueva instancia
// y asignando los valores de las propiedades.
var copia = new PedidoCliente
{
NombreComprador = this.NombreComprador,
IdArticulo = this.IdArticulo
};
copia.EstablecerCantidadArticulos(this._unidadesPedido); // También copia la cantidad
return copia;
}
public int ObtenerCantidadArticulos()
{
return _unidadesPedido;
}
public void EstablecerCantidadArticulos(int cantidad)
{
// Validaciones de lógica de negocio (omitidas)
this._unidadesPedido = cantidad;
}
public override string ToString()
{
return $"Pedido de Cliente: Comprador: '{NombreComprador}', Artículo: '{IdArticulo}', Cantidad: '{_unidadesPedido}'";
}
}
}
using System;
namespace EjemplosPrototype.ConPatron
{
/// <summary>
/// Representa un pedido corporativo, con capacidad de clonación.
/// </summary>
internal class PedidoEmpresarial : IOrdenClonable
{
public string? RazonSocial { get; set; }
public string? IdArticulo { get; set; }
private int _unidadesPedido = 0;
public IOrdenClonable Clonar()
{
// Realiza una copia superficial manual.
var copia = new PedidoEmpresarial
{
RazonSocial = this.RazonSocial,
IdArticulo = this.IdArticulo
};
copia.EstablecerCantidadArticulos(this._unidadesPedido);
return copia;
}
public int ObtenerCantidadArticulos()
{
return _unidadesPedido;
}
public void EstablecerCantidadArticulos(int cantidad)
{
// Validaciones de lógica de negocio (omitidas)
this._unidadesPedido = cantidad;
}
public override string ToString()
{
return $"Pedido Empresarial: Empresa: '{RazonSocial}', Artículo: '{IdArticulo}', Cantidad: '{_unidadesPedido}'";
}
}
}
2.3. Lógica de Negocio Adaptada
La clase ProcesadorPedidos ahora puede operar con la interfaz IOrdenClonable, eliminando las dependencias explícitas de los tipos concretos.
using System;
namespace EjemplosPrototype.ConPatron
{
/// <summary>
/// Objeto de negocio que procesa pedidos usando el patrón Prototype.
/// </summary& internal class ProcesadorPedidos
{
private const int MaximoArticulos = 1000;
public void GestionarPedido(IOrdenClonable pedidoBase)
{
Console.WriteLine($"\n--- Gestionando pedido inicial: {pedidoBase.ObtenerCantidadArticulos()} unidades ---");
while (pedidoBase.ObtenerCantidadArticulos() > MaximoArticulos)
{
IOrdenClonable pedidoFragmentado = pedidoBase.Clonar(); // ¡Aquí está la magia del Prototype!
pedidoFragmentado.EstablecerCantidadArticulos(MaximoArticulos);
pedidoBase.EstablecerCantidadArticulos(pedidoBase.ObtenerCantidadArticulos() - MaximoArticulos);
Console.WriteLine($"Pedido fragmentado generado: {pedidoFragmentado}");
}
Console.WriteLine($"Pedido final gestionado: {pedidoBase}");
}
}
}
2.4. Código Cliente para Probar con Prototype
using System;
namespace EjemplosPrototype
{
internal class Programa
{
static void Main(string[] args)
{
EjemploConPatron();
Console.ReadLine();
}
private static void EjemploConPatron()
{
Console.WriteLine("--- PRUEBA CON EL PATRÓN PROTOTYPE ---");
// Pedido de Cliente (individual)
var pedidoAna = new ConPatron.PedidoCliente
{
NombreComprador = "Ana Rojas",
IdArticulo = $"ELEM{new Random().Next(100, 999)}"
};
pedidoAna.EstablecerCantidadArticulos(2966);
var gestorP1 = new ConPatron.ProcesadorPedidos();
gestorP1.GestionarPedido(pedidoAna);
// Pedido Empresarial
var pedidoTech = new ConPatron.PedidoEmpresarial
{
RazonSocial = "Global Corp Ltda.",
IdArticulo = $"ELEM{new Random().Next(100, 999)}"
};
pedidoTech.EstablecerCantidadArticulos(3001);
var gestorP2 = new ConPatron.ProcesadorPedidos();
gestorP2.GestionarPedido(pedidoTech);
}
}
}
Con el patrón Prototype, logramos la funcionalidad deseada y, además, el ProcesadorPedidos es ahora agnóstico a los tipos concretos de pedidos. Si agregamos un nuevo tipo de pedido, solo necesitamos asegurarnos de que implemente IOrdenClonable, sin modificar la lógica del procesador.
3. Prototipo Original vs. Instancia Clonada
Es fundamental entender que una instancia prototipo y la instancia clonada son objetos completamente diferentes en memoria. Después de la clonación, cualquier modificación en el clon no afectará al original, siempre y cuando la clonación sea profunda.
3.1. Demostración Manual de Copia Superficial
using System;
namespace EjemplosPrototype
{
internal class Programa
{
static void Main(string[] args)
{
DemostrarClonacionSuperficialManual();
Console.ReadLine();
}
private static void DemostrarClonacionSuperficialManual()
{
Console.WriteLine("\n--- Demostración de Copia Superficial Manual ---");
// Crear instancia original
var original = new ConPatron.PedidoCliente
{
NombreComprador = "Carlos V.",
IdArticulo = "XYZ123"
};
original.EstablecerCantidadArticulos(500);
Console.WriteLine($"Original (antes de clonar): {original}");
// Clonar el objeto
var clonado = (ConPatron.PedidoCliente)original.Clonar();
Console.WriteLine($"Clonado (inicial): {clonado}");
// Modificar el objeto clonado
clonado.NombreComprador = "María S.";
clonado.IdArticulo = "ABC456";
clonado.EstablecerCantidadArticulos(150);
Console.WriteLine($"Clonado (modificado): {clonado}");
// Mostrar el original de nuevo para verificar que no ha cambiado
Console.WriteLine($"Original (después de modificar clon): {original}");
}
}
}
4. Clonación en C# con System.ICloneable
El framework .NET proporciona la interfaz System.ICloneable y el método Object.MemberwiseClone() (disponible para todas las clases) para facilitar la clonación.
ICloneabledefine un único métodoClone()que devuelve unobject.MemberwiseClone()crea una copia superficial bit a bit del objeto actual.
4.1. Interfaz de Pedido sin Método Clonar (ya cubierto por ICloneable)
using System;
namespace EjemplosPrototype.ClonableNet
{
/// <summary>
/// Interfaz para pedidos, sin el método Clonar explícito aquí.
/// </summary>
internal interface IInterfazPedido
{
int ObtenerCantidadArticulos();
void EstablecerCantidadArticulos(int cantidad);
}
}
4.2. Clase Concreta Implementando ICloneable
using System;
namespace EjemplosPrototype.ClonableNet
{
/// <summary>
/// Pedido individual que implementa System.ICloneable.
/// </summary>
internal class PedidoUnico : IInterfazPedido, ICloneable
{
public string? NombreSolicitante { get; set; }
public string? IdentificadorProducto { get; set; }
private int _cantidadProducto = 0;
// Implementación de ICloneable.Clone usando MemberwiseClone para copia superficial
public object Clone()
{
return this.MemberwiseClone();
}
public int ObtenerCantidadArticulos()
{
return this._cantidadProducto;
}
public void EstablecerCantidadArticulos(int cantidad)
{
this._cantidadProducto = cantidad;
}
public override string ToString()
{
return $"Pedido Único: Solicitante: '{NombreSolicitante}', Producto ID: '{IdentificadorProducto}', Cantidad: '{_cantidadProducto}'";
}
}
}
4.3. Código Cliente para Probar ICloneable
using System;
namespace EjemplosPrototype
{
internal class Programa
{
static void Main(string[] args)
{
DemostrarClonacionConICloneable();
Console.ReadLine();
}
private static void DemostrarClonacionConICloneable()
{
Console.WriteLine("\n--- Demostración de Clonación con System.ICloneable ---");
var pedidoProto = new ClonableNet.PedidoUnico
{
NombreSolicitante = "Elena R.",
IdentificadorProducto = "PRD999"
};
pedidoProto.EstablecerCantidadArticulos(750);
Console.WriteLine($"Prototipo original: {pedidoProto}");
var pedidoClon = (ClonableNet.PedidoUnico)pedidoProto.Clone();
Console.WriteLine($"Clonado inicialmente: {pedidoClon}");
// Modificar el clon
pedidoClon.NombreSolicitante = "Fernando G.";
pedidoClon.EstablecerCantidadArticulos(200);
Console.WriteLine($"Clonado modificado: {pedidoClon}");
Console.WriteLine($"Prototipo después de modificar clon: {pedidoProto}");
}
}
}
5. Clonación Profunda
La clonación profunda es necesaria cuando un objeto contiene referencias a otros objetos y queremos que la copia sea completamente independiente, es decir, que los objetos referenciados también sean clones, no las mismas instancias.
5.1. Implementación Manual de Clonación Profunda
Para la clonación profunda, cada objeto referenciado debe tener su propio mecanismo de clonación, y el método Clone() del objeto padre debe invocar recursivamente los métodos Clone() de sus atributos de tipo referencia.
5.1.1. Interfaz para Productos Clonables
using System;
namespace EjemplosPrototype.ClonacionProfunda
{
/// <summary>
/// Interfaz para cualquier producto que pueda clonarse a sí mismo.
/// </summary>
internal interface IProductoClonable
{
IProductoClonable DuplicarProducto();
}
}
5.1.2. Clase Producto (implementa clonación)
using System;
namespace EjemplosPrototype.ClonacionProfunda
{
/// <summary>
/// Objeto que representa un producto, con clonación superficial propia.
/// </summary>
internal class Articulo : IProductoClonable
{
public string? NumeroSerie { get; set; }
public string? Descripcion { get; set; }
public IProductoClonable DuplicarProducto()
{
// Copia superficial del objeto Articulo
return new Articulo
{
NumeroSerie = this.NumeroSerie,
Descripcion = this.Descripcion
};
}
public override string ToString()
{
return $"Artículo [No. Serie: '{NumeroSerie}', Descripción: '{Descripcion}']";
}
}
}
5.1.3. Pedido con Atributo de Tipo Referencia (Articulo)
using System;
using EjemplosPrototype.ConPatron; // Reusamos IOrdenClonable
namespace EjemplosPrototype.ClonacionProfunda
{
/// <summary>
/// Pedido individual con un objeto Articulo referenciado,
/// implementando una clonación profunda manual.
/// </summary>
internal class PedidoConArticulo : IOrdenClonable
{
public string? NombreSolicitante { get; set; }
public string? IdentificadorGlobal { get; set; }
public Articulo? ObjetoArticulo { get; set; } // Atributo de tipo referencia
private int _cantidadTotal = 0;
public IOrdenClonable Clonar()
{
// Realiza una copia superficial para los tipos de valor/string
var copia = new PedidoConArticulo
{
NombreSolicitante = this.NombreSolicitante,
IdentificadorGlobal = this.IdentificadorGlobal
};
copia.EstablecerCantidadArticulos(this._cantidadTotal);
// ¡Clonación profunda para el objeto referenciado!
if (this.ObjetoArticulo != null)
{
copia.ObjetoArticulo = (Articulo)this.ObjetoArticulo.DuplicarProducto();
}
return copia;
}
public int ObtenerCantidadArticulos()
{
return _cantidadTotal;
}
public void EstablecerCantidadArticulos(int cantidad)
{
this._cantidadTotal = cantidad;
}
public override string ToString()
{
return $"Pedido con Artículo: Solicitante: '{NombreSolicitante}', ID Global: '{IdentificadorGlobal}', Cantidad: '{_cantidadTotal}', {ObjetoArticulo}";
}
}
}
5.1.4. Código Cliente para Demostrar Clonación Profunda Manual
using System;
namespace EjemplosPrototype
{
internal class Programa
{
static void Main(string[] args)
{
DemostrarClonacionProfundaManual();
Console.ReadLine();
}
private static void DemostrarClonacionProfundaManual()
{
Console.WriteLine("\n--- Demostración de Clonación Profunda Manual ---");
var prodOriginal = new ClonacionProfunda.Articulo
{
NumeroSerie = "NSX001",
Descripcion = "Teclado Mecánico"
};
var pedidoOriginal = new ClonacionProfunda.PedidoConArticulo
{
NombreSolicitante = "Laura G.",
IdentificadorGlobal = $"PED{new Random().Next(100, 999)}",
ObjetoArticulo = prodOriginal
};
pedidoOriginal.EstablecerCantidadArticulos(10);
Console.WriteLine($"Pedido original (antes de clonar): {pedidoOriginal}");
var pedidoClonado = (ClonacionProfunda.PedidoConArticulo)pedidoOriginal.Clonar();
Console.WriteLine($"Pedido clonado (inicial): {pedidoClonado}");
// Modificar el clonado
pedidoClonado.NombreSolicitante = "Pedro A.";
pedidoClonado.EstablecerCantidadArticulos(5);
if (pedidoClonado.ObjetoArticulo != null)
{
pedidoClonado.ObjetoArticulo.Descripcion = "Mouse Gaming";
pedidoClonado.ObjetoArticulo.NumeroSerie = "MSX002";
}
Console.WriteLine($"Pedido clonado (modificado): {pedidoClonado}");
// Mostrar el original para verificar independencia
Console.WriteLine($"Pedido original (después de modificar clon): {pedidoOriginal}");
// Confirmar que las referencias al Articulo son diferentes
Console.WriteLine($"Original Artículo Hash: {pedidoOriginal.ObjetoArticulo?.GetHashCode()}");
Console.WriteLine($"Clonado Artículo Hash: {pedidoClonado.ObjetoArticulo?.GetHashCode()}");
}
}
}
5.2. Clonación Profunda con ICloneable de C#
Podemos combinar MemberwiseClone() para la copia superficial inicial y luego llamar a Clone() en cada objeto referenciado que también implemente ICloneable.
5.2.1. Clase Producto (implementa ICloneable)
using System;
namespace EjemplosPrototype.ClonacionProfundaNet
{
/// <summary>
/// Objeto Articulo que implementa System.ICloneable.
/// </summary>
internal class ElementoCompra : ICloneable
{
public string? SKU { get; set; }
public string? Nombre { get; set; }
public object Clone()
{
// Copia superficial usando MemberwiseClone
return this.MemberwiseClone();
}
public override string ToString()
{
return $"Elemento Compra [SKU: '{SKU}', Nombre: '{Nombre}']";
}
}
}
5.2.2. Pedido con Atributo de Tipo Referencia (ElementoCompra)
using System;
using EjemplosPrototype.ClonableNet; // Reusamos IInterfazPedido
namespace EjemplosPrototype.ClonacionProfundaNet
{
/// <summary>
/// Pedido que contiene una referencia a ElementoCompra,
/// implementando clonación profunda con ICloneable.
/// </summary>
internal class OrdenDeCompra : IInterfazPedido, ICloneable
{
public string? Comprador { get; set; }
public string? IDUnicoOrden { get; set; }
public ElementoCompra? DetalleElemento { get; set; } // Referencia a ElementoCompra
private int _totalCantidad = 0;
public object Clone()
{
// Primero, una copia superficial del objeto OrdenDeCompra
OrdenDeCompra clon = (OrdenDeCompra)this.MemberwiseClone();
// Luego, clonación profunda para el objeto referenciado (si existe)
if (this.DetalleElemento != null)
{
clon.DetalleElemento = (ElementoCompra)this.DetalleElemento.Clone();
}
return clon;
}
public int ObtenerCantidadArticulos()
{
return this._totalCantidad;
}
public void EstablecerCantidadArticulos(int cantidad)
{
this._totalCantidad = cantidad;
}
public override string ToString()
{
return $"Orden de Compra: Comprador: '{Comprador}', ID Orden: '{IDUnicoOrden}', Cantidad: '{_totalCantidad}', {DetalleElemento}";
}
}
}
5.2.3. Código Cliente para Demostrar Clonación Profunda con ICloneable
using System;
namespace EjemplosPrototype
{
internal class Programa
{
static void Main(string[] args)
{
DemostrarClonacionProfundaConICloneable();
Console.ReadLine();
}
private static void DemostrarClonacionProfundaConICloneable()
{
Console.WriteLine("\n--- Demostración de Clonación Profunda con System.ICloneable en .NET ---");
var elemOriginal = new ClonacionProfundaNet.ElementoCompra
{
SKU = "GAD007",
Nombre = "Auriculares Inalámbricos"
};
var ordenOriginal = new ClonacionProfundaNet.OrdenDeCompra
{
Comprador = "Sofía M.",
IDUnicoOrden = $"ORD{new Random().Next(100, 999)}",
DetalleElemento = elemOriginal
};
ordenOriginal.EstablecerCantidadArticulos(3);
Console.WriteLine($"Orden original (antes de clonar): {ordenOriginal}");
var ordenClonada = (ClonacionProfundaNet.OrdenDeCompra)ordenOriginal.Clone();
Console.WriteLine($"Orden clonada (inicial): {ordenClonada}");
// Modificar el clonado
ordenClonada.Comprador = "Roberto F.";
ordenClonada.EstablecerCantidadArticulos(1);
if (ordenClonada.DetalleElemento != null)
{
ordenClonada.DetalleElemento.Nombre = "Cargador USB-C";
ordenClonada.DetalleElemento.SKU = "ACC012";
}
Console.WriteLine($"Orden clonada (modificada): {ordenClonada}");
// Mostrar el original para verificar independencia
Console.WriteLine($"Orden original (después de modificar clon): {ordenOriginal}");
// Confirmar que las referencias al ElementoCompra son diferentes
Console.WriteLine($"Original Elemento Hash: {ordenOriginal.DetalleElemento?.GetHashCode()}");
Console.WriteLine($"Clonado Elemento Hash: {ordenClonada.DetalleElemento?.GetHashCode()}");
}
}
}
6. Gestor de Prototipos (Prototype Manager)
Cuando el número de prototipos en un sistema es dinámico, un gestor de prototipos (también conociod como registro de prototipos) es útil. Este gestor mantiene una colección de prototipos disponibles, permitiendo registrarlos, modificarlos, eliminarlos y recuperarlos para clonación en tiempo de ejecución. Esto permite una gestión centralizada y dinámica de los prototipos.
6.1. Interfaz para Prototipos Registrables
using System;
namespace EjemplosPrototype.GestorPrototipos
{
/// <summary>
/// Interfaz que todo prototipo gestionable debe implementar.
/// </summary>
internal interface IPrototipoGestionable
{
IPrototipoGestionable CrearCopia();
string ObtenerIdentificador();
void EstablecerIdentificador(string id);
}
}
6.2. Clases de Prototipos Concretos
using System;
namespace EjemplosPrototype.GestorPrototipos
{
/// <summary>
/// Una implementación concreta de un prototipo gestionable.
/// </summary>
internal class ImplementacionPrototipoA : IPrototipoGestionable
{
private string _nombreAsociado;
public ImplementacionPrototipoA(string nombre)
{
_nombreAsociado = nombre;
}
public IPrototipoGestionable CrearCopia()
{
return new ImplementacionPrototipoA(this._nombreAsociado);
}
public string ObtenerIdentificador()
{
return _nombreAsociado;
}
public void EstablecerIdentificador(string id)
{
this._nombreAsociado = id;
}
public override string ToString()
{
return $"Prototipo A con nombre: '{_nombreAsociado}'";
}
}
}
using System;
namespace EjemplosPrototype.GestorPrototipos
{
/// <summary>
/// Otra implementación concreta de un prototipo gestionable.
/// </summary>
internal class ImplementacionPrototipoB : IPrototipoGestionable
{
private string _tipoModelo;
public ImplementacionPrototipoB(string tipo)
{
_tipoModelo = tipo;
}
public IPrototipoGestionable CrearCopia()
{
return new ImplementacionPrototipoB(this._tipoModelo);
}
public string ObtenerIdentificador()
{
return _tipoModelo;
}
public void EstablecerIdentificador(string id)
{
this._tipoModelo = id;
}
public override string ToString()
{
return $"Prototipo B de tipo: '{_tipoModelo}'";
}
}
}
6.3. Clase Gestor de Prototipos
using System;
using System.Collections.Generic;
namespace EjemplosPrototype.GestorPrototipos
{
/// <summary>
/// Administra un registro de objetos prototipo.
/// </summary>
internal class AdministradorDePrototipos
{
private static Dictionary<string, IPrototipoGestionable> _registro = new Dictionary<string, IPrototipoGestionable>();
private AdministradorDePrototipos() { /* Constructor privado */ }
public static void RegistrarPrototipo(string clave, IPrototipoGestionable prototipo)
{
if (string.IsNullOrEmpty(clave) || prototipo == null)
{
Console.WriteLine("Error: La clave o el prototipo no pueden ser nulos.");
return;
}
if (!_registro.ContainsKey(clave))
{
_registro.Add(clave, prototipo);
Console.WriteLine($"Prototipo '{clave}' registrado.");
}
else
{
Console.WriteLine($"Advertencia: Ya existe un prototipo con la clave '{clave}'.");
}
}
public static void EliminarPrototipo(string clave)
{
if (_registro.ContainsKey(clave))
{
_registro.Remove(clave);
Console.WriteLine($"Prototipo '{clave}' eliminado.");
}
else
{
Console.WriteLine($"Advertencia: No se encontró prototipo con la clave '{clave}' para eliminar.");
}
}
public static IPrototipoGestionable? ObtenerPrototipo(string clave)
{
if (_registro.TryGetValue(clave, out var prototipo))
{
return prototipo;
}
Console.WriteLine($"Error: Prototipo con clave '{clave}' no encontrado.");
return null;
}
public static void ActualizarPrototipo(string clave, IPrototipoGestionable nuevoPrototipo)
{
if (string.IsNullOrEmpty(clave) || nuevoPrototipo == null)
{
Console.WriteLine("Error: La clave o el nuevo prototipo no pueden ser nulos.");
return;
}
if (_registro.ContainsKey(clave))
{
_registro[clave] = nuevoPrototipo;
Console.WriteLine($"Prototipo '{clave}' actualizado.");
}
else
{
Console.WriteLine($"Advertencia: No se encontró prototipo con la clave '{clave}' para actualizar.");
}
}
}
}
6.4. Código Cliente para Usar el Gestor de Prototipos
using System;
namespace EjemplosPrototype
{
internal class Programa
{
static void Main(string[] args)
{
UsoDelAdministradorDePrototipos();
Console.ReadLine();
}
private static void UsoDelAdministradorDePrototipos()
{
Console.WriteLine("\n--- Uso del Administrador de Prototipos ---");
// Registrar prototipos iniciales
AdministradorDePrototipos.RegistrarPrototipo("config_basica", new GestorPrototipos.ImplementacionPrototipoA("Configuracion Predeterminada"));
AdministradorDePrototipos.RegistrarPrototipo("template_doc", new GestorPrototipos.ImplementacionPrototipoB("Documento Estándar"));
// Obtener y clonar un prototipo
var config1 = AdministradorDePrototipos.ObtenerPrototipo("config_basica")?.CrearCopia();
config1?.EstablecerIdentificador("Mi Configuracion Personal");
Console.WriteLine($"Instancia 1: {config1}");
// Actualizar un prototipo en el registro
var nuevoTemplate = new GestorPrototipos.ImplementacionPrototipoB("Plantilla Especial");
AdministradorDePrototipos.ActualizarPrototipo("template_doc", nuevoTemplate);
// Obtener y clonar el prototipo actualizado
var doc2 = AdministradorDePrototipos.ObtenerPrototipo("template_doc")?.CrearCopia();
doc2?.EstablecerIdentificador("Reporte Mensual");
Console.WriteLine($"Instancia 2: {doc2}");
// Intentar obtener un prototipo eliminado
AdministradorDePrototipos.EliminarPrototipo("config_basica");
var config3 = AdministradorDePrototipos.ObtenerPrototipo("config_basica")?.CrearCopia();
config3?.EstablecerIdentificador("Configuracion de Respaldo");
Console.WriteLine($"Instancia 3: {config3}"); // Esto debería mostrar un error
}
}
}