c# Object oriented design – ¿Cómo aplicas el principio Single Responsability

Si alguna vez te has preguntado ¿Qué es el Principio de Responsabilidad Única? y ¿Cómo lo puedo implementar? te recomiendo que leas este post.
Principio Responsabilidad Única
Principio Responsabilidad Única


Artículos relacionados:

Este término fue introducido por Robert C. Martin en su artículo The Principles of Object Oriented Design y luego lo popularizó en su libro Agile Principles, Patterns, and Practices in C#. El principio de responsabilidad única está basado en el de cohesión, que fue descrito en el libro Structured Analysis and Systems Specification. Este es el primero de los cinco principios SOLID.

Iniciemos con esta frase obtenida del libro Agile Principles, Patterns, and Practices in C#:

A Class should have only one reason to change.

Wikipedia nos dice lo siguiente:

…states that every class should have a single responsibility, and that responsibility should be entirely encapsulated by the class.

Este principio señala que cada clase debe tener una única responsabilidad. Una clase debe tener una, y solo una, razón de cambio. Esto no quiere decir que una clase contenga solo un método o una propiedad, pero tampoco que tengamos una clase que haga todo y cambie por distintos motivos. Recordemos que las clases grandes son difíciles de mantener y leer. Si piensas que hay más de un motivo para cambiar una clase entonces esa clase tiene más de una responsabilidad.

SRP - Responsabilidad unica
SRP – Responsabilidad única

Hagamos este ejercicio, agarremos una clase cualquiera y describamos todas las funcionalidades que realiza. Por ejemplo, si obtuviste como resultado algo así: “Esta clase graba en BD Y encripta Y envía correos Y lee archivos de texto.”, quiere decir que la clase tiene más de una responsabilidad,

Nota: Este ejercicio también aplica a métodos.

¿Sobre qué artefactos aplica este principio?
  • Métodos
  • Clases
  • Paquetes
  • Módulos
  • Sistemas
¿Qué beneficios trae el trabajar con este principio?
  • Organiza el código, un lugar para cada cosa y cada cosa en su lugar
  • Bajo acoplamiento.
  • Alta cohesión.
  • Clases especificas con nombres fáciles de entender.
  • Código fácil de mantener.
  • Código fácil de probar y depurar.
  • Código fácil de rehusar.
  • Código fácil de entender.
¿Cuándo debemos aplicar este principio?
  • Cuando una clase es muy larga (sugerencia si tiene más de 300 lineas de código).
  • Cuando un método es muy largo (sugerencia si tiene más de 40 lineas de código).
  • Cuando existen muchas dependencias a otros objetos (sugerencia si tiene más de 20 dependencias)
  • Cuando hay baja cohesión.
  • Cuando se usan nombres muy genéricos: Util, Manager, Process.
  • Cuando se hace uso del anti patrón Spagethi Code.
¿Cuándo no debemos aplicar este principio?
  • Cuando una clase no va a ser rehusada (Una clase controladora o un servicio web)
  • Cuando nadie depende de esta clase.
  • Cuando no contiene campos privados que almacenen valores que la clase use.
Argumentos en contra de este principio
  • Demasiadas clases
  • Complicado entender el panorama general del diseño

Nota: Tener más clases no significa necesariamente que el código sea más complicado, hay casos y casos.

Veamos un ejemplo:
Aplicando el principio en clases

Tenemos el siguiente caso: las clases ControllerSeguridad y ServicioWebSeguridad usan una clase llamada Util.

SRP - Clase Util
SRP – Clase Util

Como vemos la clase Util tiene 3 responsabilidades. La primera responsabilidad es enviar correos, la segunda es registrar mensajes  y la última es la criptografia.

Pregunta, ¿Estas funcionalidades deben estar separadas?

Respuesta: Depende si en el desarrollo de la aplicación se necesita agregar o cambiar las funcionalidades que existen en la clase Util.

Escenario 1:

La aplicación requiere constantemente agregar o cambiar las responsabilidades que se encuentran en la clase Util, por lo tanto existe la necesidad de separarlas en distintas clases. Entonces, si seguimos trabajando de esta manera generaríamos que el diseño huela a los design smells de Rigidez y Fragilidad. Rigidez: Hacer un cambio sobre la clase Util se vuelve complicado, porque se tendrá que volver a compilar y desplegar los componentes, aunque no usen la funcionalidad afectada. Fragilidad: Al momento de hacer un cambio sobre una responsabilidad, accidentalmente se puede afectar la funcionalidad de otro y puede ocasionar que el código se rompa en distintas partes. En este caso, una propuesta para resolver este escenario es diseñar las clases de la siguiente forma:

SRP - Clases refactorizadas
SRP – Clases refactorizadas

Revisando el diseño final vemos que si cambiamos la clase ServicioNotificacion el único afectado será la clase ServicioWebSeguridad. Además, si se hace un cambio sobre la clase ServicioRegistro este afatara a las clases ControllerSeguridad ServicioWebSeguridad. Ahora el cambio de una responsabilidad no afectara a las demás.

Escenario 2:

En el desarrollo de la aplicación no se requiere agregar ni cambiar las funcionalidades que existen en la clase Util, por lo tanto no existe la necesidad de separarlas en clases distintas. En cambio si separamos estas funcionalidades en distintas clases, sin ningún motivo, generaríamos que el diseño huela al design smell de Complejidad Innecesaria.

Aplicando el principio en métodos

Primero, aplicando el anti patrón Spagethi Code:

public void CaducarPoliza(Poliza poliza)
{ 
	var servicioPoliza = new ServicioWebPolizaExterno();
    RequestOpera105874 requestCaducarPoliza;
	//Código...
	servicioPoliza.Opera105874(requestCaducarPoliza);
	 
	var servicioNotificacion = new ServicioNotificacion();
    Mensaje mensajeResponsable;
	//Código...
	servicioNotificacion.EnviarNotificacion(mensajeResponsable);

	var servicioTraza = new ServicioTraza();
    Traza trazaPolizaCaducada;
	//Código...
	servicioTraza.Registrar(trazaPolizaCaducada);
}

Ahora aplicando el principio de Responsabilidad Única:

public void CaducarPoliza(Poliza poliza)
{
	CaducarPolizaServicioExterno(poliza);
	EnviarNotificacionResponsable(poliza);
	RegistrarLogPoliza(poliza);
}

private void CaducarPolizaServicioExterno(Poliza poliza)
{
	var servicioPoliza = new ServicioWebPolizaExterno();
    RequestOpera105874 requestCaducarPoliza;
	//Código...
	servicioPoliza.Opera105874(requestCaducarPoliza);
}

private void EnviarNotificacionResponsable(Poliza poliza)
{
	var servicioNotificacion = new ServicioNotificacion();
    Mensaje mensajeResponsable;
	//Código...
	servicioNotificacion.EnviarNotificacion(mensajeResponsable);
}

private void RegistrarLogPoliza(Poliza poliza)
{
	var servicioTraza = new ServicioTraza();
    Traza trazaPolizaCaducada;
	//Código...
	servicioTraza.Registrar(trazaPolizaCaducada);
}
Conclusión

Una clase solo debe tener una razón de cambio, si tiene más de una se debe evaluar su diseño. Este principio se aplica a: métodos, clases, paquetes, módulos y sistemas. Como principales beneficios tenemos que nos permite crear código acoplado y cohesivo. Se debe aplicar si se encuentran clases con nombres genéricos o en las cuales se haya implementado el anti patrón Spagethi Code. También tiene argumentos en contra que se generan muchas clases o que es difícil entender el diseño completo, pero tener más clases no significa necesariamente que el código es más complicado.

Referencias:
Metal Tip:

Este artículo lo escribí escuchando la canción Escudo y Espada de la banda Kraken de Colombia, les comparto el enlace

Happy coding and Stay Heavy lml

Anuncios

9 comentarios en “c# Object oriented design – ¿Cómo aplicas el principio Single Responsability

  1. Un elemento a considerar durante el diseño y desarrollo de aplicaciones, sin duda un tema que se tiene que analizar a fondo. En lo particular soy de los que tienen integrados en un solo procedimiento a todos los elementos necesarios para ejecutar una función, con el objeto de trabajar a la función en un solo lugar y aprovechar las definiciones que puedan ser utilizadas entre las diferentes secciones del procedimiento, Simplemente no me veo definiendo 10 procedimientos diferentes cuando los tengo integrados en uno solo.

    Me gusta

    1. Hola,
      Para saber si nuestro diseño es el educado (siguiendo o no un principio) debemos usar herramientas de análisis de código. Estas herramientas nos dirán el nivel de acoplamiento y cohesión que tiene cada clase; además, de la cantidad de líneas de código repetido que existe. Toda esta información la podemos usar para tomar decisiones en el diseño y determinar si nos conviene usar uno o muchos métodos :).
      Saludos

      Me gusta

  2. Interesante articulo. Este principio SOLID es relativo al escenario, cual seria el criterio para lograr un código extendible sin que el diseño huela al design smell de Complejidad Innecesaria.

    Me gusta

    1. Hola Alex, Como mencione anteriormente, si hablamos de una clase que no va a ser reutilizada (controladora, servicio web, etc.) no es necesario aplicar este principio. Lo recomendado es aplicarlo en clases que se llaman varias veces. Una forma de identificar cuando no cumplimos con este principio es cuando vemos lo siguiente:
      • Una clase con más de 300 líneas de código,
      • Métodos con más de 30 líneas de código.
      Otra forma es al tratar de describir lo que hace una clase, si obtenemos algo como: “Guarda log Y Envía correos Y guarda en bd Y encripta Y…“ vemos que hace muchas cosas; además, su cohesión es baja. Personalmente, creo que este principio es el que menos puede llevar a la sobre ingeniería, si en tu diseño justificas el uso de una clase no puedes caer en esto.
      Saludos

      Me gusta

  3. Aún tengo dudas.
    Si tengo un método que guarda una entidad en una base de datos y tiene un try catch donde guardo un log en caso de una excepción. Teniendo en cuenta este principio ¿Es correcto?.

    Me gusta

  4. Hola Maías, una solución puede ser de la siguiente forma:

    public class RepositorioPersona
    {
    private readonly ILog log;

    public void Grabar(Persona persona)
    {
    try
    {
    EjecutarGrabar(persona);
    }
    catch(Exception ex)
    {
    log.RegistrarError(ex);
    }
    }

    private void EjecutarGrabar(Persona persona)
    {
    //Mapeo de campos
    _dbContext.Persona.Add(persona);
    }
    }

    Me gusta

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s