¡Hola! Hoy continuaremos estudiando patrones de diseño y discutiremos el patrón de fábrica abstracto . Patrones de diseño: Fábrica abstracta - 1Esto es lo que cubriremos en la lección:
  • Discutiremos qué es una fábrica abstracta y qué problema resuelve este patrón.
  • Crearemos el esqueleto de una aplicación multiplataforma para pedir café a través de una interfaz de usuario
  • Estudiaremos las instrucciones sobre cómo usar este patrón, incluido mirar un diagrama y código
  • Y como extra, esta lección incluye un huevo de Pascua oculto que te ayudará a aprender a usar Java para determinar el nombre del sistema operativo y, según el resultado, realizar una u otra acción.
Para comprender completamente este patrón, debe estar bien versado en los siguientes temas:
  • herencia en java
  • Clases abstractas y métodos en Java.

¿Qué problemas resuelve una fábrica abstracta?

Una fábrica abstracta, como todos los patrones de fábrica, nos ayuda a garantizar que los nuevos objetos se creen correctamente. Lo usamos para administrar la "producción" de varias familias de objetos interconectados. Varias familias de objetos interconectados... ¿Qué significa eso? No te preocupes: en la práctica, todo es más sencillo de lo que parece. Para empezar, ¿qué podría ser una familia de objetos interconectados? Supongamos que estamos desarrollando una estrategia militar que involucra varios tipos de unidades:
  • infantería
  • caballería
  • arqueros
Este tipo de unidades están interconectadas porque sirven en el mismo ejército. Podríamos decir que las categorías enumeradas anteriormente son una familia de objetos interconectados. Entendemos esto. Pero el patrón de fábrica abstracto se usa para organizar la creación de varias familias de objetos interconectados. Aquí tampoco hay nada complicado. Sigamos con el ejemplo de la estrategia militar. En términos generales, las unidades militares pertenecen a varios bandos beligerantes diferentes. Dependiendo de qué lado estén, las unidades militares pueden variar significativamente en apariencia. Los soldados de a pie, los jinetes y los arqueros del ejército romano no son lo mismo que los soldados de a pie, los jinetes y los arqueros vikingos. En la estrategia militar, los soldados de diferentes ejércitos son diferentes familias de objetos interconectados. Sería gracioso si un programador' Este error provocó que un soldado con un uniforme francés de la época de Napoleón y el mosquete en ristre se encontrara caminando entre las filas de la infantería romana. El patrón de diseño de fábrica abstracto es necesario precisamente para resolver este problema. No, no el problema de la vergüenza que puede venir del viaje en el tiempo, sino el problema de crear varios grupos de objetos interconectados. Una fábrica abstracta proporciona una interfaz para crear todos los productos disponibles (una familia de objetos). Una fábrica abstracta normalmente tiene múltiples implementaciones. Cada uno de ellos se encarga de crear productos de una de las familias. Nuestra estrategia militar incluiría una fábrica abstracta que crea soldados de infantería, arqueros y jinetes abstractos, así como implementaciones de esta fábrica. Por ejemplo, una fábrica que crea legionarios romanos y una fábrica que crea soldados cartagineses. La abstracción es el principio rector más importante de este patrón. Los clientes de la fábrica trabajan con la fábrica y sus productos solo a través de interfaces abstractas. Como resultado, no tienes que pensar en qué soldados se están creando actualmente. En cambio, pasa esta responsabilidad a alguna implementación concreta de la fábrica abstracta.

Sigamos automatizando nuestra cafetería

En la última lección, estudiamos el patrón del método de fábrica. Lo usamos para expandir nuestro negocio de café y abrir varias ubicaciones nuevas. Hoy continuaremos modernizando nuestro negocio. Usando el patrón de fábrica abstracto, sentaremos las bases para una nueva aplicación de escritorio para pedir café en línea. Al escribir una aplicación de escritorio, siempre debemos pensar en el soporte multiplataforma. Nuestra aplicación debe funcionar tanto en macOS como en Windows (spoiler: el soporte para Linux se deja para que lo implementes como tarea). ¿Cómo será nuestra aplicación? Bastante simple: será un formulario que consistirá en un campo de texto, un campo de selección y un botón. Si tiene experiencia en el uso de diferentes sistemas operativos, seguramente habrá notado que los botones en Windows se representan de manera diferente a los de una Mac. Como todo lo demás... Bueno, comencemos.
  • botones
  • campos de texto
  • campos de selección
Descargo de responsabilidad: en cada interfaz, podríamos definir métodos como onClick, onValueChangedo onInputChanged. En otras palabras, podríamos definir métodos que nos permitan manejar varios eventos (presionar un botón, ingresar texto, seleccionar un valor en un cuadro de selección). Todo esto se omite deliberadamente aquí para no sobrecargar el ejemplo y hacerlo más claro a medida que estudiamos el patrón de fábrica. Definamos interfaces abstractas para nuestros productos:

public interface Button {}
public interface Select {}
public interface TextField {}
Para cada sistema operativo, debemos crear elementos de interfaz al estilo del sistema operativo. Estaremos escribiendo código para Windows y MacOS. Vamos a crear implementaciones para Windows:

public class WindowsButton implements Button {
}

public class WindowsSelect implements Select {
}

public class WindowsTextField implements TextField {
}
Ahora hacemos lo mismo para MacOS:

public class MacButton implements Button {
}

public class MacSelect implements Select {
}

public class MacTextField implements TextField {
}
Excelente. Ahora podemos pasar a nuestra fábrica de resúmenes, que creará todos los tipos de productos de resúmenes disponibles:

public interface GUIFactory {

    Button createButton();
    TextField createTextField();
    Select createSelect();

}
Magnífico. Como puede ver, no hemos hecho nada complicado todavía. Todo lo que sigue es también sencillo. Por analogía con los productos, creamos varias implementaciones de fábrica para cada sistema operativo. Comencemos con Windows:

public class WindowsGUIFactory implements GUIFactory {
    public WindowsGUIFactory() {
        System.out.println("Creating GUIFactory for Windows OS");
    }

    public Button createButton() {
        System.out.println("Creating Button for Windows OS");
        return new WindowsButton();
    }

    public TextField createTextField() {
        System.out.println("Creating TextField for Windows OS");
        return new WindowsTextField();
    }

    public Select createSelect() {
        System.out.println("Creating Select for Windows OS");
        return new WindowsSelect();
    }
}
Hemos agregado algunos resultados de la consola dentro de los métodos y el constructor para ilustrar mejor lo que está sucediendo. Ahora para macOS:

public class MacGUIFactory implements GUIFactory {
    public MacGUIFactory() {
        System.out.println("Creating GUIFactory for macOS");
    }

    @Override
    public Button createButton() {
        System.out.println("Creating Button for macOS");
        return new MacButton();
    }

    @Override
    public TextField createTextField() {
        System.out.println("Creating TextField for macOS");
        return new MacTextField();
    }

    @Override
    public Select createSelect() {
        System.out.println("Creating Select for macOS");
        return new MacSelect();
    }
}
Tenga en cuenta que cada firma de método indica que el método devuelve un tipo abstracto. Pero dentro de los métodos, estamos creando implementaciones específicas de los productos. Este es el único lugar donde controlamos la creación de instancias específicas. Ahora es el momento de escribir una clase para el formulario. Esta es una clase de Java cuyos campos son elementos de la interfaz:

public class CoffeeOrderForm {
    private final TextField customerNameTextField;
    private final Select coffeeTypeSelect;
    private final Button orderButton;

    public CoffeeOrderForm(GUIFactory factory) {
        System.out.println("Creating coffee order form");
        customerNameTextField = factory.createTextField();
        coffeeTypeSelect = factory.createSelect();
        orderButton = factory.createButton();
    }
}
Una fábrica abstracta que crea elementos de interfaz se pasa al constructor del formulario. Pasaremos la implementación de fábrica necesaria al constructor para crear elementos de interfaz para un sistema operativo en particular.

public class Application {
    private CoffeeOrderForm coffeeOrderForm;

    public void drawCoffeeOrderForm() {
        // Determine the name of the operating system through System.getProperty()
        String osName = System.getProperty("os.name").toLowerCase();
        GUIFactory guiFactory;

        if (osName.startsWith("win")) { // For Windows
            guiFactory = new WindowsGUIFactory();
        } else if (osName.startsWith("mac")) { // For Mac
            guiFactory = new MacGUIFactory();
        } else {
            System.out.println("Unknown OS. Unable to draw form :(");
            return;
        }
        coffeeOrderForm = new CoffeeOrderForm(guiFactory);
    }

    public static void main(String[] args) {
        Application application = new Application();
        application.drawCoffeeOrderForm();
    }
}
Si ejecutamos la aplicación en Windows, obtenemos el siguiente resultado:

Creating GUIFactory for Windows OS
Creating coffee order form
Creating TextField for Windows OS
Creating Select for Windows OS
Creating Button for Windows OS
En una Mac, la salida será la siguiente:

Creating GUIFactory for macOS
Creating coffee order form
Creating TextField for macOS
Creating Select for macOS
Creating Button for macOS
En Linux:

Unknown OS. Unable to draw form :( 
Y ahora resumimos. Escribimos el esqueleto de una aplicación basada en GUI en la que los elementos de la interfaz se crean específicamente para el sistema operativo relevante. Repetiremos de manera concisa lo que hemos creado:
  • Una familia de productos que consta de un campo de entrada, un campo de selección y un botón.
  • Diferentes implementaciones de la familia de productos para Windows y macOS.
  • Una fábrica abstracta que define una interfaz para crear nuestros productos.
  • Dos implementaciones de nuestra fábrica, cada una responsable de crear una familia específica de productos.
  • Un formulario (una clase de Java) cuyos campos son elementos de interfaz abstractos que se inicializan con los valores necesarios en el constructor utilizando una fábrica abstracta.
  • Clase de aplicación Dentro de esta clase, creamos un formulario, pasando la implementación de fábrica deseada a su constructor.
El resultado es que implementamos el patrón de fábrica abstracto.

Fábrica abstracta: cómo usar

Una fábrica abstracta es un patrón de diseño para administrar la creación de varias familias de productos sin estar vinculado a clases de productos concretas. Al usar este patrón, debe:
  1. Definir familias de productos. Supongamos que tenemos dos de ellos:
    • SpecificProductA1,SpecificProductB1
    • SpecificProductA2,SpecificProductB2
  2. Para cada producto dentro de la familia, defina una clase abstracta (interfaz). En nuestro caso, tenemos:
    • ProductA
    • ProductB
  3. Dentro de cada familia de productos, cada producto debe implementar la interfaz definida en el paso 2.
  4. Cree una fábrica abstracta, con métodos para crear cada producto definidos en el paso 2. En nuestro caso, estos métodos serán:
    • ProductA createProductA();
    • ProductB createProductB();
  5. Cree implementaciones de fábrica abstractas para que cada implementación controle la creación de productos de una sola familia. Para hacer esto, dentro de cada implementación de la fábrica abstracta, debe implementar todos los métodos de creación para que creen y devuelvan implementaciones de productos específicas.
El siguiente diagrama UML ilustra las instrucciones descritas anteriormente: Patrones de diseño: Fábrica abstracta - 3Ahora escribiremos código de acuerdo con estas instrucciones:

    // Define common product interfaces
    public interface ProductA {}
    public interface ProductB {}

    // Create various implementations (families) of our products
    public class SpecificProductA1 implements ProductA {}
    public class SpecificProductB1 implements ProductB {}

    public class SpecificProductA2 implements ProductA {}
    public class SpecificProductB2 implements ProductB {}

    // Create an abstract factory
    public interface AbstractFactory {
        ProductA createProductA();
        ProductB createProductB();
    }

    // Implement the abstract factory in order to create products in family 1
    public class SpecificFactory1 implements AbstractFactory {

        @Override
        public ProductA createProductA() {
            return new SpecificProductA1();
        }

        @Override
        public ProductB createProductB() {
            return new SpecificProductB1();
        }
    }

    // Implement the abstract factory in order to create products in family 2
    public class SpecificFactory2 implements AbstractFactory {

        @Override
        public ProductA createProductA() {
            return new SpecificProductA2();
        }

        @Override
        public ProductB createProductB() {
            return new SpecificProductB2();
        }
    }

Tarea

Para reforzar el material, puedes hacer 2 cosas:
  1. Refine la aplicación de pedido de café para que también funcione en Linux.
  2. Crea tu propia fábrica abstracta para producir unidades involucradas en cualquier estrategia militar. Esta puede ser una estrategia militar histórica que involucre ejércitos reales o una fantasía con orcos, gnomos y elfos. Lo importante es elegir algo que te interese. ¡Sea creativo, imprima mensajes en la consola y disfrute aprendiendo sobre patrones!