Patrones de diseño principales de C#
Los patrones de diseño son soluciones reutilizables a problemas comunes de desarrollo de software, que proporcionan plantillas para estructurar e implementar código orientado a objetos de una manera más eficiente y fácil de mantener. Ayudan a los desarrolladores a resolver problemas de creación, estructura y comunicación de objetos de forma flexible y escalable. Los patrones de diseño sirven como conceptos de mejores prácticas que guían a los desarrolladores a la hora de escribir mejor código. Uno de los principios fundamentales en el diseño de software es el Principio de Responsabilidad Única (SRP), que forma parte de los principios SOLID.
En su vídeo, "Patrones de diseño: Single Responsibility Principle Explained Practically in C# (The S in SOLID)", Tim Corey explora el Principio de Responsabilidad Única (SRP), destacando su importancia en el diseño de software y proporcionando ideas prácticas sobre cómo implementarlo de manera efectiva. Este artículo ofrece una visión general concisa de los puntos clave de su vídeo, haciendo hincapié en la importancia de la SRP en la creación de código limpio y mantenible.
Introducción a SRP
En el diseño de software, los principios SOLID son cruciales para crear código mantenible y escalable. Garantizan que el código sea fácil de entender, probar y modificar. Los cinco principios -Principio de Responsabilidad Única (SRP), Principio Abierto/Cerrado (OCP), Principio de Sustitución de Liskov (LSP), Principio de Segregación de Interfaces (ISP) y Principio de Inversión de Dependencias (DIP)- forman parte integral del diseño orientado a objetos y pueden aplicarse dentro de los patrones de diseño para hacer que las soluciones sean más robustas.
Mediante la aplicación de patrones de diseño en C#, los desarrolladores pueden resolver problemas comunes con mayor eficacia. Ya se trate de crear objetos, definir estructuras de árbol o garantizar la reutilización con instancias únicas, los patrones de diseño proporcionan soluciones predefinidas que mejoran la arquitectura del software. Patrones como Factory Method, Builder y Singleton proporcionan soluciones flexibles y reutilizables, mientras que los patrones estructurales y de comportamiento ayudan a gestionar la complejidad y mejorar la comunicación dentro de los sistemas. Al aprender y utilizar estos patrones, los desarrolladores pueden crear sistemas más fáciles de mantener y ampliar.
Tim analiza el concepto de SRP, haciendo hincapié en que es crucial que los desarrolladores se aseguren de que su código se adhiere a las mejores prácticas. SRP establece que una clase debe tener una sola responsabilidad o razón para cambiar. Este principio ayuda a mantener un código limpio, mantenible y escalable.
Descripción general del código demo
Tim crea una sencilla aplicación de consola en C# que solicita el nombre y los apellidos del usuario, los valida y genera un nombre de usuario. La implementación inicial viola SRP, proporcionando una excelente oportunidad para demostrar cómo refactorizar el código para adherirse a este principio.
Explicación de SRP
Tim explica SRP destacando las múltiples responsabilidades dentro de la clase inicial:
- Interacción con el usuario: Manejo de mensajes de bienvenida y avisos.
- Captura de datos: Captura del nombre y apellidos del usuario.
- Validación: Validación de los nombres de entrada.
- Generación de nombres de usuario: Generación de un nombre de usuario a partir de los nombres de entrada.
Cada una de estas responsabilidades representa una razón diferente para que la clase cambie, violando la SRP.
Refactorización para adherirse a SRP
Tim demuestra cómo refactorizar el código para seguir SRP extrayendo cada responsabilidad en su propia clase. Este enfoque garantiza que cada clase tenga un único motivo de cambio, lo que hace que el código sea más modular y fácil de mantener.
Ejemplo práctico
Tim ofrece un ejemplo práctico de refactorización:
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Welcome to my application");
Console.Write("Enter your first name: ");
string firstName = Console.ReadLine();
Console.Write("Enter your last name: ");
string lastName = Console.ReadLine();
if (string.IsNullOrWhiteSpace(firstName) || string.IsNullOrWhiteSpace(lastName))
{
Console.WriteLine("You did not give us valid information!");
Console.ReadLine();
return;
}
var userName = $"{firstName.Substring(0, 1)}{lastName}".ToLower();
Console.WriteLine($"Your username is {userName}");
Console.WriteLine("Press enter to close...");
Console.ReadLine();
}
}
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Welcome to my application");
Console.Write("Enter your first name: ");
string firstName = Console.ReadLine();
Console.Write("Enter your last name: ");
string lastName = Console.ReadLine();
if (string.IsNullOrWhiteSpace(firstName) || string.IsNullOrWhiteSpace(lastName))
{
Console.WriteLine("You did not give us valid information!");
Console.ReadLine();
return;
}
var userName = $"{firstName.Substring(0, 1)}{lastName}".ToLower();
Console.WriteLine($"Your username is {userName}");
Console.WriteLine("Press enter to close...");
Console.ReadLine();
}
}
Refactorización paso a paso
Paso 1: Creación de la clase StandardMessages
En primer lugar, Tim crea una clase para gestionar los mensajes estándar que se muestran al usuario. Esta clase gestionará los mensajes de bienvenida y los mensajes finales.
public class StandardMessages
{
public static void WelcomeMessage()
{
Console.WriteLine("Welcome to my application");
}
public static void EndApplication()
{
Console.WriteLine("Press enter to close...");
Console.ReadLine();
}
public static void ShowValidationErrorMessage()
{
Console.WriteLine("You did not give us valid information!");
}
}
public class StandardMessages
{
public static void WelcomeMessage()
{
Console.WriteLine("Welcome to my application");
}
public static void EndApplication()
{
Console.WriteLine("Press enter to close...");
Console.ReadLine();
}
public static void ShowValidationErrorMessage()
{
Console.WriteLine("You did not give us valid information!");
}
}
En la clase Program, sustituye las llamadas directas a Console.WriteLine y Console.ReadLine por llamadas a los métodos de la clase StandardMessages:
class Program
{
static void Main(string[] args)
{
StandardMessages.WelcomeMessage();
// Other code...
StandardMessages.EndApplication();
}
}
class Program
{
static void Main(string[] args)
{
StandardMessages.WelcomeMessage();
// Other code...
StandardMessages.EndApplication();
}
}
Paso 2: Creación de la clase PersonDataCapture
A continuación, Tim crea una clase para capturar el nombre y los apellidos de la persona. Esta clase será responsable de recoger la entrada del usuario y devolver un objeto Persona.
public class PersonDataCapture
{
public static Person Capture()
{
Person output = new Person();
Console.Write("Enter your first name: ");
output.FirstName = Console.ReadLine();
Console.Write("Enter your last name: ");
output.LastName = Console.ReadLine();
return output;
}
}
public class PersonDataCapture
{
public static Person Capture()
{
Person output = new Person();
Console.Write("Enter your first name: ");
output.FirstName = Console.ReadLine();
Console.Write("Enter your last name: ");
output.LastName = Console.ReadLine();
return output;
}
}
Paso 3: Creación de la clase Persona
También se necesita una clase Person para contener el nombre y los apellidos del usuario.
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
En la clase Program, sustituya el manejo directo de la entrada del usuario por una llamada a PersonDataCapture.Capture:
class Program
{
static void Main(string[] args)
{
StandardMessages.WelcomeMessage();
Person user = PersonDataCapture.Capture();
// Other code...
StandardMessages.EndApplication();
}
}
class Program
{
static void Main(string[] args)
{
StandardMessages.WelcomeMessage();
Person user = PersonDataCapture.Capture();
// Other code...
StandardMessages.EndApplication();
}
}
Paso 4: Creación de la clase PersonValidator
A continuación, Tim crea una clase para gestionar la validación del nombre y apellidos de la persona. Esta clase se encargará de garantizar que los nombres no contengan nulos ni espacios en blanco.
public class PersonValidator
{
public static bool Validate(Person person)
{
if (string.IsNullOrWhiteSpace(person.FirstName))
{
StandardMessages.ShowValidationErrorMessage("first name");
return false;
}
if (string.IsNullOrWhiteSpace(person.LastName))
{
StandardMessages.ShowValidationErrorMessage("last name");
return false;
}
return true;
}
}
public class PersonValidator
{
public static bool Validate(Person person)
{
if (string.IsNullOrWhiteSpace(person.FirstName))
{
StandardMessages.ShowValidationErrorMessage("first name");
return false;
}
if (string.IsNullOrWhiteSpace(person.LastName))
{
StandardMessages.ShowValidationErrorMessage("last name");
return false;
}
return true;
}
}
En la clase Program, sustituye el código de validación por una llamada a PersonValidator.Validate:
class Program
{
static void Main(string[] args)
{
StandardMessages.WelcomeMessage();
Person user = PersonDataCapture.Capture();
if (!PersonValidator.Validate(user))
{
StandardMessages.EndApplication();
return;
}
// Other code...
StandardMessages.EndApplication();
}
}
class Program
{
static void Main(string[] args)
{
StandardMessages.WelcomeMessage();
Person user = PersonDataCapture.Capture();
if (!PersonValidator.Validate(user))
{
StandardMessages.EndApplication();
return;
}
// Other code...
StandardMessages.EndApplication();
}
}
Paso 7: Creación de la clase AccountGenerator
Tim traslada la lógica de generación de nombres de usuario y creación de cuentas a una nueva clase AccountGenerator.
-
Creación de la clase AccountGenerator:
- La clase incluye la lógica para generar el nombre de usuario y simular la creación de la cuenta.
public class AccountGenerator { public static void CreateAccount(Person user) { string username = $"{user.FirstName.Substring(0, 1)}{user.LastName}".ToLower(); Console.WriteLine($"Your username is: {username}"); } }public class AccountGenerator { public static void CreateAccount(Person user) { string username = $"{user.FirstName.Substring(0, 1)}{user.LastName}".ToLower(); Console.WriteLine($"Your username is: {username}"); } } -
Actualización de la clase principal:
- Llame al AccountGenerator para crear la cuenta en la clase principal.
class Program
{
static void Main(string[] args)
{
StandardMessages.WelcomeMessage();
Person user = PersonDataCapture.Capture();
bool isUserValid = PersonValidator.Validate(user);
if (!isUserValid)
{
StandardMessages.EndApplication();
return;
}
AccountGenerator.CreateAccount(user);
StandardMessages.EndApplication();
}
}
class Program
{
static void Main(string[] args)
{
StandardMessages.WelcomeMessage();
Person user = PersonDataCapture.Capture();
bool isUserValid = PersonValidator.Validate(user);
if (!isUserValid)
{
StandardMessages.EndApplication();
return;
}
AccountGenerator.CreateAccount(user);
StandardMessages.EndApplication();
}
}

Resumen y conclusiones
En esta sección final, Tim Corey resume las ventajas y la aplicación del principio de responsabilidad única (SRP) a través del proceso de refactorización del código de demostración. Destaca las ventajas de dividir la aplicación en clases más pequeñas y centradas.
Beneficios clave de SRP
-
Mantenimiento simplificado del código:
-
Cada clase tiene una única responsabilidad, lo que facilita la localización de los cambios que deben realizarse. Por ejemplo, la lógica de captura de datos de usuario se sitúa claramente en PersonDataCapture.
- Esta estructura simplifica la comprensión, ya que cualquiera que desee modificar la validación de usuarios sabe que debe consultar PersonValidator.
-
-
Mejora de la legibilidad:
- Con responsabilidades claramente definidas, el flujo principal del programa resulta más legible. El código ahora se lee como un conjunto de acciones claras y secuenciales:
StandardMessages.WelcomeMessage(); Person user = PersonDataCapture.Capture(); bool isUserValid = PersonValidator.Validate(user); if (!isUserValid) { StandardMessages.EndApplication(); return; } AccountGenerator.CreateAccount(user); StandardMessages.EndApplication();StandardMessages.WelcomeMessage(); Person user = PersonDataCapture.Capture(); bool isUserValid = PersonValidator.Validate(user); if (!isUserValid) { StandardMessages.EndApplication(); return; } AccountGenerator.CreateAccount(user); StandardMessages.EndApplication(); -
Complejidad reducida:
-
Las clases pequeñas con responsabilidades específicas suelen tener menos líneas de código, lo que facilita su comprensión y mantenimiento.
- Ejemplo: Los métodos de la clase StandardMessages son concisos y sirven para un único propósito, como mostrar un mensaje de bienvenida o finalizar la aplicación.
public class StandardMessages { public static void WelcomeMessage() { Console.WriteLine("Welcome to my application"); } public static void EndApplication() { Console.WriteLine("Press enter to close..."); Console.ReadLine(); } public static void ShowValidationErrorMessage(string fieldName) { Console.WriteLine($"You did not give us a valid {fieldName}!"); } }public class StandardMessages { public static void WelcomeMessage() { Console.WriteLine("Welcome to my application"); } public static void EndApplication() { Console.WriteLine("Press enter to close..."); Console.ReadLine(); } public static void ShowValidationErrorMessage(string fieldName) { Console.WriteLine($"You did not give us a valid {fieldName}!"); } }
-
-
Facilidad de cambios de código:
-
Dado que cada clase tiene un único motivo de cambio, modificar el código en respuesta a nuevos requisitos resulta sencillo.
- Ejemplo: Si el requisito es cambiar el mensaje final, el cambio se produce únicamente en el método StandardMessages.EndApplication.
-
-
Mejor depuración y colaboración:
-
Con clases más pequeñas y bien definidas, la depuración resulta más sencilla, ya que se puede localizar fácilmente un problema.
- Los nuevos desarrolladores pueden incorporarse más rápidamente, entendiendo la estructura clara y las responsabilidades de cada clase.
-
Preocupaciones sobre muchas clases
Tim aborda la preocupación común de que la aplicación de SRP da lugar a demasiadas clases, lo que hace que el proyecto sea engorroso:
-
Navegación y comprensión:
-
Herramientas como IntelliSense en Visual Studio facilitan la navegación por varias clases. Por ejemplo, pulsando F12 se accede directamente a la definición de métodos o clases.
- Tener muchas piezas pequeñas y manejables puede facilitar la comprensión de toda la aplicación en comparación con las grandes clases monolíticas.
-
-
Rendimiento y almacenamiento:
- Las clases adicionales no afectan significativamente al espacio en disco ni al rendimiento, teniendo en cuenta las capacidades informáticas y de almacenamiento modernas.
-
Saldo y exceso :
-
Tim aconseja encontrar un equilibrio. Si la responsabilidad de una clase hace que crezca demasiado, considere si tiene múltiples razones para cambiar, lo que indica que puede necesitar una división adicional.
- Sugiere que si es necesario desplazarse mucho por una clase en Visual Studio, puede que sea demasiado grande y sea necesario dividirla.
-
Ejecución práctica
Tim anima a los desarrolladores a aplicar SRP gradualmente, especialmente en las bases de código existentes. Comience con pequeños cambios y código nuevo para alinearse con los principios de la SRP. Este enfoque gradual garantiza transiciones más fluidas y una mejora continua.
Conclusión
El ejemplo de refactorización de Tim Corey demuestra cómo la adhesión al Principio de Responsabilidad Única (SRP) da como resultado un código más limpio y fácil de mantener. Al dividir las responsabilidades en clases más pequeñas y específicas, los desarrolladores pueden mejorar la legibilidad, la depuración y la colaboración en sus bases de código. Este principio fundamental de los patrones de diseño SOLID allana el camino para principios más avanzados y mejores prácticas en el desarrollo de software.
Para obtener información más detallada y ejemplos de código, vea su vídeo y visite su canal para ver más vídeos sobre patrones de diseño.
