Las factorías y el principio de responsabilidad única

Las factorías y el principio de responsabilidad única

Siguiendo nuestro curso de programación hoy no veremos un patrón de diseño nos centraremos en mejorar un código que mezcla la creación de un objeto con las interacciones sobre dicho objeto y como esto, amparándonos en los principios SOLID, en concreto el principio de responsabilidad única, veremos que como no es correcto y que tendremos que realizar una refactorización del código. Esta refactorización dará lugar a Factoría. Tenemos que tener claro que para nosotros, una factoría es cualquier método u objeto que se usa para crear otros objetos. Sin embargo el término factorías se refiere a los patrones de creación Abstract Factory y Factory Method que veremos más adelante.

SingleResponsibilityPrinciple

Ejemplo

Supongamos estamos desarrollando una aplicación para una pizzería en la que tendremos que crear nuestra Pizza pero también prepararlas, cocinarlas, cortarlas... Lo primero que nos viene a la cabeza, siguiendo un modelo de desarrollo orientado a objeto, es crear una clase Pizza con un método que se encarga de crear la Pizza pero también de prepararla.

public Class Pizzeria {    Pizza orderPizza() {         Pizza pizza;       pizza = new Pizza();       pizza.prepare();       pizza.bake();       pizza.cut();       pizza.box();       return pizza;    }}

Lo primero que podemos pensar de este diseño es que quizás en la pizzeria se quiera distinguir los distintos tipos de pizza como por ejemplo las cuatro quesos, chorizo, vegetal... Podemos sustituir el método por algo similar a:

Pizza orderPizza(String type) {    Pizza pizza;    if (type.equals("cheese")) {        pizza = new CheesePizza();    } else if (type.equals("greek")) {        pizza = new GreekPizza();    else if (type.equals("pepperoni")) {        pizza = new PepperoniPizza();    }    pizza.prepare();    pizza.bake();    pizza.cut();    pizza.box();    return pizza;}

Cuando se pide una pizza, le pasamos el tipo de pizza a crear. ¿Qué ocurre si añadimos nuevos tipos de Pizza o eliminamos alguno? En este caso, parece razonable pensar que la primera parte del método (donde creamos la Pizza) que más propensa es a cambiar con el tiempo (porque se cambia la selección de pizzas) pero también tiene una parte que se espere que no cambie. Tenemos que tener en cuenta que en este diseño, cada tipo de pizza sabe cómo prepararse a sí misma. En muchos casos, en problemas reales, esto no tiene por qué ser necesariamente así.

Tenemos un problema, tenemos que aplicar los principios SOLID en los que nos indican que debemos separar la creación de un objeto con lo que sería la interacción con él. Sacaremos la la lógica de la creación del objeto fuera del método orderPizza a un objeto aparte que llamaremos SimplePizzaFactory. Tenemos que tener siempre claro que una clase debe encargarse de crear y gestionar los objetos o bien utilizarlos (interaccionar), pero nunca ambas cosas. Debe tener poco acoplamiento; división de las tareas. Debe cumplir el principio de responsabilidad única.

uml_inicial_factoryMethod

Tendriamos un código similar a:

public class Pizzeria {    SimplePizzaFactory factory;    public PizzaStore(SimplePizzaFactory factory) {      this.factory = factory;    }    public Pizza orderPizza(String type) {      Pizza pizza;      pizza = factory.createPizza(type);      pizza.prepare();      pizza.bake();      pizza.cut();      pizza.box();      return pizza;    }}

Mientras que la clase encargada de crear las pizzas sería:

public class SimplePizzaFactory {    public Pizza createPizza(String type) {        Pizza pizza = null;        if (type.equals("queso")) {            pizza = new PizzaQueso();        } else if (type.equals("chorizo")) {            pizza = new PizzaChorizo();        } else if (type.equals("vegetal")) {            pizza = new PizzaVegetal();        }        return pizza;    }}

Después de todo, el código sigue estando parametrizado por el tipo de pizza, y lo único que hemos hecho ha sido trasladar el problema a otro objeto. Efectivamente, así es y en un ejemplo tan tonto no se aprecia ninguna ventaja (aparte de la legibilidad del código y de que cada método y, a ser posible, cada clase, se dediquen a una sola cosa, que por sí sólo posiblemente ya hiciera que mereciese la pena separarlo). Pero hemos de pensar que muchas veces dicha lógica se repite en distintos puntos del programa. La clave está en eliminar la creación de los objetos concretos de la lógica del cliente, de manera que sólo haya que ir a un sitio cuando las clases concretas cambien.

En próximos artículos veremos como evolucionar este ejemplo hacía algún patrón de diseño de creación. Os animo haber si sois capaces a realizar una refactorización de este código hacía un Abstract Factory o un Factory Method.

Para ti
Queremos saber tu opinión. ¡Comenta!