Simular el diseño y posterior refactorización de una aplicación que gestiona una clínica veterinaria y un hotel de mascotas, aplicando patrones de diseño creacionales (Factory Method, Abstract Factory, Singleton, Builder, Prototype).
Se busca que el alumno identifique problemas en un código espagueti inicial y proponga soluciones con base en los patrones GoF.
El proyecto inicial presenta múltiples fallos de diseño:
- Código duplicado al crear instancias de diferentes tipos de mascotas.
- Alta dependencia entre clases concretas.
- Mezcla de lógica de clínica, hotel y facturación en Main().
- No existe un punto central de configuración.
- Clases monolíticas con múltiples responsabilidades.
- Métodos largos y difíciles de probar.
- Enumeraciones usadas incorrectamente para distinguir servicios.
- Propiedades públicas expuestas sin encapsulación.
- No hay fábrica para habitaciones del hotel.
- Sin separación de capas (todo está en el mismo archivo).
Para resolver los problemas anteriores, se aplicaron los siguientes patrones creacionales:
- Permite crear familias de objetos (Perro, Gato, Ave) sin depender de clases concretas.
- Ventaja: evita código duplicado y facilita extensión con nuevas especies.
public interface IMascotaFactory { IMascota CrearMascota(string nombre); }
public class PerroFactory : IMascotaFactory { public IMascota CrearMascota(string n) => new Perro(n); }
public class GatoFactory : IMascotaFactory { public IMascota CrearMascota(string n) => new Gato(n); }var perroFac = MascotaFactoryProvider.GetFactory("perro");
var perro = perroFac.CrearMascota("Firulais");- Permite instanciar distintos tipos de habitación (Estándar, Suite, Aislada) según la necesidad.
- Ventaja: encapsula la lógica de creación, facilita la extensión.
public abstract class HotelCreator {
public abstract Habitacion CrearHabitacion(string tipo);
}
public class HotelConcretoCreator : HotelCreator {
public override Habitacion CrearHabitacion(string tipo) {
return tipo == "suite" ? new HabitacionSuite(1) : new HabitacionEstandar(1);
}
}- Garantiza una única instancia del hotel en toda la aplicación.
- Se implementó con Lazy para hacerlo thread-safe.
public sealed class HotelManager {
private static readonly Lazy<HotelManager> instancia = new Lazy<HotelManager>(() => new HotelManager());
public static HotelManager Instance => instancia.Value;
private HotelManager() { }
}- Permite construir facturas paso a paso (consulta, vacunas, servicios extra).
- Evita constructores con demasiados parámetros.
public interface IFacturaBuilder {
IFacturaBuilder ParaMascota(IMascota mascota);
IFacturaBuilder AgregarItem(string item, decimal precio);
Factura Build();
}Uso:
var factura = new FacturaBuilder()
.ParaMascota(perro)
.AgregarItem("Consulta general", 30)
.Build();- Se pueden clonar rutinas médicas o de cuidado (ej. “Chequeo básico”) y adaptarlas a cada mascota.
- Ahorra tiempo y evita repetir pasos.
public class RutinaServicio : ICloneable {
public List<string> Pasos { get; set; } = new();
public object Clone() => new RutinaServicio { Pasos = new List<string>(this.Pasos) };
}Uso:
var rutinaBase = new RutinaServicio { Pasos = {"Revisión", "Vacunación"} };
var rutinaClon = (RutinaServicio)rutinaBase.Clone();
rutinaClon.Pasos.Add("Control de peso");- name: Instalar .NET 8 y preparar entorno para Clínica Veterinaria y Hotel de Mascotas
hosts: localhost
become: yes
tasks:
- name: Actualizar sistema
apt:
update_cache: yes
upgrade: dist
- name: Instalar herramientas necesarias
apt:
name:
- apt-transport-https
- ca-certificates
- curl
- software-properties-common
state: present
- name: Descargar repositorio de Microsoft
get_url:
url: https://packages.microsoft.com/config/ubuntu/24.04/packages-microsoft-prod.deb
dest: /tmp/packages-microsoft-prod.deb
- name: Instalar repositorio de Microsoft
apt:
deb: /tmp/packages-microsoft-prod.deb
- name: Actualizar caché apt
apt:
update_cache: yes
- name: Instalar .NET SDK 8.0
apt:
name: dotnet-sdk-8.0
state: present
- name: Crear directorio del proyecto
file:
path: /home/ubuntu/clinica_mascotas_app
state: directory
- name: Crear archivo del proyecto
copy:
dest: /home/ubuntu/clinica_mascotas_app/clinica_mascotas_app.csproj
content: |
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
</Project>
- name: Crear código base refactorizado con patrones de diseño
copy:
dest: /home/ubuntu/clinica_mascotas_app/Program.cs
content: |
using System;
using System.Collections.Generic;
using System.Threading;
namespace ClinicaMascotas
{
// Singleton Thread-Safe para registro de mascotas
public sealed class RegistroMascotas
{
private static readonly Lazy<RegistroMascotas> instancia =
new Lazy<RegistroMascotas>(() => new RegistroMascotas(), LazyThreadSafetyMode.ExecutionAndPublication);
private List<Mascota> mascotas = new List<Mascota>();
private RegistroMascotas() {}
public static RegistroMascotas Instancia => instancia.Value;
public void RegistrarMascota(Mascota mascota)
{
mascotas.Add(mascota);
Console.WriteLine($"Mascota registrada: {mascota.Nombre} ({mascota.Tipo})");
}
}
// Entidad Mascota
public class Mascota
{
public string Nombre { get; }
public string Tipo { get; }
public Mascota(string nombre, string tipo)
{
Nombre = nombre;
Tipo = tipo;
}
}
// Abstract Factory para Alimentos, Bebidas y Suplementos
public interface INutricionFactory
{
IAlimento CrearAlimento();
IBebida CrearBebida();
ISuplemento CrearSuplemento();
}
public interface IAlimento { string Describir(); }
public interface IBebida { string Describir(); }
public interface ISuplemento { string Describir(); }
// Implementación de Nutrición Canina
public class NutricionCaninaFactory : INutricionFactory
{
public IAlimento CrearAlimento() => new Croquetas();
public IBebida CrearBebida() => new Agua();
public ISuplemento CrearSuplemento() => new Vitaminas();
}
public class Croquetas : IAlimento { public string Describir() => "Croquetas premium"; }
public class Agua : IBebida { public string Describir() => "Agua fresca"; }
public class Vitaminas : ISuplemento { public string Describir() => "Vitaminas para energía"; }
// Builder para Rutinas de Alimentación
public class RutinaAlimentacion
{
public List<string> Pasos { get; } = new List<string>();
public void Mostrar()
{
Console.WriteLine("Rutina de Alimentación:");
foreach (var paso in Pasos) Console.WriteLine($"- {paso}");
}
}
public interface IRutinaBuilder
{
void PrepararAlimento();
void PrepararBebida();
void PrepararSuplemento();
RutinaAlimentacion ObtenerRutina();
}
public class RutinaCaninaBuilder : IRutinaBuilder
{
private RutinaAlimentacion rutina = new RutinaAlimentacion();
private readonly INutricionFactory factory;
public RutinaCaninaBuilder(INutricionFactory factory)
{
this.factory = factory;
}
public void PrepararAlimento() => rutina.Pasos.Add(factory.CrearAlimento().Describir());
public void PrepararBebida() => rutina.Pasos.Add(factory.CrearBebida().Describir());
public void PrepararSuplemento() => rutina.Pasos.Add(factory.CrearSuplemento().Describir());
public RutinaAlimentacion ObtenerRutina() => rutina;
}
// Prototype para clonar rutinas
public class RutinaPrototype : ICloneable
{
public RutinaAlimentacion Rutina { get; set; }
public object Clone()
{
var nuevaRutina = new RutinaAlimentacion();
nuevaRutina.Pasos.AddRange(this.Rutina.Pasos);
return new RutinaPrototype { Rutina = nuevaRutina };
}
}
// Factory Method para dietas
public abstract class DietaCreator
{
public abstract string CrearDieta();
}
public class DietaCanina : DietaCreator
{
public override string CrearDieta() => "Dieta balanceada para perro";
}
public class DietaFelina : DietaCreator
{
public override string CrearDieta() => "Dieta balanceada para gato";
}
// Servicios
public class ServicioVeterinario
{
public void RegistrarConsulta(Mascota mascota) =>
Console.WriteLine($"Consulta registrada para {mascota.Nombre}");
public void RegistrarVacuna(Mascota mascota) =>
Console.WriteLine($"Vacuna aplicada a {mascota.Nombre}");
}
public class HotelMascotas
{
public void AsignarHabitacion(Mascota mascota) =>
Console.WriteLine($"Habitación asignada a {mascota.Nombre}");
}
// Programa principal
public class Program
{
public static void Main(string[] args)
{
var dog = new Mascota("Firulais", "Perro");
var cat = new Mascota("Mishi", "Gato");
RegistroMascotas.Instancia.RegistrarMascota(dog);
RegistroMascotas.Instancia.RegistrarMascota(cat);
var vet = new ServicioVeterinario();
vet.RegistrarConsulta(dog);
vet.RegistrarVacuna(cat);
var hotel = new HotelMascotas();
hotel.AsignarHabitacion(dog);
hotel.AsignarHabitacion(cat);
var factory = new NutricionCaninaFactory();
var builder = new RutinaCaninaBuilder(factory);
builder.PrepararAlimento();
builder.PrepararBebida();
builder.PrepararSuplemento();
var rutina = builder.ObtenerRutina();
rutina.Mostrar();
var rutinaPrototype = new RutinaPrototype { Rutina = rutina };
var rutinaClonada = (RutinaPrototype)rutinaPrototype.Clone();
rutinaClonada.Rutina.Mostrar();
DietaCreator dieta = new DietaCanina();
Console.WriteLine(dieta.CrearDieta());
}
}
}
- name: Compilar la aplicación
command: dotnet build /home/ubuntu/clinica_mascotas_app/clinica_mascotas_app.csproj
args:
chdir: /home/ubuntu/clinica_mascotas_app
- name: Ejecutar la aplicación
command: dotnet run
args:
chdir: /home/ubuntu/clinica_mascotas_app
async: 10
poll: 0
🧾 Propuesta de calificación
Dado que es una práctica inicial, y considerando el dominio más amplio que Joel abordó, la integración funcional demostrada y los recursos aplicados:
Calificación sugerida: 90 / 100
Razonamiento:
Joel aplica correctamente todos los patrones requeridos (Abstract Factory, Factory Method, Singleton, Builder, Prototype).
Logra integrarlos en un escenario más complejo que solo “granja”, lo cual es un plus.
La propuesta de diseño parte de un diagnóstico claro del problema inicial.
Las áreas de mejora identificadas (tests, documentación interna, extensibilidad, validaciones) son mejoras esperables a futuro, no debilitan la solución principal.