Patrones

Charla sobre Factory Method

Recientemente tuve la oportunidad de dar una pequeña charla de introducción al patrón Factory Method que se enmarca dentro del ciclo de presentaciones sobre patrones de diseño en Autentia.

Aquí tienes el vídeo y más abajo un texto que escribí explicando el patrón como apoyo a la charla. Espero que pueda enseñarte algo.

Factory Method

Motivación

La intención del Method Factory según el libro Design Patterns del GoF (Gamma et. al. Design Pattens, 1994) es:

“Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method let a class defer instantiation to subclasses.”

Diagrama de Clases

FM_Clases1

 

 

Créditos diagrama de clases: http://www.apwebco.com

Se trata de la implementación clásica recogida en el libro de GoF. En ella podemos ver un cliente que necesita un objeto de un tipo concreto. En vez de instanciar el objeto concreto de la familia que necesita y manejarlo como tal (de forma concreta, no genérica), se apoya en una Factoría concreta, que le proporcionará un objeto concreto. La factoría concreta (ConcreteFactory) es una implementación de la Factoría Abstracta (AbstractFactory) de objetos de la familia.

 

Por tanto, el cliente (Client) maneja una ConcreteFactory como si fuera una AbstractFatory, llamando al método factory que en este caso es “CreateProduct()”.

Como está manejando en realidad una implementación concreta (ConcreteFactory), al invocar este método recibe un “Product” concreto, que es una implementación de AbstractProduct, es decir, de la familia de productos que se están manejando.

En resumen, el cliente queda aislado de las implementaciones concretas de cada objeto. Delega la responsabilidad de la instanciación del objeto en las factorías concretas.

Cuando usar el Factory Method

El Factory Method parece un buen patrón para usar en situaciones como las siguientes:

  • Cuando una clase necesita instanciar otra pero queremos abstraer de todas las operaciones de instanciación, o no puede saber qué tipo concreto de clase se va a instanciar: solamente tiene conocimiento de la familia pero no la clase concreta. Por ejemplo: tipos de pago pero no puede saber si será en concreto con tarjeta, metálico, electrónico, etcétera…
  • Cuando se quiere que sean las clases hijas las que determinen qué clases concretas se instancian de una familia.
  • Como alternativa a los constructores del lenguaje. Bien para reducir el acomplamento de la aplicación o bien para hacer la sintáxis más expresiva (especialmente en el caso del Static Factory Method).
  • Cuando se prevé que puede haber un aumento en el número de productos de la familia, o se desee delegar en lugares concretos el proceso de creación.

Ventajas y Desventajas

Como ventajas se puede citar:

  • Elimina la necesidad de instanciar de forma explícita los objetos que se van a utilizar, lo que tiene ventajas claras en el desacoplamiento e interdependencia de las clases.
  • Es fácilmente extendible ya que la arquitectura queda abierta a desarrollos horizontales con nuevas clases que extiendan a la factoría y a la familia de productos.
  • Permite encapsular en las clases factorías toda la lógica de creación de objetos, que a pueden ser más complejas que realizar un simple new.
  • Es una buena forma de disponer de una solución en la arquitectura de la aplicación que se entiende fácilmente con metáforas.

Como desventajas podemos considerar las siguientes:

  • Aumenta la complejidad de la aplicación, añadiendo nuevos niveles de indirección. No obstante, al tratarse de un patrón conocido es fácil abstraerse de  esta complejidad
  • No se puede utilizar cuando el cliente no tiene claro qué tipo concreto de objeto de la familia necesita.
  • Al delegar funciones puede ser más complejo encontrar en primera instancia la mecánica de funcionamiento de la aplicación.
  • Uso no es adecuado en todos los casos: es necesaria una familia de objetos sobre la que operar. Si no existe este polimorfismo no se tiene por qué caer en la tentación de hacer factorías de objetos para al futuro. Hay que analizar bien el problema al que nos enfrentamos para ver si encaja.

Tipos de Implemetación

Básicamente podemos distinguir entre tres variaciones a la hora de desarrollar el factory method:

  • Cuando la factoría “padre” es abstracta o es una interfaz y por lo tanto no provee ningún tipo de implementación por defecto. Son las clases que heredan o implementan las que cargan con toda la responsabilidad a la hora de crear los objetos que van a servir.
  • Cuando la factoría “padre” realiza algún tipo de comportamiento por defecto por encima de lo que hagan las clases que la utilizan para implementar. En este caso se tiene un objeto que devolver por defecto. Para este tipo de variante, la clase factoría no es una interfaz ni una clase abstracta, sino una clase normal que es heredada por otras subclases. Las subclases en este case se pueden utilizar par extender la familia de objetos que son creados en este caso.
  • Cuando la factoría “padre” no solamente tiene un comportamiento por defecto sino que su método de creación admite parámetros para indicar qué tipo de objeto se tiene que devolver. En este caso no serían necesarias las clases hijas aunque pueden existir. Se puede extender fácilmente la familia de objetos que se crean añadiendo nuevos elementos o también añadiendo subclases a la factoría que realicen implementaciones concretas.

Caso especial: Static Factory Method

Es fácil confundir por el cometido y el nombre al patrón Factory Method con la técnica conocida como Static Factory Method. No se trata exactamente del mismo concepto, aunque también está relacionado con la creación de objetos.

Se trata del uso de métodos estáticos en la creación de objetos en vez del constructor. El método por lo tanto está dentro del propio objeto que se está creando. Por ejemplo en el modo clásico tenemos que:


Foo x = new Foo();

Pero si queremos ganar control sobre la generación del objeto (porque es un recurso limitado montando un pool por ejemplo), o si queremos implementar rápidamente una especie de factoría implícita dentro de la propia clase, podemos cambiar el constructor y hacerlo privado, para introducir un método estático con un nombre significativo como “create” que nos devuelva una instancia.


Foo x = Foo.create();

Si Foo es una clase que es heredada por clases que actúan como subtipos podemos añadir un parámetro que indique qué subclase de Foo se tiene que devolver. Por supuesto, la subclase tiene una implementación particular que puede variar el comportamiento:


Foo x = Foo.create(tipo1);

Donde FooTipo1 extends Foo, y por lo tanto puede ser usado por Foo.

Otra de las aplicaciones de esto métodos es la de sustituir a los constructores, lo que mejora la lectura de código al poder aplicar nombres más significativos. Por ejemplo, en vez de:


Punto punto = new Punto(x,y, sistemaMedida);

Podemos hacer:


Punto punto = Punto.createByMetros(x,y);
Punto punto2 = Punto.createByInches(x,y);

Ejemplo de uso de Factory Method

Enunciado: se quiere modelar una familia de productos de la superclase “Pelota” para diferentes deportes y se quieren asignar a unos objetos de la clase deportista. Dependiendo del tipo de deportista se requerirá un tipo diferente de pelota. De este modo, a un futbolista se le asignará una pelota de fútbol, y a un tenista una de tenis.

El objetivo al utilizar el patrón factory method es por una parte lograr que la aplicación principal sea independiente del tipo concreto de la familia pelota que se está utilizando, y por otra parte que sea la factoría la que se encargue de proveer del tipo de pelota que se tiene que proporcionar a cada tipo de deportista.

De este modo se delegará la creación de la pelota adecuada a una fábrica o factoría especializada en generar el tipo de pelota necesario. Hacer que la aplicación soporte un tipo de pelota adicional es muy sencillo: solamente será necesario extender una nueva fábrica que se encargue de proveer de nua nueva pelota de baloncesto o de golf por ejemplo.

La familia de Pelota queda formada por:

 

public abstract class Pelota {</pre>
<pre>    float diametro;
    float peso;
    String material;</pre>
<pre>    public abstract String getTipo();

    //Getters y Setters...
}

Y las clases que implementan a Pelota son:


public class BaloncestoPelota extends Pelota {
    public String getTipo() {
        return "pelota de Baloncesto";
    }
}

public class FutbolPelota extends Pelota {
    public String getTipo() {
        return "una pelota de fútbol";
    }
}

public class TenisPelota extends Pelota {
    public String getTipo() {
        return "pelota de tenis";
    }
}

public class GenericaPelota extends Pelota {
    public String getTipo() {
        return "pelota genérica";
    }
}

Como se puede ver se ha definido una familia de objetos típica que será la que será generada por las fábricas de este tipo de objetos: se definirá una fábrica genérica y unas subclases de esta fábrica que serán las que devuelvan los diferentes tipos de objetos.


public interface  FactoryPelotas {
    public Pelota create();
}

Y las fábricas concretas que implementan la interfaz

public class FactoryBaloncestoPelotas implements FactoryPelotas {
    public Pelota create() {
        return new BaloncestoPelota();
    }
}

public class FactoryFutbolPelotas implements FactoryPelotas {
    public Pelota create() {
        return new FutbolPelota();
    }
}

public class FactoryTenisPelotas implements FactoryPelotas {
    public Pelota create() {
        return new TenisPelota();
    }
}

Por tanto para tomar un tipo u otro de objeto se tiene que emplear una de los objetos factoría que implementan la interfaz FactoryPelotas.

Finalmente la clase Deportista a la que se le va a asignar un tipo e pelota u otro es trivial:


public class Deportista {
    String nombre;
    Pelota pelota;

    public Deportista(String nombre) {
        this.nombre = nombre;
    }

    public String getSaludo() {
        return "Hola, soy " + getNombre() + " y juego con una pelota de "+ getPelota().getTipo() + " que pesa " + getPelota().getPeso() + " gramos";
    }

    //Getters y Setters...
}

El programa principal se limita a instanciar unos objetos deportistas, a asignarles una pelota adecuada llamando a las factorías necesarias y a pintar el saludo que diría el deportista anunciando el tipo de pelota que posee.

 

</pre>
<pre>public class main {
    public static void main(String[] args) {
        ArrayList&lt;Deportista&gt; deportistas = new ArrayList&lt;Deportista&gt;();

        // Definición de deportistas
        Deportista baloncestoDep = new Deportista("Gasol");
        Deportista futbolDep = new Deportista("Messi");
        Deportista tenisDep = new Deportista("Nadal");

        // Fábricas concretas
        baloncestoDep.setPelota(new FactoryBaloncestoPelotas().create());
        futbolDep.setPelota(new FactoryFutbolPelotas().create());
        tenisDep.setPelota(new FactoryTenisPelotas().create());

        deportistas.add(baloncestoDep);
        deportistas.add(futbolDep);
        deportistas.add(tenisDep);

        for (Deportista dep : deportistas) {
            dep.getPelota().setPeso((float) Math.random() * 500);
            System.out.println(dep.getSaludo());
        }
    }
}

Una variante que se ha indicando es la posibilidad de emplear una fábrica que se encargue, utilizando un método estático, de proveer del objeto de una clase determinada utilizando un parámetro:


public class FactoryParamPelotas {
    public static Pelota create(TipoPelota tipo) {
        Pelota salida;

        switch (tipo) {
            case BALONCESTO:
                salida = new BaloncestoPelota();
                break;
            case TENIS:
                salida = new TenisPelota();
                break;
            case FUTBOL:
                salida = new FutbolPelota();
                break;
            default:
                salida = new GenericaPelota();
                break;
        }

        return salida;
    }
}

Donde TipoPelota es una enumeración para poder hacer el switch


public enum TipoPelota {
    BALONCESTO, TENIS, FUTBOL
}

De este modo no se delega en las clases hijas la responsabilidad de la creación de los objetos concretos. No obstante sí que es posible generar class hijas simplemente heredando y sobreescribiendo el método factory:


public class FactoryParamExtPelotas extends FactoryParamPelotas {
    public static Pelota create(TipoPelota tipo) {
        return new GenericaPelota();
    }
}

Por lo tanto se tienen dos modos de extender la funcionalidad a nuevos elementos de la familia: mediante la adición de nuevos elementos a la enumeración y a la estructura de control que decide cuál devolver, o añadiendo subclases, lo que puede ser interesante a la hora de desarrollos complejos en este punto.

De este modo en la clase main podríamos tener:

//Utilizando constructor parametrizado y estático.
baloncestoDep.setPelota(FactoryParamPelotas.create(TipoPelota.BALONCESTO));
baloncestoDep.setPelota(FactoryParamPelotas.create(TipoPelota.FUTBOL));
baloncestoDep.setPelota(FactoryParamPelotas.create(TipoPelota.TENIS));

Relación con el patrón Abstract Factory

La relación más clara y directa es con el patrón Abstract Factory. Este patrón se sitúa en un nivel superior de abstracción: Si el patrón Factory Method trabaja con una familia concreta de productos, el Abstract Factory trabaja con familias completas  de productos.

De este modo el patrón Abstract Factory se implementa a base de Factory Methods: utilizan factorías que crean objetos de diferentes familias que están relacionados.

Siguiendo el ejemplo de pelotas, se podría añadir al mundo deportivo la familia de zapatillas. De este modo, se podría tener una AdidasFactory que tuviese los métodos createPelota() y createZapatillas(); del mismo modo otra factoría sería NikeFactory que tendría los métodos createPelota y createZapatillas. Y ambas serían realizaciones concretas de la factoría abstracta de MarcasDeportivasFactory, que crearían objetos concretos de la familia que heredase de Pelota y de Zapatilla.

Un diagrama de clases genérico del Abstract Factory es el siguiente:

class-example-abstract-factory

 

Créditos diagarama de clases: http://www.uml-diagrams.org/

Por tanto se podría considerar que el patrón Factory Method es una simplificación del patrón Abstract Factory cuando las diferentes fábricas concretas solamente generan objetos de un sólo tipo de productos.