Dominar el principio DRY: Aplicación de patrones de diseño en C# para un código más limpio
Los patrones de diseño en C# son herramientas esenciales para escribir código eficiente, reutilizable y fácil de mantener. Estos patrones proporcionan soluciones estándar a problemas comunes de diseño de software, promoviendo las mejores prácticas y ayudando a los desarrolladores a evitar código redundante. Uno de los principios básicos en la aplicación de patrones de diseño es el principio DRY (Don't Repeat Yourself), que hace hincapié en minimizar la repetición dentro del código para mejorar la legibilidad y la capacidad de mantenimiento.
Este artículo está inspirado en el perspicaz vídeo de Tim Corey, "Design Patterns: Don't Repeat Yourself in C#", que profundiza en el principio DRY y su aplicación práctica para crear un código más limpio y organizado. Mediante la exploración de los conceptos clave y las estrategias discutidas en el vídeo de Tim, este artículo tiene como objetivo proporcionar una guía completa para implementar el principio de patrón de diseño DRY de manera efectiva en sus proyectos de C#.
Introduction to the DRY Principle in C
En la introducción, Tim Corey explica el principio DRY, que significa "Don't Repeat Yourself" (no te repitas) Este principio es un concepto fundamental en programación que hace hincapié en evitar la redundancia asegurándose de que cada pieza de conocimiento o lógica esté representada en un único lugar del código. Tim ilustra el principio con un ejemplo sencillo de una aplicación WinForms con un formulario de cuadro de mando. El formulario incluye campos para introducir un nombre y un apellido, y un botón para generar un ID de empleado basado en estos campos.
Identificar y anticipar la repetición de código
En el minuto (0:53), Tim pasa a identificar y anticipar las repeticiones en el código. Utiliza el ejemplo de la aplicación WinForms para mostrar cómo puede producirse la repetición, incluso cuando los métodos se llaman una sola vez. En la aplicación, la lógica de generación del ID de empleado consiste en extraer subcadenas de los campos de texto para el nombre y los apellidos y añadir un código de 3 dígitos al final.

En la captura de pantalla anterior (1:31), Tim muestra la funcionalidad de la aplicación, mostrando cómo genera un ID de empleado combinando las cuatro primeras letras del nombre y el apellido con un código de tres dígitos. Destaca que, aunque el código parece seguir el principio DRY porque no repite la misma lógica explícitamente, hay problemas subyacentes con el patrón de repetición que deben abordarse.
En (1:51), señala que, aunque el código parece sencillo, no se adhiere plenamente al principio DRY porque la lógica para generar el ID de empleado está estrechamente vinculada al evento de clic del botón. Esto significa que si se necesitara esta lógica en otra parte del código del cliente, como al procesar una lista de nuevos empleados (3:58), habría que repetir o adaptar el código, lo que daría lugar a redundancias.
Creación de métodos independientes y reutilizables
En este segmento, Tim Corey demuestra cómo crear un método independiente y reutilizable para adherirse al principio DRY. Comienza extrayendo la lógica para generar un ID de empleado del controlador de eventos en un método independiente. Esta refactorización implica crear un método privado llamado GenerateEmployeeID y mover el código existente a este método (5:15). El código revisado en el controlador de eventos simplemente llama a este método.
Pasos y ejemplo:
-
Código inicial: La lógica para generar el ID de empleado estaba directamente en el manejador de eventos de clic de un botón.

-
Código refactorizado: Tim mejora el método haciéndolo más flexible. En lugar de depender de elementos específicos de la UI, el método ahora acepta
firstNameylastNamecomo parámetros y devuelve el ID generado. Este cambio permite utilizar el método en diversos contextos y elementos de la interfaz de usuario:private string GenerateEmployeeID(string firstName, string lastName) { string employeeID = firstName.Substring(0, 4) + lastName.Substring(0, 4) + DateTime.Now.Millisecond.ToString(); return employeeID; }private string GenerateEmployeeID(string firstName, string lastName) { string employeeID = firstName.Substring(0, 4) + lastName.Substring(0, 4) + DateTime.Now.Millisecond.ToString(); return employeeID; }A continuación, Tim demuestra cómo se llama a este método desde el evento de clic:
employeeIdText.Text = GenerateEmployeeID(firstNameText.Text, lastNameText.Text);employeeIdText.Text = GenerateEmployeeID(firstNameText.Text, lastNameText.Text);También señala que este método puede utilizarse ahora en otras partes de la aplicación, como en el procesamiento de archivos CSV con múltiples registros de empleados, sin repetir el código.
Construcción y uso de una biblioteca de clases
A continuación, Tim Corey explora el concepto de biblioteca de clases para mejorar aún más la reutilización y el mantenimiento del código. Él ilustra cómo encapsular el método GenerateEmployeeID en un objeto de biblioteca de clases, que se puede usar en múltiples proyectos.
En el minuto (8:00), Tim explica que el diseño sigue cambiando en función de los requisitos del usuario o por políticas de la empresa para hacerlo más interactivo con gráficos y animaciones. Así, introduce un proyecto WPF dentro de la solución con campos exactos y un botón para Generar ID de empleado.
Tim, en el minuto (9:15), defiende firmemente el uso de una biblioteca de clases diciendo que si queremos evitar repetirnos, el código se habría copiado y pegado en el nuevo proyecto WPF. Por lo tanto, para mantenerlo DRY necesitamos crear clases en una biblioteca de clases.
Pasos y ejemplo:
-
Creación de la biblioteca de clases:
-
Tim en (9:47), crea un nuevo proyecto de biblioteca de clases en .NET Framework, llamándolo DRYDemoLibrary.
-
Dentro de esta biblioteca, él define una clase pública
EmployeeProcessory mueve el métodoGenerateEmployeeIDa esta clase:public class EmployeeProcessor { public string GenerateEmployeeID(string firstName, string lastName) { string employeeID = firstName.Substring(0, 4) + lastName.Substring(0, 4) + DateTime.Now.Millisecond.ToString(); return employeeID; } }public class EmployeeProcessor { public string GenerateEmployeeID(string firstName, string lastName) { string employeeID = firstName.Substring(0, 4) + lastName.Substring(0, 4) + DateTime.Now.Millisecond.ToString(); return employeeID; } }
-
-
Uso de la biblioteca de clases en proyectos:
-
En sus proyectos WinForms (13:18) y WPF (14:00), Tim añade una referencia a la biblioteca de clases DRYDemoLibrary.
-
Luego reemplaza el código antiguo con llamadas al método
GenerateEmployeeIDdesde la biblioteca de clases:EmployeeProcessor processor = new EmployeeProcessor(); employeeIDText.Text = processor.GenerateEmployeeID(firstNameText.Text, lastNameText.Text);EmployeeProcessor processor = new EmployeeProcessor(); employeeIDText.Text = processor.GenerateEmployeeID(firstNameText.Text, lastNameText.Text); - Este enfoque elimina la redundancia, ya que el método se mantiene ahora en un único lugar. Tim demuestra que la misma biblioteca de clases puede utilizarse en distintos marcos de interfaz de usuario (WinForms y WPF) sin repetir el código.
-
-
Ventajas:
-
Consistencia: Al centralizar la lógica en una biblioteca de clases, Tim se asegura de que los cambios en la lógica (por ejemplo, correcciones de errores) se apliquen de manera uniforme en todos los proyectos.
- Mantenimiento reducido: Los cambios en el método solo deben realizarse en la biblioteca de clases, lo que evita incoherencias y reduce la sobrecarga de mantenimiento.
-
Integración de la biblioteca de clases en varios proyectos
Tim Corey continúa explorando cómo utilizar la biblioteca de clases DRYDemoLibrary en diferentes tipos de proyectos, centrándose específicamente en la integración de la biblioteca en una nueva aplicación de consola. Esto demuestra cómo la funcionalidad de la biblioteca se puede reutilizar en varias aplicaciones, no solo en una única instancia o solo en aquellas dentro de la misma solución.
Pasos y ejemplo:
-
Creación de una nueva solución y proyecto:
-
Tim en (17:29), comienza creando una nueva solución para una aplicación de consola, simulando un escenario en el que podrías necesitar utilizar la DRYDemoLibrary en un tipo diferente de proyecto, como un servicio de Windows o una aplicación de consola.
-
Nombra el nuevo proyecto ConsoleUI y muestra cómo configurar una aplicación de consola básica.
class Program { static void Main(string[] args) { Console.ReadLine(); } }class Program { static void Main(string[] args) { Console.ReadLine(); } }
-
-
Añadir una referencia a la biblioteca de clases:
-
Tim explica cómo añadir una referencia a la DLL DRYDemoLibrary en el nuevo proyecto. Se trata de buscar el archivo DLL en la carpeta bin del proyecto de biblioteca de clases y añadirlo a la aplicación de consola.
using DRYDemoLibrary;using DRYDemoLibrary; -
Una vez añadida la referencia, Tim (19:24) utiliza la clase EmployeeProcessor de la biblioteca para generar un ID de empleado basado en la entrada del usuario.
Console.WriteLine("What is your first name?"); string firstName = Console.ReadLine(); Console.WriteLine("What is your last name?"); string lastName = Console.ReadLine(); EmployeeProcessor processor = new EmployeeProcessor(); string employeeID = processor.GenerateEmployeeID(firstName, lastName); Console.WriteLine($"Your employee ID is {employeeID}");Console.WriteLine("What is your first name?"); string firstName = Console.ReadLine(); Console.WriteLine("What is your last name?"); string lastName = Console.ReadLine(); EmployeeProcessor processor = new EmployeeProcessor(); string employeeID = processor.GenerateEmployeeID(firstName, lastName); Console.WriteLine($"Your employee ID is {employeeID}");
-
-
Ejecución de la aplicación de consola:
-
Tim demuestra la ejecución de la aplicación de consola para mostrar que genera correctamente el ID de empleado utilizando la biblioteca. Esto confirma que el mismo código de la biblioteca de clases puede reutilizarse en distintos proyectos.

-
-
Actualización de la DLL:
- Tim menciona brevemente que si la DLL cambia, se puede actualizar en los proyectos que hacen referencia a ella. Señala que, aunque este vídeo no lo trata en detalle, el uso de paquetes NuGet es un enfoque recomendado para gestionar y actualizar DLL en varios proyectos.
Actualización de DLL y gestión de paquetes NuGet
Tim Corey introduce brevemente el concepto de uso de paquetes NuGet para gestionar y actualizar bibliotecas de clases. Este enfoque ofrece una solución más escalable para gestionar dependencias y actualizaciones, especialmente en proyectos u organizaciones de mayor tamaño.
Puntos clave:
-
Creación de un paquete NuGet:
- En lugar de gestionar manualmente los archivos DLL, Tim sugiere crear un paquete NuGet para la biblioteca de clases. Esto implica empaquetar la DLL en un paquete NuGet y subirlo a un servidor NuGet (privado o público).
-
Paquetes de actualización:
- Al utilizar un paquete NuGet, puede actualizar la biblioteca en todos los proyectos que hagan referencia a ella simplemente actualizando la versión del paquete. De este modo se garantiza la coherencia y se reduce el riesgo de que se produzcan desajustes entre versiones o de que falten actualizaciones.
-
Beneficios:
-
Gestión centralizada: Los paquetes NuGet proporcionan una forma centralizada de gestionar las versiones y dependencias de las bibliotecas.
-
Facilidad de actualización: La actualización de la biblioteca en varios proyectos resulta más sencilla y fiable.
- Integración: NuGet se integra con varias herramientas y entornos de desarrollo, agilizando el proceso de gestión de dependencias de bibliotecas.
-
Implementación de DRY en pruebas unitarias: Un curso intensivo
En este segmento, Tim Corey demuestra cómo la aplicación del principio DRY (Don't Repeat Yourself) puede mejorar las pruebas unitarias. Muestra cómo aplicar los principios DRY en el trabajo de desarrollo, centrándose especialmente en las pruebas unitarias.
Configuración de la prueba inicial
Tim comienza ejecutando una prueba unitaria que falla debido a un error en la DLL. Destaca la importancia de las pruebas unitarias para identificar problemas, incluso cuando el código está fuera de la solución principal. El código esperaba una entrada de 4 letras, pero en su lugar Tim pasó un nombre de 3 letras que se cuela en el archivo DLL aunque no esté incluido directamente en la solución.

Refactorización de código para corregir errores
Para resolver el problema del tratamiento del nombre de pila, Tim refactoriza el código. Explica cómo se puede aplicar DRY al desarrollo mediante la creación de un nuevo proyecto de biblioteca de clases (23:50). Este enfoque garantiza que los cambios en varios objetos puedan hacerse una vez y probarse eficazmente sin repetir correcciones.

Añadir pruebas unitarias
Tim introduce una nueva clase de prueba como EmployeeProcessorTest en el proyecto de biblioteca de clases y configura pruebas unitarias utilizando XUnit. Demuestra cómo crear un método de prueba para generar ID de empleados y analiza la importancia de simular dependencias en lugar de confiar en valores reales.

Escribir un método de prueba
Tim escribe un método de prueba unitaria llamado GenerateEmployeeID_ShouldCalculate. Establece una teoría con datos en línea para probar diferentes escenarios, asegurándose de que el método devuelve los resultados esperados. También explica cómo utilizar Assert.Equal para verificar la salida.
public class EmployeeProcessorTest
{
[Theory]
[InlineData("Timothy", "Corey", "TimoCore")]
public void GenerateEmployeeID_ShouldCalculate(string firstName, string lastName, string expectedStart)
{
// Arrange
var processor = new EmployeeProcessor();
// Act
var actualStart = processor.GenerateEmployeeID(firstName, lastName).Substring(0, 8);
// Assert
Assert.Equal(expectedStart, actualStart);
}
}
public class EmployeeProcessorTest
{
[Theory]
[InlineData("Timothy", "Corey", "TimoCore")]
public void GenerateEmployeeID_ShouldCalculate(string firstName, string lastName, string expectedStart)
{
// Arrange
var processor = new EmployeeProcessor();
// Act
var actualStart = processor.GenerateEmployeeID(firstName, lastName).Substring(0, 8);
// Assert
Assert.Equal(expectedStart, actualStart);
}
}
Ejecución de la prueba unitaria
Tim hace hincapié en la importancia de simular datos dinámicos, como valores de fecha y hora, para controlar las condiciones y los resultados de las pruebas. Se analiza el reto de trabajar con cadenas dinámicas y cómo probar diferentes escenarios utilizando valores controlados. Luego él ejecuta la prueba unitaria, pero antes añade dos paquetes NuGet necesarios para ejecutar las pruebas: xunit.runner.console y xunit.runner.visualstudio.

Tras ejecutar correctamente todas las pruebas para un dato en línea, el resultado es el siguiente:

Ahora, en (31:30), Tim añadió otro dato en línea y cambió el segundo parámetro de la subcadena a expectedStart.Length:
public class EmployeeProcessorTest
{
[Theory]
[InlineData("Timothy", "Corey", "TimoCore")]
[InlineData("Tim", "Corey", "TimCore")]
public void GenerateEmployeeID_ShouldCalculate(string firstName, string lastName, string expectedStart)
{
var processor = new EmployeeProcessor();
var actualStart = processor.GenerateEmployeeID(firstName, lastName).Substring(0, expectedStart.Length);
Assert.Equal(expectedStart, actualStart);
}
}
public class EmployeeProcessorTest
{
[Theory]
[InlineData("Timothy", "Corey", "TimoCore")]
[InlineData("Tim", "Corey", "TimCore")]
public void GenerateEmployeeID_ShouldCalculate(string firstName, string lastName, string expectedStart)
{
var processor = new EmployeeProcessor();
var actualStart = processor.GenerateEmployeeID(firstName, lastName).Substring(0, expectedStart.Length);
Assert.Equal(expectedStart, actualStart);
}
}
Después de ejecutar la prueba unitaria de nuevo en (32:05), con la segunda teoría la prueba se rompió:

Mejorando el código con métodos privados
Para adherirse a DRY, Tim refactoriza aún más el código creando un método privado GetPartOfName en la clase actual EmployeeProcessor bajo DRYDemoLibrary. Este método se encarga de la extracción de partes de un nombre, lo que mejora la reutilización y legibilidad del código. Tim realizó los siguientes cambios:
public string GenerateEmployeeID(string firstName, string lastName)
{
string employeeID = $@"{GetPartOfName(firstName, 4)}{GetPartOfName(lastName, 4)}{DateTime.Now.Millisecond.ToString()}";
return employeeID;
}
private string GetPartOfName(string name, int numberOfCharacters)
{
string output = name;
if (name.Length > numberOfCharacters)
{
output = name.Substring(0, numberOfCharacters);
}
return output;
}
public string GenerateEmployeeID(string firstName, string lastName)
{
string employeeID = $@"{GetPartOfName(firstName, 4)}{GetPartOfName(lastName, 4)}{DateTime.Now.Millisecond.ToString()}";
return employeeID;
}
private string GetPartOfName(string name, int numberOfCharacters)
{
string output = name;
if (name.Length > numberOfCharacters)
{
output = name.Substring(0, numberOfCharacters);
}
return output;
}
Actualización de pruebas unitarias
Tim actualiza las pruebas unitarias para reflejar los cambios en el código, como la modificación de la longitud esperada de las subcadenas. Explica cómo la ejecución de estas pruebas ayuda a identificar rápidamente los problemas y a validar que el código cumple los nuevos requisitos. Tim añade nuevas teorías y luego ejecuta las pruebas unitarias para verificar si los resultados son los esperados:

Ampliación de la versatilidad con las bibliotecas .NET Standard
Creación de una biblioteca .NET Standard
Para aumentar la versatilidad de su biblioteca de clases, Tim Corey recomienda pasar de una biblioteca de clases .NET Framework a una biblioteca de clases .NET Standard. Este cambio permite que la biblioteca sea compatible con varias plataformas, incluidas:
- Plataformas Windows: WinForms, WPF y aplicaciones de consola
- Multiplataforma: .NET Core, Xamarin (para iOS y Android), Linux y macOS
Pasos para crear una biblioteca .NET Standard:
- Añadir nuevo proyecto: Haga clic con el botón derecho en su solución y elija añadir un nuevo proyecto.
-
Seleccione .NET Standard: en lugar de seleccionar una biblioteca de clases de .NET Framework, elija .NET Standard. Este tipo de biblioteca es compatible con una amplia gama de plataformas.

- Migración de código: Copie y pegue su código existente (por ejemplo, la clase EmployeeProcessor) en la nueva biblioteca .NET Standard. Este proceso puede implicar pequeños ajustes, pero la lógica central sigue siendo coherente.
La conversión a .NET Standard permite que la biblioteca sea accesible desde varias plataformas, lo que reduce la repetición de código en distintos tipos de aplicaciones y ahorra esfuerzo de desarrollo.
Evitar la repetición en el código y las pruebas
Reducir la repetición en el desarrollo
Tim Corey hace hincapié en que, al adoptar una biblioteca .NET Standard, se minimiza la repetición de código no sólo en la base de código, sino también en el proceso de desarrollo. En lugar de duplicar el código en diferentes proyectos de plataformas específicas, se centraliza en una única biblioteca que funciona en múltiples entornos.
Beneficios:
- Código base unificado: Una base de código para varias plataformas reduce el esfuerzo necesario para mantener y actualizar su código.
- Pruebas simplificadas: Con una biblioteca .NET Standard, puede escribir pruebas unitarias una vez y asegurarse de que se aplican a todas las plataformas compatibles.
Pruebas y depuración: Tim presenta las pruebas unitarias como una forma de reducir aún más el esfuerzo y la repetición. Las pruebas automatizadas verifican la corrección del código sin necesidad de probar manualmente cada iteración de la aplicación.
Consejos para aplicar DRY: saber cuándo parar
Tim Corey subraya que, aunque seguir el principio DRY (Don't Repeat Yourself) es crucial para escribir código mantenible, es importante saber cuándo y dónde aplicarlo. No todas las situaciones requieren el mismo enfoque, así que aquí tienes algunos consejos prácticos inspirados en las ideas de Tim:
-
Evitar el código en el código fuente y la interfaz de usuario: Tim desaconseja colocar la lógica directamente en los archivos de código fuente o en las interfaces de usuario. Por ejemplo, la lógica empresarial no debe incrustarse en un formulario o en un evento de clic de botón. En su lugar, mantenga dicha lógica en clases o bibliotecas separadas. Esta separación ayuda a mantener una arquitectura limpia y hace que su código sea más reutilizable en diferentes interfaces de usuario.
-
Aprovechar las bibliotecas .NET Standard: Al crear bibliotecas, Tim sugiere utilizar bibliotecas .NET Standard en lugar de bibliotecas .NET Framework siempre que sea posible. las bibliotecas .NET Standard son más versátiles, lo que permite utilizar el código en diferentes plataformas, como .NET Core, Xamarin y otras. Este enfoque reduce la duplicación de código y mejora su portabilidad.
-
Código específico de la plataforma: es posible que parte del código no quepa en una biblioteca .NET Standard debido a requisitos específicos de la plataforma, como la gestión de archivos o la gestión de la configuración. Tim recomienda crear dos bibliotecas en estos casos: una para el código .NET Standard y otra para el código específico de la plataforma. De este modo, se puede seguir reutilizando la lógica básica al tiempo que se da cabida a las necesidades específicas de cada plataforma.
-
Haga hincapié en las pruebas unitarias: Tim recomienda encarecidamente escribir pruebas unitarias para su código. Las pruebas unitarias ayudan a identificar errores en una fase temprana y garantizan que el código se comporta como se espera. Pueden acelerar significativamente el proceso de depuración, ya que se pueden verificar rápidamente los cambios sin necesidad de probar manualmente toda la aplicación.
- Considera el tamaño del proyecto: Para proyectos muy pequeños o experimentales, Tim reconoce que la sobrecarga de crear bibliotecas separadas y pruebas unitarias extensas podría no ser necesaria. Sin embargo, para las aplicaciones de producción, es aconsejable empezar con una arquitectura limpia y pruebas unitarias, ya que los proyectos pequeños suelen crecer y evolucionar con el tiempo.
Si sigues estos consejos, podrás aplicar el principio DRY de forma eficaz y equilibrar la necesidad de reutilizar y mantener el código con consideraciones prácticas.
Conclusión
Dominar el principio DRY mediante patrones de diseño es esencial para escribir código C# limpio y fácil de mantener. Como demuestra Tim Corey, la aplicación eficaz de DRY implica la creación de métodos reutilizables, el aprovechamiento de las bibliotecas de clases y la adopción de .NET Standard para una mayor compatibilidad. Si sabe cuándo y cómo aplicar estas prácticas, podrá mejorar significativamente la calidad y flexibilidad de su código.
Para más información, consulta el vídeo de Tim Corey sobre este tema aquí. Para estar al día de los últimos contenidos de Tim, visite su canal de YouTube.


