El patrón de diseño Factory, ventajas sobre new y diferencias con Builder

Escrito por el .
java planeta-codigo programacion
Enlace permanente Comentarios

El patrón de diseño Factory es uno de los patrones dedicados a la creación de instancias. El patrón Factory proporciona varias ventajas sobre la palabra reservada new que proporcionan los lenguajes de programación orientada a objetos para la creación de instancias. Es muy utilizado en muchas librerías, en ocasiones también es necesario implementar una clase que implemente este patrón por lo que es muy útil conocer y usar este patrón en las ocasiones que sea adecuado.

Java

El patrón de diseño factoría o Factory es uno de los más empleados en multitud de ocasiones en el código propio y aunque no se implemente al ser usado en multitud de librerías también en muchos casos conviene conocer sus principios y ventajas frente a otros métodos.

El patrón de diseño Factory es uno de patrones ya identificados como útiles en los que su aplicación es adecuada. Los patrones de diseño se clasifican en las áreas funcionales de creacionales dedicados a la creación de objetos, de comportamiento centrados en la comunicación entre objetos, estructurales para mantener de forma sencilla relaciones entre entidades y finalmente los patrones de concurrencia empleados en la aplicación concurrente con múltiples hilos de ejecución.

Las limitaciones de instanciar objetos con la palabra reservada new

Los lenguajes de programación orientada a objetos modelan los conceptos que trata una aplicación mediante clases que define las propiedades del objeto y los métodos que permiten el acceso o modifican el estado del objeto, esto proporciona encapsulación y es uno de los conceptos fundamentales de la programación orientada a objetos entre los que también se encuentran el polimorfismo, herencia y composición.

1
2
3
4
5
public abstract class Shape {

    public abstract double getPerimeter();
    public abstract double getArea();
}
Shape.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class Circle extends Shape {

    private double radious;

    public Circle(double radious) {
        this.radious = radious;
    }

    @Override
    public double getPerimeter() {
        return 2 * Math.PI * radious;
    }

    @Override
    public double getArea() {
        return Math.PI * radious * radious;
    }
}
Circle.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class Square extends Shape {

    private double length;

    public Square(double length) {
        this.length = length;
    }

    @Override
    public double getPerimeter() {
        return 4 * length;
    }

    @Override
    public double getArea() {
        return 2 * length;
    }
}
Square.java

Las clases son una definición de las entidades de una aplicación en tiempo de compilación, en tiempo de ejecución un programa crea instancias individuales de las clases. En el lenguaje de programación Java para la creación de instancias de objetos se usa la palabra reservada new. Al emplear esta palabra reservada el lenguaje en tiempo de ejecución devuelve una referencia a la instancia creada, habitualmente se asigna la referencia a una variable.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class Main {

    public static void main(String[] args) {
        Shape shape1 = new Square(2d);
        System.out.printf("Shape perimeter: %s%n", shape1.getPerimeter());
        System.out.printf("Shape area: %s%n", shape1.getArea());

        Shape shape2 = new Circle(2d);
        System.out.printf("Shape perimeter: %s%n", shape2.getPerimeter());
        System.out.printf("Shape area: %s%n", shape2.getArea());
    }
}
Main-new.java

La palabra reservada new o una equivalente es el mecanismo que emplean muchos lenguajes de programación orientados a objetos como Java y C#. Al ser un mecanismo proporcionado por el lenguaje es sencillo de utilizar en la mayoría de ocasiones.

Aunque la palabra reservada new es una opción válida para crear instancias tiene algunas limitaciones que en algunas ocasiones requieren una alternativa.

Una de sus limitaciones es que la palabra reservada new siempre devuelve una instancia del tipo concreto que explícitamente se crea, a veces el tipo concreto de la instancia no se conoce en tiempo de compilación, por ejemplo en tiempo de compilación se sabe que se necesita un Shape pero solo hasta en tiempo de ejecución no se sabe si la instancia a construir es un Square o un Circle.

Otra limitación es que el código que usa new tiene la responsabilidad de crear las instancias, a veces interesa delegar esta responsabilidad en otra clase para no repetirla múltiples veces.

Para evitar las las limitaciones de la palabra new se suele emplear alguno de los patrones creacionales, como el patrón de diseño Factory.

El patrón de diseño Factory

El patrón Factory es un patrón de diseño creacional que tiene como función crear instancias de objetos sin las limitaciones de la palabra reservada new. El patrón Factory solventa las dos limitaciones comentadas de new, pudiendo crear diferentes tipos de instancias que implementen una interfaz o hereden de una clase común, al mismo tiempo el código de creación de las instancias queda encapsulado en la clase que implementa el patrón Factory abstrayendo al código que lo usa de la responsabilidad de creación de instancias.

La creación de instancias es una de las tareas más comunes que realiza un programa, de modo que habitualmente es necesario implementar una clase factoría o en caso de usar una librería utilizar una factoría implementada por una clase de la librería. Un contenedor de IoC y su inyección de dependencias que también tienen como misión delegar en ellos la creación de instancias requieren implementar una factoría propia que el contenedor invoca.

El patrón Factory es tan simple como una clase con uno o varios métodos que devuelven la instancia que la factoría crea, el método de la factoría puede recibir parámetros. El método factoría puede ser estático si la creación de las instancias depende de únicamente los parámetros que recibe el método o el método puede ser de instancia y estar basado además de los parámetros que recibe en propiedades de la instancia de la factoría.

El patrón Factory hay dos categorías: Factory Method Pattern que se basan en un único método y Abstract Factory Pattern que son una indirección más pudiendo sustituir una implementación de factoría por otra haciendo posible devolver cada una diferentes instancias.

Como muchos patrones de diseño añade cierta complejidad en el diseño de las clases del programa con lo que su uso debe estar justificado con el objetivo de simplificar el código o la necesidad de evitar las limitaciones de la palabra new.

Diagrama de clases del patrón de diseño Factory

Diagrama de clases del patrón de diseño Factory

Diferencias con el patrón de diseño Builder

El patrón de diseño Builder es otro patrón creacional dedicado a la creación de instancias, aunque comparte objetivos las implementación es diferente del patrón Factory.

Una diferencia entre el patrón Factory y el patrón Builder es que el patrón Factory crea la instancia en un único paso con la invocación de un método de la factoría que lo devuelve inmediatamente, el patrón Builder suele requerir la invocación de varios métodos y un método final build que realiza la creación de la instancia con una API fluída.

Los Builder son objetos con estado y requieren crear una instancia de Builder, el patrón Factory no requiere crear una instancia y se puede compartir entre varios objetos que la necesitan.

Por el contrario el patrón Builder proporciona más control sobre los pasos de la creación de la instancia y proporciona más flexibilidad para variar la representación interna de la instancia creada. Otra diferencia es que el Builder crea instancias con diferente composición de objetos.

Ejemplo de patrón de diseño Factory

En el siguiente ejemplo de implementación en Java de patrón Factory Method se observa que el método factoría en función del parámetro se devuelve una instancia u otra empleando una sentencia if-else. Esta factoría además contiene otros dos métodos de factoría específicos para tipos concretos de Shape.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class ShapeFactory {

   public static Shape create(String type) {
       if (type.equals("square")) {
           return new Square(1);
       } else if (type.equals("circle")) {
           return new Circle(1);
       } else {
           throw new IllegalArgumentException();
       }
   }

   public static Square createSquare(double length) {
       return new Square(length);
   }

   public static Circle createCircle(double radious) {
       return new Circle(radious);
   }
}
ShapeFactory.java

La sentencia if-else hace que no se cumpla el principio O (Open-closed) de SOLID, si se añade un nuevo tipo requiere modificar el código del método en vez de proporcionar una extensión. Para evitar la sentencia if-else en una factoría hay varias opciones.

Con la incorporación de los default methods en Java el método factoría es posible implementarlo en una interfaz de Shape no requiriendo una clase ShapeFactory dedicada que lo contenga.

El siguiente programa crea una figura según el parámetro indicado que solo se conoce en tiempo de ejecución según el argumento proporcionado al lanzar el programa y una instancia de cada tipo de figura, se observa que la clase Main no utiliza directamente los constructores de ninguna figura.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class Main {

    public static void main(String[] args) {
        String shapeType = args[0];
        Shape shape1 = ShapeFactory.create(shapeType);
        System.out.printf("Shape perimeter: %s%n", shape1.getPerimeter());
        System.out.printf("Shape area: %s%n", shape1.getArea());

        Shape shape2 = ShapeFactory.createSquare(2d);
        System.out.printf("Shape perimeter: %s%n", shape2.getPerimeter());
        System.out.printf("Shape area: %s%n", shape2.getArea());

        Shape shape3 = ShapeFactory.createCircle(2d);
        System.out.printf("Shape perimeter: %s%n", shape3.getPerimeter());
        System.out.printf("Shape area: %s%n", shape3.getArea());
    }
}
Main-factory.java


Comparte el artículo: