viernes, 1 de agosto de 2014

El Principio de Responsabilidad Única (The Single Responsibility Principle)


El Principio de Responsabilidad Única (The Single Responsibility Principle)

Bueno Team, el día de hoy revisaremos uno de los principios de SOLID, el cual es el principio de responsabilidad única, bueno para comenzar este termino o mas bien principio fue introducido en los 90 por Robert C. Martin o más conocido por el Tio Bob y lo define de la siguiente manera “El Principio de responsabilidad única establece que cada modulo de software debe tener una y sólo una razón para cambiar”, esto de entrada suena bien y con mucha lógica, pero primero hay que establecer que el Tio Bob cuando dice modulo de software incluye una amplia gama de opciones donde aplicar dicho principio ya que esto aplica para diseño, clases, funciones, variables, etc...

Con esto en mente por el momento podemos pasar a lo siguiente a que se refiere con una y sólo una razón para cambiar, esta idea lo que quiere decirnos es que cada modulo de software debe tener una responsabilidad bien definida y encapsulada, este concepto esta muy relacionado a un principio de diseño de separación de responsabilidades que habla de modularidad y encapsulación, la realidad es que esta idea le vemos día a día he incluso la implementamos, en nuestro trabajo las responsabilidades están bien definidas, las responsabilidades del su jefe y ustedes como parte de su equipo de trabajo, de entrada esta idea suena bien ya que si la aplicamos para muchas cosas en nuestra vida puede aplicar para diseñar software, una ves aceptada la idea de repartir responsabilidades bien definidas llega la pregunta, ¿que define una razón para cambiar?, algunos pensaran puede ser la resolución de bug encontrado en alguno de nuestros módulos o aplicar un refact a nuestro modulo, aquí lo que nos comenta el Tio Bob que estos dos temas, tanto resolución de bugs y refact no cuentan como razones para cambiar un modulo, ya que la aplicación de estos dos son mas responsabilidad del desarrollador mismo.

Dado que soy un fan del Fútbol pondré unos ejemplos esperando que esto ayude un poco a comprender este principio, primero la idea separación de responsabilidades, por un lado tenemos al dueño del equipo que tiene a grandes rasgos la parte administrativa de dicho equipo esa es su responsabilidad, por otro lado esta el director técnico del equipo y su responsabilidad es clara dirigir al equipo de fútbol y pues también están los jugadores y su responsabilidad de jugar al fútbol, veamos otra idea del este principio que es que un modulo sea capaz de realizar otras responsabilidades no quiere decir que deba hacerlo, esto lo podemos ver en el equipo de fútbol, que aun que el director técnico sea un ex-jugador de fútbol no quiere decir que deba entrar a jugar durante un partido aun que pueda y sepa hacerlo su responsabilidad esta clara dirigir al equipo de fútbol, de igual forma aun que el dueño sepa de fútbol no quiere decir que deba dirigir al equipo para eso esta el director técnico, y así podríamos seguir definiendo cada responsabilidad de cada modulo en el equipo de fútbol sin importar en que nivel se encuentra ya sea diseño, clases, funciones, variables, etc... como definimos previamente.

 Bueno ahora la pregunta ¿que define una razón para cambiar? Bueno esto va enfocado a la responsabilidad que tiene un modulo para atender las necesidades de un actor( persona, grupos de personas, módulos, etc...) , veamos lo desde el ejemplo del equipo el dueño del equipo tiene la necesidad de tener un equipo ofensivo por lo cual requiere un director técnico que cumpla con estas características, la única razón de cambio para el modulo de director técnico seria que el dueño ya no quisiera un director técnico ofensivo si no mas bien defensivo, esta cambiando la necesidad del dueño, dado que si cambia un director técnico ofensivo por otros de las misma características lo veríamos mas como corrección de un bug o refactor dado que el director técnico no estaba funcionando como lo esperado, lo mismo lo podemos ver a nivel del director técnico y los jugadores cuando cambia la estrategia o parado del equipo se puede tomar como una razón de cambio, pero cuando cambia a un jugador sin alterar la estrategia se podría ver como una resolución de un bug o un refact. Espero que este ejemplo le haya servido para entender la idea de el principio de responsabilidad única, pero dejemos un poco la teoría y veamos un ejemplo mas practico y enfocado al software que es lo que nos gusta e interesa.


Con mi equipo de trabajo hemos estado realizando Katas que la idea básica es ponernos ejercicios diarios que nos ayuden a mejorar y perfeccionar nuestras habilidades al momento de programar, muy parecido a una kata dentro de las artes marciales, bueno una de estas Katas era sobre un programa que se encargaba de actualizar el inventario de quesos de una empresa llamada GildedRose la cual contaba con varios tipos de quesos y dependiendo el tipo se actualizaba de una forma u otra, las características que se actualizaban ere la calidad del queso y su fecha limite de venta, la idea de esta kata era partir de un sistema ya echo y que estaba englobado todo en una sola clase y la idea era utilizar los principios de SOLID y rehacer este sistema sin alterar el funcionamiento actual de dicho sistema y a continuación les mostrare como aplique el principio de responsabilidad única, donde lo separe en 3 actores el sistema, los tipos de quesos y las reglas de negocio.


Código al Inicio de la Kata:
package gildedrose;

class GildedRose {

    Item[] items;

    public GildedRose(final Item[] items) {
        this.items = items;
    }

    public void updateQuality() {
        for (int i = 0; i < items.length; i++) {
            if (!items[i].name.equals("Aged Brie")
                    && !items[i].name.equals("Backstage passes to a TAFKAL80ETC concert")) {
                if (items[i].quality > 0) {
                    if (!items[i].name.equals("Sulfuras, Hand of Ragnaros")) {
                        items[i].quality = items[i].quality - 1;
                    }
                }
            } else {
                if (items[i].quality < 50) {
                    items[i].quality = items[i].quality + 1;

                    if (items[i].name.equals("Backstage passes to a TAFKAL80ETC concert")) {
                        if (items[i].sellIn < 11) {
                            if (items[i].quality < 50) {
                                items[i].quality = items[i].quality + 1;
                            }
                        }

                        if (items[i].sellIn < 6) {
                            if (items[i].quality < 50) {
                                items[i].quality = items[i].quality + 1;
                            }
                        }
                    }
                }
            }

            if (!items[i].name.equals("Sulfuras, Hand of Ragnaros")) {
                items[i].sellIn = items[i].sellIn - 1;
            }

            if (items[i].sellIn < 0) {
                if (!items[i].name.equals("Aged Brie")) {
                    if (!items[i].name.equals("Backstage passes to a TAFKAL80ETC concert")) {
                        if (items[i].quality > 0) {
                            if (!items[i].name.equals("Sulfuras, Hand of Ragnaros")) {
                                items[i].quality = items[i].quality - 1;
                            }
                        }
                    } else {
                        items[i].quality = items[i].quality - items[i].quality;
                    }
                } else {
                    if (items[i].quality < 50) {
                        items[i].quality = items[i].quality + 1;
                    }
                }
            }
        }
    }
}
Veamos el Sistema ya con los cambios:
package gildedrose;


public class GildedRoseSystem {
    
    private ItemUpgradeInstanceable factory;

    public GildedRoseSystem() {
        this(GildedRoseMainFactory.getItemUpgradableFactory());
    }

    public GildedRoseSystem(final ItemUpgradeInstanceable factory) {
        this.factory = factory;
    }

    public void updateItem(final Item item) {
        ItemUpgradeable upgratable = this.factory.getInstance(item);
        upgratable.updateQuality(item);
        upgratable.updateSellIn(item);
    }

}
El sistema tiene como responsabilidad es actualizar de cada tipo de queso(item) su calidad y su fecha limite de venta, sin siquiera saber el detalle de como se actualiza cada propiedad de los tipos de quesos, ni que reglas de negocio aplican para cada propiedad y tipo de queso, su responsabilidad es clara, modular y encapsulada.


Veamos ahora un ejemplo de los tipos de quesos(item):
package gildedrose;


public class NormalItemUpgradeable implements ItemUpgradeable {
    
    private GildedRoseBusiness business;

    public NormalItemUpgradeable() {
        business = new GildedRoseBusiness();
    }

    @Override
    public void updateQuality(final Item item) {
        this.business.degreteQualityInOne(item);
        if (item.sellIn <= 0) {
            business.degreteQualityInOne(item);
        }
    }

    @Override
    public void updateSellIn(final Item item) {
        this.business.degreteSellIn(item);
    }

}
Los quesos normales saben que su calidad se degrada en uno y que si su fecha de venta limite ya paso se degrada al doble, ha reglas de negocio que se aplican a dicho degradado pero es una responsabilidad que no es propia del tipo de queso. Veamos otro tipo de queso:
package gildedrose;

public class BackstageSpecialItemUpgradeable implements ItemUpgradeable {

    private GildedRoseBusiness business;

    public BackstageSpecialItemUpgradeable() {
        business = new GildedRoseBusiness();
    }

    @Override
    public void updateQuality(final Item item) {
        business.incrementQualityInOne(item);
        updateQualityInSpecialSellinTime(item);
    }
    
    @Override
    public void updateSellIn(final Item item) {
        business.degreteSellIn(item);
    }

    private void updateQualityInSpecialSellinTime(final Item item) {
        if (item.sellIn < 11)
            business.incrementQualityInOne(item);
        if (item.sellIn < 6)
            business.incrementQualityInOne(item);
        if (item.sellIn <= 0)
            item.quality = item.quality - item.quality;
    }

}
Este queso a diferencia del normal aumenta su calidad con forme se acerca a su fecha limite de venta en uno, dos o tres dependiendo el rango de proximidad a dicha fecha y una ves que llega a la fecha la calidad es cero, a cada incremento se le aplica una regla de negocio que no es responsabilidad como tal de el queso.

Veamos a continuación el negocio:
package gildedrose;


public class GildedRoseBusiness {
    
    public void degreteSellIn(Item item) {
        if (!item.name.equals("Sulfuras, Hand of Ragnaros")) {
            item.sellIn = item.sellIn - 1;
        }
    }

    public void incrementQualityInOne(Item item) {
        if (item.quality < 50) {
            item.quality = item.quality + 1;
        }
    }

    public void degreteQualityInOne(Item item) {
        if (item.quality > 0) {
            item.quality = item.quality - 1;
        }
    }

}
Ven esta clase se encuentran todas las definiciones del negocio en cuanto al decremento e incremento de la calidad sin importar como las aplique cada item y al decremento de la fecha limite.


Cual es la idea, bueno el sistema su responsabilidad es garantizar que de cada item se tiene que actualizar la calidad y la fecha, dirige la actualización, los tipos de item su responsabilidad es saber como se actualizan si incrementan o decrementan y cuantas veces ocurren estos incrementos y decrementos, y el negocio es quien establece los limites para cada incremento y decremento. Esto en que no ayuda, de entrada las responsabilidades están bien definidas, modulares y encapsuladas, aun que el sistema pudiera realizar todas las operaciones no seria bueno que lo hiciera ya que seria muy rígido y frágil ya que cada cambio que se realizara se afectaría todo el sistema, con esta separación de responsabilidades es menos rígido o en otras palabras muy fácil de realizar cambios y menos frágil un cambio no me rompe todo el sistema, dado que si el negocio cambia para cambiar los rangos de incremento o decremento los tipos de queso no se ven afectados su responsabilidad sigue siendo la misma o si se agregan, quitan o cambian su implementación los tipos de quesos el sistema no se ve afectado.


Para concluir podemos decir que el principio de la responsabilidad única es de los mas simples de los principios de SOLID, pero a la ves es de los mas difíciles de hacer bien, ya que a pesar que la separación de responsabilidades es algo natural en nuestro día a día, encontrar y separar bien estas responsabilidades en un programa es algo mas complicado y lo mejor es practicarlo he ir lo mejorando.


Por mi parte no me queda mas que agradecerles su tiempo y espero que el tema haya sido de su agrado y les sirva para su día a día y recuerda...




... hackea tu código!!!