A veces, cuando estamos aprendiendo a programar, algo que nos cuesta entender un poco es el concepto de responsabilidad de clase. Es por eso que nuestros primeros proyectos se vuelven inmantenibles, con clases de muchas lineas de codigo y, mucho mas critico, infinita cantidad de responsabilidades.

Una buena forma de saber realmente cual es la responsabilidad de cada clase es pensar en la escabilidad.

Vamos a ver un ejemplo de como pensar de esta forma. Pero antes, quiero introducir un poco de teoria y explicar porque es importante este concepto en el diseño de software.

Introduccion a Single Responsibility Principle

La idea de que cada clase tenga una unica responsabilidad en un proyecto de software, y esa responsabilidad a su vez sea encapsulada en esa unica clase tiene un nombre: Single Responsibility Principle

Este es uno de los 5 principios fundamentales de diseño de software SOLID, donde, en programacion orientada a objetos, buscan sentar una base para obtener un software lo mas entendible, flexible y mantenible. Estos principios son:

  • Single responsibility principle
  • Open/closed principle
  • Liskov substitution principle
  • Interface segregation principle
  • Dependency inversion principle

El autor de estos principios, Robert C. Martin (autor de uno de los libros mas importantes de arquitectura de software, Clean Code) define el principio de SRP como ” Una clase deberia tener solo una razon para cambiar”, por lo que concluye que una responsabilidad es una razon para cambiar.

Por ejemplo, consideremos un modulo que procesa e imprime un reporte. Imaginemos, entonces, que este modulo podria ser modificado por dos razones. Primero, el contenido del reporte (los datos) podria cambiar. Segundo, el formato del reporte (la presentacion) tambien podria hacerlo. Estas dos razones son, por diferentes causas, una sustancial y la otra cosmetica.

El principio define que estos dos aspectos son en realidad dos responsabilidades separadas, y deberian estar en diferentes clases o modulos. Seria un mal diseño acoplar dos cosas que pueden cambiar por diferentes razones en diferentes tiempos.

La razon por la cual es importante mantener una clase en una simple responsabilidad es lo que hace a la clase mas robusta. Continuando con el ejemplo anterior, si hay un cambio al proceso de generacion de reporte, hay un gran riesgo de que el codigo de impresion del reporte se altere si esta en la misma clase.

Por que es importante definir correctamente la responsabildad por clase

Si definimos nuestras clases sabiendo cual es la responsabilidad cumple en nuestro projecto podemos:

  • Entender mas facilmente que funcionalidad realiza cada parte del codigo.
  • Modificar logica existente mucho mas rapido y prolijo.
  • Encontrar con menos problemas el origen de bugs o comportamientos no deseados.
  • Abstraer logica en diferentes clases o modulos.
  • Separar sin mayores problemas implementaciones de modo que puedan ser completamente reemplazadas posteriormente.
  • Definir tests unitarios por clase o modulo mas eficientemente, pudiendo testear una pequeña parte del codigo y no mas de lo que realmente queramos testear.

Pensando en la escabilidad para definir responsabilidades

Como se dijo anteriormente, se puede pensar en la escabilidad de una clase para definir responsabilidades. Esto es, simplemente, pensar si en nuestro proyecto en algun momento se modifican los requerimientos, mirar en nuestra arquitectura como se aplicarian esas modificaciones.

Si vemos que para un simple cambio de vista tenemos que modificar o reacomodar logica de negocio, no estamos definiendo correctamente las responsabilidades en nuestro proyecto, por ejemplo.

Vamos a ver un ejemplo concreto en Swift.

Ejemplo en Swift

Supongamos que tengo una aplicacion que muestra una lista de items de supermercado. Por ahora solo tengo un ItemsViewController que se encarga de toda la logica de ese flujo, tanto la obtencion de los items, como de la presentacion de los mismos. Ademas, se imprime un log cuando se selecciona un item.

Pueden ver el codigo del proyecto en https://github.com/fedejordan/SRPExample, branch master.

Para ello, ItemsViewController utiliza un UITableView para mostrar los items, en forma de lista. Tambien se utiliza una subclase de UITableViewCell llamada ItemTableViewCell para mostrar cada elemento.

El problema radica en si queremos cambiar la vista, por ejemplo, por una UICollectionView. ¿Cual es el problema en este caso?

El c odigo de la vista de la lista esta muy ligado a la logica de obtencion de los items. Es muy probable que modifiquemos la clase que se encarga de devolver los objetos de tipo Item tambien.

En especifico, el problema esta en estas lineas:

let item = items[indexPath.row]

¿Por que el problema esta aca?

Porque estamos usando el indice del UITableView para acceder al item especifico en el array. Deberiamos abstraernos de alguna forma de que la vista es un UITableView.

Para evitar ello, refactorizamos el ItemsViewController y movemos la logica de obtencion de datos a otra clase llamada ItemsInteractor.

El término Interactor tiene su origen en la arquitectura VIPER. Cómo lo dice en su definicion, un Interactor contiene la logica de negocio para manipular modelos de objetos (Entidades) para realizar una tarea en especifico. En este caso, nuestro ItemsInteractor se encarga de devolver informaci on acerca de algun o algunos objetos de tipo Item.

Esta version del codigo la pueden obtener en https://github.com/fedejordan/SRPExample haciendo un check out al branch interactor_refactor.

Como podemos ver, el ItemsViewController no conoce nada acerca del modelo de datos. Simplemente pide al Interactor lo que necesita para poder renderizar la pantalla. En un futuro podriamos cambiar el tipo de dato Item por otra cosa, que solo hariamos cambios en el ItemsInteractor, este devolveria la misma informacion y finalmente ItemsViewController puede seguir funcionando como siempre.

Entonces, de esta forma, si queremos cambiar solo el layout de nuestra aplicacion, simplemente cambiamos ItemsViewController para que use un UICollectionView:

Screenshot de ItemsViewController usando un UICollectionView

Pueden ver el resultado final en https://github.com/fedejordan/SRPExample en el branch collection_view_refactor.

¿Cambiamos algo en el ItemsInteractor? Para nada. Simplemente cambiamos la presentacion de los mismos.

Esto a su vez, nos permite testear de una forma mas modularizada. Podemos empezar a testear la vista primero y despues la logica de negocio. Claro que, para hacer tests correctamente tenemos que hacer algo mas injectable, cosa que podamos inicializar los modulos con clases mockeadas y dependamos de interfaces o protocols en Swift. Basicamente esto se traduce en, por ejemplo, no instanciar el ItemsInteractor en ItemsViewController, ya que es una dependencia y debe abstraerse de su implementacion. Pero ello ya excede del proposito del articulo.

Conclusion

A simple vista parece que no hicimos mucho trabajo. Simplemente separamos la logica de la vista de la de obtencion de datos. ¿Nos sirvio realmente? En este ejemplo puede que no sea muy util hacerlo, es mas, puede que simplemente se necesite esa lista y nada mas.

Pero cuando tenemos UIs mucho mas complicadas, adaptar la misma vista a diferentes data sources, o compartir el data source entre diferentes flujos, aplicar este concepto nos permite reutilizar el codigo y escalar ante cambios que haya tanto en la vista como en la logica de nuestro proyecto. Y para poder lograr llegar a eso, es importante entender que responsabilidad debe tener cada clase que lo conforma.

Me gustaria agregar que, en mi opinion, este concepto es condicion necesaria para tener un buen codigo. De hecho, en cualquier codigo que estuviese mal escrito, siempre se va a poder encontrar alguna clase con mas de una responsabilidad. Es por eso que vimos la importancia del concepto de Single Responsibility Principle.

Nuestro ejemplo en Swift nos permitio ver por que es tan importante definir correctamente las responsabilidades. Como una correcta arquitectura modularizada puede incidir significativamente en la escabilidad de nuestro proyecto, simplemente por querer hacer un cambio de layout en una pantalla.

A modo de resumen me gustaria concluir que, mirando siempre en la escabilidad de nuestro proyecto, podemos ver si estamos definiendo correctamente las responsabilidades de nuestras clases o m odulos.

¡Muchas gracias por leer el post!

Espero que les haya gustado. Cualquier agradecimiento o sugerencia de los temas que trate pueden hacerlo en los comentarios, o tambien comunicandose a mi mail: fedejordan99@gmail.com

Este artículo tambien esta disponible para ver en Medium