Patr贸n de dise帽o de m茅todo de f谩brica en Java

    Introducci贸n

    Los patrones de dise帽o son una colecci贸n de metodolog铆as de programaci贸n que se utilizan en la programaci贸n diaria. Representan soluciones a algunos problemas que ocurren com煤nmente en la industria de la programaci贸n, que tienen soluciones intuitivas.

    Tarde o temprano, un programa de escritorio, una aplicaci贸n m贸vil o alg煤n otro tipo de software se volver谩 inevitablemente complejo y comenzar谩 a presentar ciertos tipos de problemas. Estos problemas suelen estar relacionados con la complejidad de nuestra base de c贸digo, la no modularidad, la incapacidad de separar determinadas partes entre s铆, etc.

    Por esta raz贸n, los patrones de dise帽o se han convertido en el est谩ndar de facto en la industria de la programaci贸n desde su uso inicial hace algunas d茅cadas debido a su capacidad para resolver muchos de estos problemas. En este art铆culo, profundizaremos en una de estas metodolog铆as, a saber, el Patr贸n de m茅todo de f谩brica.

    Patrones de dise帽o creacional

    El patr贸n de m茅todo de f谩brica es uno de los varios patrones de dise帽o de creaci贸n que usamos a menudo en Java. Su prop贸sito es hacer que el proceso de creaci贸n de objetos sea m谩s simple, m谩s modular y m谩s escalable.

    Estos patrones controlan la forma en que definimos y dise帽amos los objetos, as铆 como c贸mo los instanciamos. Algunos encapsulan la l贸gica de creaci贸n lejos de los usuarios y manejan la creaci贸n (Factory y Abstract Factory), algunos se enfocan en el proceso de construcci贸n de los objetos en s铆 mismos (Builder), algunos minimizan el costo de creaci贸n (Prototype) y algunos controlan el n煤mero de instancias en el JVM completo (Singleton).

    Espec铆ficamente, el m茅todo Factory y Abstract Factory son muy comunes en el desarrollo de software Java.

    El patr贸n del m茅todo de f谩brica

    El Patr贸n de m茅todo de f谩brica (tambi茅n conocido como Constructor virtual o Patr贸n de plantilla de f谩brica) es un patr贸n de dise帽o de creaci贸n utilizado en lenguajes orientados a objetos.

    La idea principal es definir una interfaz o clase abstracta (una f谩brica) para crear objetos. Sin embargo, en lugar de instanciar el objeto, la instanciaci贸n se deja a sus subclases.

    Cada objeto se crea a trav茅s de un m茅todo de f谩brica disponible en la f谩brica, que puede ser una interfaz o una clase abstracta.

    Si la f谩brica es una interfaz, las subclases deben definir sus propios m茅todos de f谩brica para crear objetos porque no hay una implementaci贸n predeterminada.

    Si la f谩brica es una clase, las subclases pueden usar la implementaci贸n existente u opcionalmente anular los m茅todos de f谩brica.

    Con Factory Pattern, la l贸gica de creaci贸n de objetos se oculta al cliente. En lugar de conocer la clase de objeto exacta y crear una instancia a trav茅s de un constructor, la responsabilidad de crear un objeto se aleja del cliente.

    Luego, el cliente puede crear objetos a trav茅s de una interfaz com煤n que simplifica el proceso.

    Este enfoque separa la creaci贸n de objetos de la implementaci贸n, lo que promueve un acoplamiento flexible y, por lo tanto, un mantenimiento y actualizaciones m谩s f谩ciles.

    Motivaci贸n

    Despu茅s de una introducci贸n te贸rica, veamos el patr贸n de f谩brica en la pr谩ctica.

    Imagina que estamos intentando construir nuestra propia nave espacial. Dado que este es un ejemplo simplificado, tambi茅n simplificaremos la construcci贸n y diremos que nuestra nave espacial consta de un casco, un Enginey un sat茅lite Dish:

    public class SpaceshipHangar {
        public Spaceship createSpaceship() {
            Spaceship ship = new Spaceship();
            Engine engine = new SublightEngine();
            Dish dish = new RoundDish();
    
            ship.setEngine(engine);
            ship.setDish(dish);
    
            return ship;
        }
    }
    

    Nota: SublightEngine y RoundDish son subclases de Engine y Dish, respectivamente.

    Ahora imagina que le mostraste tu nueva nave espacial a un amigo y, de repente, 茅l tambi茅n quiere una nave espacial propia. Pero en lugar del SublightEngine quieren poner un HyperdriveEngine, y en lugar del RoundDish quieren poner un SquareDish:

    public class SpaceshipHangar {
        public Spaceship createSpaceship() {
            Spaceship ship = new Spaceship();
            Engine engine = new HyperdriveEngine();
            Dish dish = new SquareDish();
    
            ship.setEngine(engine);
            ship.setDish(dish);
    
            return ship;
        }
    }
    

    Dado que las instancias est谩n codificadas de forma r铆gida, puede crear un duplicado de su m茅todo original o cambiar su c贸digo.

    Si duplica el m茅todo cada vez que alguien m谩s quiere hacer una peque帽a modificaci贸n en el barco, esto se convierte r谩pidamente en un problema porque tendr谩 muchos m茅todos casi id茅nticos con una diferencia m铆nima.

    Si cambia el c贸digo original, entonces el m茅todo en s铆 pierde el punto porque necesita ser reescrito cada vez que alguien quiere hacer un peque帽o cambio en el barco.

    Esto contin煤a a medida que agrega m谩s variaciones relacionadas de una colecci贸n l贸gica, todas las naves espaciales, por ejemplo.

    Implementaci贸n

    Para resolver este problema, podemos crear una F谩brica de naves espaciales y dejar los detalles (qu茅 motor o plato se usa) a las subclases para definir.

    En lugar de codificar la creaci贸n de objetos en el createSpaceship() m茅todo con new operadores, crearemos un Spaceship interfaz e implementarlo a trav茅s de un par de clases concretas diferentes.

    Luego, usando un SpaceshipFactory como nuestro punto de comunicaci贸n con estos, crearemos una instancia de objetos de Spaceship type, sin embargo, implementado como clases concretas. Esta l贸gica permanecer谩 oculta para el usuario final, ya que estaremos especificando qu茅 implementaci贸n queremos a trav茅s del argumento pasado al SpaceshipFactory m茅todo utilizado para la instanciaci贸n.

    Empecemos con el Spaceship interfaz:

    public interface Spaceship {
        void setEngine(Engine engine);
        void setDish(Dish dish);
    }
    

    Dado que estamos trabajando con Engine y Dish clases, vamos a definirlas muy r谩pido:

    public class Engine {
        private String model;
    
        public Engine(String model) {
            this.model = model;
        }
    
        // Getters and Setters
    }
    
    public class Dish {
        private String model;
    
        public Dish(String model) {
            this.model = model;
        }
    
        // Getters and Setters
    }
    

    Y ahora, implementemos la interfaz a trav茅s de dos implementaciones concretas, comenzando con el SpaceshipMk1:

    public class SpaceshipMk1 implements Spaceship {
        private Engine engine;
        private Dish dish;
    
        public SpaceshipMk1(Engine engine, Dish dish) {
            this.engine = engine;
            System.out.println("Powering up the Mk.1 Raptor Engine");
    
            this.dish = dish;
            System.out.println("Activating the Mk.1 Satellite Dish");
        }
    
        @Override
        public void setEngine(Engine engine) {
            this.engine = engine;
        }
    
        @Override
        public void setDish(Dish dish) {
            this.dish = dish;
        }
    }
    

    Y el SpaceshipMk2:

    public class SpaceshipMk2 implements Spaceship {
        private Engine engine;
        private Dish dish;
    
        public SpaceshipMk2(Engine engine, Dish dish) {
            this.engine = engine;
            System.out.println("Powering up the Mk.2 Raptor Engine");
    
            this.dish = dish;
            System.out.println("Activating the Mk.2 Satellite Dish");
        }
    
        @Override
        public void setEngine(Engine engine) {
            this.engine = engine;
        }
    
        @Override
        public void setDish(Dish dish) {
            this.dish = dish;
        }
    }
    

    Ahora, en lugar de simplemente instanciarlos como lo har铆amos normalmente, creemos un SpaceshipFactory para ellos:

    public class SpaceshipFactory {
        public Spaceship getSpaceship(Engine engine, Dish dish) {
            if (engine.getModel().equals("Mk.2") && dish.getModel().equals("Mk.2")) {
                return new SpaceshipMk2(engine, dish);
            } else if (engine.getModel().equals("Mk.1") && dish.getModel().equals("Mk.1")) {
                return new SpaceshipMk1(engine, dish);
            } else {
                System.out.println("Incompatible models of engine and satellite dish.");
            }
            return null;
        }
    }
    

    La f谩brica normalmente tiene un m茅todo 煤nico llamado getTypeName() con los par谩metros que le gustar铆a pasar. Entonces, a trav茅s de tantos if declaraciones requeridas, verificamos qu茅 clase exacta debe usarse para atender la llamada.

    Y con esta f谩brica en su lugar, cuando nos gustar铆a crear una instancia de cualquiera de estas dos clases de naves espaciales, usamos la f谩brica:

    SpaceshipFactory factory = new SpaceshipFactory();
    
    Engine engineMk1 = new Engine("Mk.1");
    Dish dishMk1 = new Dish("Mk.1");
    
    Engine engineMk2 = new Engine("Mk.2");
    Dish dishMk2 = new Dish("Mk.2");
    
    Spaceship spaceshipMk1 = factory.getSpaceship(engineMk1, dishMk1);
    Spaceship spaceshipMk2 = factory.getSpaceship(engineMk2, dishMk2);
    Spaceship spaceshipMkHybrid = factory.getSpaceship(engineMk1, dishMk2);
    

    Aqu铆, en lugar de usar el new operador para instanciar cualquiera de las naves espaciales, recurrimos a la interfaz com煤n Spaceship y usando la f谩brica construir / instanciar los objetos. Ejecutar este c贸digo producir铆a:

    Powering up the Mk.1 Raptor Engine
    Activating the Mk.1 Satellite Dish
    Powering up the Mk.2 Raptor Engine
    Activating the Mk.2 Satellite Dish
    Incompatible models of engine and satellite dish.
    

    Nota: Idealmente, tambi茅n tendr铆amos f谩bricas de motores y platos, especialmente si tenemos tipos derivados como HyperdriveEngine y SquareDish. Tener m煤ltiples f谩bricas terminar铆a con m煤ltiples new palabras clave, que va en contra de lo que representa el m茅todo de f谩brica.

    Entonces, 驴cu谩l es la soluci贸n? 驴No hemos hecho solo una rotonda y acabamos con el mismo problema?

    Ah铆 es donde interviene el Patr贸n de dise帽o de f谩brica abstracta. Es como una f谩brica de f谩bricas que, utilizando el mismo enfoque, crear铆a una instancia de todas las f谩bricas relacionadas con naves espaciales con un solo new llamar al inicio.

    Pros y contras

    Pros

    • Permite c贸digo acoplado libremente, lo que hace que los cambios sean menos disruptivos
    • F谩cil de realizar pruebas unitarias y simulacros ya que el c贸digo est谩 desacoplado

    Contras

    • Hace que el c贸digo sea menos legible ya que todo el c贸digo de creaci贸n de objetos est谩 detr谩s de una capa de abstracci贸n
    • Si se usa con Abstract Factory Pattern (una f谩brica de f谩bricas), el c贸digo se vuelve engorroso pero funcional r谩pidamente

    Conclusi贸n

    El m茅todo de f谩brica y otros patrones de dise帽o se prueban y se prueban t茅cnicas de trabajo. Independientemente de si se usa en proyectos personales o en bases de c贸digo industriales muy grandes. Ofrecen soluciones inteligentes a algunos problemas comunes y animan a los desarrolladores y equipos enteros a hacer primero el dise帽o de la arquitectura y luego la programaci贸n. Esto casi siempre conduce a un c贸digo de mayor calidad en lugar de saltar directamente a la programaci贸n.

    Es un error pensar que los patrones de dise帽o son soluciones sagradas para todos los problemas. Los patrones de dise帽o son t茅cnicas para ayudar a mitigar algunos problemas comunes, inventadas por personas que han resuelto estos problemas en numerosas ocasiones.

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *