DEV Community

Victor Manuel Pinzon
Victor Manuel Pinzon

Posted on • Updated on

SOLID: Principio de Responsabilidad Única

La calidad de código es una pieza angular en el desarrollo de software, sin embargo, es una de las más descuidadas. Hoy en día, muchos desarrolladores se enfocan en el aprendizaje de nuevos lenguajes de programación y dejan a un lado las buenas practicas en el diseño de sus aplicaciones. Esto causa que sus aplicaciones, aunque funcionales, no sean lo suficientemente robustas para ser escalables y mantenibes a largo plazo. Los principios SOLID son los cimientos de la calidad de código en la programación orientada a objetos. Dichos principios dictan las reglas básicas para que podamos diseñar aplicaciones mantenibles, escalables y robustas.

Lamentablemente, es común toparnos con desarrolladores, que con varios años de experiencia en el desarrollo de aplicaciones, desconocen estos principios. Por supuesto, yo no fui la excepción. Mi pasión por el desarrollo de software inició en el año 2004, desarrollando programas básicos en Pascal, que sumaban números y otros que los restaban. En el transcurso de los años aprendí varios lenguajes, entre ellos: C, C++, Visual Basic, Visual Fox Pro, etc. En el año 2008 me tope con Java y su paradigma de programación orientada a objetos. Este paradigma permite abstraer los objetos de la vida real a entidades de código con atributos y comportamientos específicos. En el año 2014, 6 años después de iniciar mi carrera profesional como desarrollador, me topé con el libro Clean Code de Robert C. Martin y sus famosos principios SOLID.

Transcurrieron 6 años de desarrollo profesional en donde diseñe aplicaciones sin saber que existían los principios SOLID. Para ser sincero, desde que aprendí a aplicarlos ha mejorado por mucho la calidad de mi código. Ha permitido que mis aplicaciones sean sencillas de mantener y robustas en su implementación.

Creyendo que nunca es tarde para aprender, revisaremos cada uno de los principios SOLID y su aplicación.

Principios SOLID

Los principios SOLID son 5 principios básicos de la programación orientada a objetos. Estos fueron desarrollados por Robert C. Martin (Uncle Bob) en el año 2000. Los principios SOLID tienen como objetivo eliminar las malas practicas en el diseño y desarrollo de software. La aplicación de estos principios ayudan al desarrollador a escribir un código mantenible, escalable y robusto.

Los principios SOLID son:

  • Single Responsibility Principle (Principio de responsabilidad única).
  • Open/Closed Principle (Principio de abierto/cerrado). Liskov Substitution Principle (Principio de sustitución de Liskov).
  • Interface Segregation Principle (Principio de segregación de interfaces).
  • Dependency Inversion Principle (Principio de inversión de dependencia).

El termino SOLID es un acrónimo de los 5 principios.

Principio de Responsabilidad Única

SRP o Principio de responsabilidad única se refiere a que una clase, método o modulo debería de tener solamente una responsabilidad, es decir, solamente una razón para cambiar. En ocasiones sucede que codificamos clases/métodos/módulos que hacen muchas cosas a la vez, por ejemplo, se encargan de la lógica de negocio, de la persistencia de datos, del registro en el log, etc. El principio de responsabilidad única nos ayuda a refactorizar este código y separar la responsabilidades de clases/métodos/módulos para que tengan solo una responsabilidad.

La desventaja de que una unidad de código tenga más de una responsabilidad, es que cuando se introduce un cambio para alguna de las responsabilidades se puede afectar el funcionamiento de las otras. El objetivo de SRP es la separación de responsabilidades y evitar que otras responsabilidades se vean afectadas por cambios ajenas a ellas.

Vamos a ver el siguiente ejemplo. A continuación se presenta la clase Cliente.

package com.yourregulardeveloper.entidades;

import java.math.BigDecimal;
import java.util.List;

public class Cliente {
    private String idEmpleado;
    private String nombre;
    private String apellido;

    public Cliente(String idEmpleado, String nombre, String apellido) {
        this.idEmpleado = idEmpleado;
        this.nombre = nombre;
        this.apellido = apellido;
    }

    /**
     * Método encargado de calcular el subtotal de la factura.
     * @param articulos         Listado de articulos que el cliente desea comprar.
     * @return                  Subtotal de la factura
     */
    public BigDecimal calcularSubtotalFactura(List<Articulo> articulos){
        return articulos
                .stream()
                .map(articulo -> articulo.getPrecioUnitario()
                        .multiply(new BigDecimal(articulo.getCantidad())))
                .reduce(BigDecimal.ZERO, BigDecimal::add);

    }

    /**
     * Calcula el total de factura en base al impuesto
     * @param subtotal                  Subtotal de factura
     * @param porcentajeImpuesto        Porcentaje del impuesto
     * @return                          Gran total de la factura
     */
    public BigDecimal calcularTotalFactura(BigDecimal subtotal, BigDecimal porcentajeImpuesto){
        return subtotal.add(subtotal.multiply(porcentajeImpuesto));
    }

    public String getIdEmpleado() {
        return idEmpleado;
    }

    public void setIdEmpleado(String idEmpleado) {
        this.idEmpleado = idEmpleado;
    }

    public String getNombre() {
        return nombre;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    public String getApellido() {
        return apellido;
    }

    public void setApellido(String apellido) {
        this.apellido = apellido;
    }
}
Enter fullscreen mode Exit fullscreen mode

Tras un breve análisis de la clase, nos podemos dar cuenta que la entidad “Cliente” representa una violación al principio de responsabilidad única. Debido a que la clase tiene dos responsabilidades, las cuales son:

  • El manejo de la entidad cliente en el transcurso de la aplicación. Esta responsabilidad es dada por los atributos y los métodos relacionados a la información del cliente.
  • Calculador de totales de factura. Esta responsabilidad es dada por los métodos de calcularSubtotalFactura() y calcularTotalFactura().

Claramente la responsabilidad principal de la clase es el manejo de la entidad tipo Cliente. Un mal diseño de la clase permitió que introdujéramos la segunda responsabilidad de calculador de totales. Para solucionar este mal diseño debemos de separar las dos responsabilidades en dos clases diferentes.

package com.yourregulardeveloper.srp;

/**
 *
 * @author desarrollo
 */
public class Cliente {
    private String idEmpleado;
    private String nombre;
    private String apellido;

    public Cliente(String idEmpleado, String nombre, String apellido) {
        this.idEmpleado = idEmpleado;
        this.nombre = nombre;
        this.apellido = apellido;
    }

    public String getIdEmpleado() {
        return idEmpleado;
    }

    public void setIdEmpleado(String idEmpleado) {
        this.idEmpleado = idEmpleado;
    }

    public String getNombre() {
        return nombre;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    public String getApellido() {
        return apellido;
    }

    public void setApellido(String apellido) {
        this.apellido = apellido;
    }
}
Enter fullscreen mode Exit fullscreen mode
package com.yourregulardeveloper.srp;

import java.math.BigDecimal;
import java.util.List;

/**
 *
 * @author desarrollo
 */
public class Facturador {

    /**
     * Método encargado de calcular el subtotal de la factura.
     * @param articulos         Listado de articulos que el cliente desea comprar.
     * @return                  Subtotal de la factura
     */
    public BigDecimal calcularSubtotalFactura(List<Articulo> articulos){
        return articulos
                .stream()
                .map(articulo -> articulo.getPrecioUnitario()
                        .multiply(new BigDecimal(articulo.getCantidad())))
                .reduce(BigDecimal.ZERO, BigDecimal::add);

    }

    /**
     * Calcula el total de factura en base al impuesto
     * @param subtotal                  Subtotal de factura
     * @param porcentajeImpuesto        Porcentaje del impuesto
     * @return                          Gran total de la factura
     */
    public BigDecimal calcularTotalFactura(BigDecimal subtotal, BigDecimal porcentajeImpuesto){
        return subtotal.add(subtotal.multiply(porcentajeImpuesto));
    }
}
Enter fullscreen mode Exit fullscreen mode

La separación de las clases de Cliente y Facturador permite que cada clase tenga solo una responsabilidad o una sola razón para cambiar. La separación de responsabilidades permite que las dos clases sean mantenibles, escalables y fáciles de entender.

Principio de Responsabilidad Única a Nivel de Método

Es importante hacer énfasis que el principio de responsabilidad única se refiere a una unidad de código, el cual puede ser a nivel de modulo, clase o método. Hago énfasis en este punto porque es muy común ver métodos que realizan varias acciones, incluso he visto métodos de 500 líneas de código. Por ejemplo, he visto métodos que se encargan de registrar los parámetros en el log, realizar los cálculos correspondientes a la función y, por último, guardar el resultado en la base de datos. El problema de estos métodos es que, además de ser poco escalables y difíciles de mantener, no se pueden probar correctamente mediante pruebas unitarias. La aplicación del principio de responsabilidad única a nivel de método permite que podamos escribir métodos pequeños, con una sola responsabilidad, escalables, mantenibles y fáciles probar mediante pruebas unitarias.

Malas Practicas en la Aplicación del Principio de Responsabilidad Única

En muchas ocasiones he visto el principio de responsabilidad única incorrectamente aplicado. Esto sucede cuando se realiza una separación exagerada de las responsabilidades de las clases. Por ejemplo, cuando el principio está incorrectamente aplicado es común ver clases con un solo método, lo cual definitivamente es incorrecto. Debemos de recordar que el principio habla acerca de que una unidad de código debe de cambiar por solo una razón, no habla nada acerca de la cantidad de métodos y atributos que debe de tener dicha clase.

Es importante tambien recordar que se deben de agrupar todos los métodos que tienen una misma razón para cambiar en una sola clase. Por ejemplo, la clase Facturador agrupa dos métodos, calcularSubtotalFactura() y calcularTotalFactura(), en una sola clase. Debemos de recordar que el principio de responsabilidad única habla acerca de la separación de responsabilidad y la agrupación de unidades de código que tienen la misma responsabilidad, no acerca de la cantidad de métodos que debe de tener una clase o la longitud de un método.

El principio de responsabilidad única es clave en el diseño de código escalable y mantenible. Permite eliminar aquellas clases que hacen mil cosas y eliminar los métodos que realizan como cien acciones en una sola unidad de código. La separación de responsabilidades permite que nuestro código sea fácil de mantener en el futuro y que pueda ser escalable por otros desarrolladores. Recordemos, escribir código complicado de entender, no te hace un mejor desarrollador, lo contrario, te convierte en un desarrollador que escribe código espagueti y poco robusto.

Si desean ampliar su conocimiento acerca del principio de responsabilidad única, les recomiendo leer el blog de Robert C. Martin.

En una siguiente publicación hablaremos acerca del principio abierto-cerrado.

Top comments (0)