Refactor de un módulo de reporte de formas geométricas, aplicando principios de programación orientada a objetos para facilitar la extensión de nuevas formas e idiomas sin modificar código existente.
La clase FormaGeometrica concentraba toda la responsabilidad en un único archivo: calculaba áreas y perímetros, generaba el reporte, y manejaba las traducciones mediante switch e if anidados con constantes enteras (int tipo, int idioma). Agregar una nueva forma o un nuevo idioma requería modificar múltiples métodos en la misma clase.
Se descompuso la clase original en una jerarquía de interfaces e implementaciones independientes.
DevelopmentChallenge.Data/
├── Interfaces/
│ ├── IFormaGeometrica.cs # Contrato para toda forma geométrica
│ └── IIdioma.cs # Contrato para todo idioma
├── Formas/
│ └── Formas.cs # Cuadrado, Circulo, TrianguloEquilatero, Trapecio, Rectangulo
├── Idiomas/
│ ├── Castellano.cs
│ ├── Ingles.cs
└── Classes/
└── ReporteFormas.cs # Orquestador del reporte
DevelopmentChallenge.Data.Tests/
├── DataTests.cs # Tests originales migrados
└── FormaGeometricaTests.cs # Tests nuevos (Trapecio, Rectangulo)
IFormaGeometrica
public interface IFormaGeometrica
{
decimal CalcularArea();
decimal CalcularPerimetro();
string ObtenerNombre(IIdioma idioma, int cantidad);
}IIdioma
public interface IIdioma
{
string ObtenerEncabezado();
string ObtenerEncabezadoVacio();
string ObtenerPalabrasFormas();
string ObtenerPalabraPerimetro();
string ObtenerNombreSingular(string forma);
string ObtenerNombrePlural(string forma);
}| Clase | Parámetros del constructor |
|---|---|
Cuadrado |
lado |
Circulo |
diametro |
TrianguloEquilatero |
lado |
Trapecio |
baseMayor, baseMenor, altura |
Rectangulo |
ancho, alto |
| Clase | Idioma |
|---|---|
Castellano |
Español |
Ingles |
Inglés |
var formas = new List<IFormaGeometrica>
{
new Cuadrado(5),
new Circulo(3),
new TrianguloEquilatero(4),
new Trapecio(6, 4, 3)
};
string reporte = ReporteFormas.Imprimir(formas, new Castellano());Single Responsibility (SRP) — cada clase tiene una única razón para cambiar. ReporteFormas orquesta, las formas calculan geometría, los idiomas traducen.
Open/Closed (OCP) — el sistema está abierto a extensión y cerrado a modificación. Agregar una nueva forma o idioma no requiere tocar ninguna clase existente.
Liskov Substitution (LSP) — cualquier IFormaGeometrica es intercambiable. ReporteFormas no sabe si está procesando un Circulo o un Trapecio.
Strategy Pattern — los idiomas son estrategias intercambiables en runtime. Se pasa la instancia de idioma deseada al momento de generar el reporte.
- Crear una clase que implemente
IFormaGeometrica. - Agregar su nombre singular y plural en el diccionario de cada clase de idioma.
- No modificar nada más.
public class Pentagono : IFormaGeometrica
{
private readonly decimal _lado;
public Pentagono(decimal lado) { _lado = lado; }
public decimal CalcularArea() => (decimal)(Math.Sqrt(25 + 10 * Math.Sqrt(5)) / 4) * _lado * _lado;
public decimal CalcularPerimetro() => _lado * 5;
public string ObtenerNombre(IIdioma idioma, int cantidad) =>
cantidad == 1 ? idioma.ObtenerNombreSingular("Pentagono")
: idioma.ObtenerNombrePlural("Pentagono");
}- Crear una clase que implemente
IIdioma. - Completar el diccionario con los nombres de todas las formas.
- No modificar nada más.
public class Frances : IIdioma
{
private static readonly Dictionary<string, (string, string)> _traducciones =
new Dictionary<string, (string, string)>
{
{ "Cuadrado", ("Carré", "Carrés") },
{ "Circulo", ("Cercle", "Cercles") },
{ "TrianguloEquilatero", ("Triangle", "Triangles") },
{ "Trapecio", ("Trapèze", "Trapèzes") },
{ "Rectangulo", ("Rectangle", "Rectangles")},
};
public string ObtenerEncabezado() => "<h1>Rapport des formes</h1>";
public string ObtenerEncabezadoVacio() => "<h1>Liste vide de formes!</h1>";
public string ObtenerPalabrasFormas() => "formes";
public string ObtenerPalabraPerimetro() => "Périmètre";
public string ObtenerNombreSingular(string forma) =>
_traducciones.TryGetValue(forma, out var t) ? t.Item1 : forma;
public string ObtenerNombrePlural(string forma) =>
_traducciones.TryGetValue(forma, out var t) ? t.Item2 : forma;
}El proyecto de tests usa NUnit 4 sobre .NET Framework 4.8.
NUnit 4.6.0
NUnit.ConsoleRunner 3.22.0
NUnit3TestAdapter 6.2.0
Para correr los tests: Test Explorer → Run All o click derecho sobre un test → Debug Test para correr con debugger.
Los tests cubren: lista vacía en los dos idiomas, forma única, plurales, mezcla de tipos, Trapecio, Rectangulo.