JavaRush /Blog Java /Random-ES /Patrones de diseño: AbstractFactory

Patrones de diseño: AbstractFactory

Publicado en el grupo Random-ES
¡Hola! Hoy continuaremos estudiando patrones de diseño y hablando sobre la fábrica abstracta . Patrones de diseño: AbstractFactory - 1Qué haremos durante la conferencia:
  • Analicemos qué es una fábrica abstracta y qué problema resuelve este patrón;
  • crearemos el marco de una aplicación multiplataforma para pedir café con una interfaz de usuario;
  • Estudiemos las instrucciones para usar este patrón con un diagrama y código;
  • Como beneficio adicional, en la conferencia hay un huevo de Pascua escondido, gracias al cual aprenderás a determinar el nombre del sistema operativo usando Java y, dependiendo del resultado, realizar una u otra acción.
Para comprender completamente este patrón, es necesario tener un buen conocimiento de los siguientes temas:
  • herencia en Java;
  • Clases y métodos abstractos en Java.

¿Qué problemas resuelve el patrón abstracto de fábrica?

La fábrica abstracta, como todos los patrones de fábrica, nos ayuda a organizar correctamente la creación de nuevos objetos. Con su ayuda gestionamos la "liberación" de varias familias de objetos interconectados. Varias familias de objetos interrelacionados... ¿Qué es? No te preocupes: en la práctica todo es más sencillo de lo que parece. Comencemos con lo que podría ser una familia de objetos relacionados. Supongamos que estamos desarrollando una estrategia con usted y que contiene varias unidades de combate:
  • infantería;
  • caballería;
  • arqueros.
Estos tipos de unidades de combate están relacionados entre sí porque sirven en el mismo ejército. Podemos decir que las categorías enumeradas anteriormente son una familia de objetos interrelacionados. Eso está solucionado. Pero el patrón abstracto de fábrica se utiliza para organizar la creación de varias familias de objetos interconectados. Aquí tampoco hay nada complicado. Sigamos el ejemplo con la estrategia. Suelen tener varios bandos opuestos diferentes. Las unidades de combate de diferentes bandos pueden diferir significativamente en apariencia. Los soldados de infantería, jinetes y arqueros del ejército romano no son los mismos que los soldados de infantería, jinetes y arqueros de los vikingos. En el marco de la estrategia, los soldados de diferentes ejércitos son diferentes familias de objetos interconectados. Sería curioso que, por error de un programador, un soldado con uniforme francés de la época de Napoleón, con un mosquete en ristre, se paseara entre la infantería romana. Para resolver tal problema se necesita el patrón de diseño de fábrica abstracto. No, no los problemas de la vergüenza de viajar en el tiempo, sino la creación de varios grupos de objetos interconectados. Una fábrica abstracta proporciona una interfaz para crear todos los productos existentes (objetos familiares). Una fábrica abstracta suele tener múltiples implementaciones. Cada uno de ellos es responsable de crear productos de una de las variaciones. Como parte de la estrategia, tendríamos una fábrica abstracta que crea infantería, arqueros y caballería abstractos, así como implementaciones de esta fábrica. Una fábrica que crea legionarios romanos y, por ejemplo, una fábrica que crea guerreros cartagineses. La abstracción es el principio más importante de este patrón. Los clientes de fábrica trabajan con él y con productos sólo a través de interfaces abstractas. Por lo tanto, no tenemos que pensar en qué tipo de guerreros estamos creando actualmente, sino transferir esta responsabilidad a alguna implementación específica de la fábrica abstracta.

Seguimos automatizando la cafetería.

En la última conferencia estudiamos el patrón del método de fábrica, con la ayuda del cual pudimos expandir el negocio del café y abrir varios puntos nuevos de venta de café. Hoy continuaremos nuestro trabajo para modernizar nuestro negocio. Utilizando el patrón abstracto de fábrica, sentaremos las bases para una nueva aplicación de escritorio para pedir café en línea. Cuando escribimos una aplicación para escritorio, siempre debemos pensar en multiplataforma. Nuestra aplicación debería funcionar tanto en macOS como en Windows (spoiler: Linux te lo dejaremos como tarea). ¿Cómo será nuestra aplicación? Muy simple: este será un formulario que constará de 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 en Windows los botones se representan de manera diferente que en Mac. Como todo... Entonces, comencemos. En el papel de familias de productos, como probablemente ya habrás entendido, tendremos elementos de la interfaz gráfica:
  • botones;
  • campos de texto;
  • campos para la selección.
Descargo de responsabilidad. Dentro de cada interfaz podríamos definir métodos como onClick, onValueChangedo onInputChanged. Aquellos. métodos que nos permitirán manejar varios eventos (hacer clic en un botón, ingresar texto, seleccionar un valor en un cuadro de selección). Todo esto se omite deliberadamente para no sobrecargar el ejemplo y hacerlo más visual para estudiar 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 de ese sistema operativo. Escribimos para Windows y MacOS. Creemos implementaciones para Windows:
public class WindowsButton implements Button {
}

public class WindowsSelect implements Select {
}

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

public class MacSelect implements Select {
}

public class MacTextField implements TextField {
}
Excelente. Ahora podemos iniciar nuestra fábrica abstracta, que creará todos los tipos de productos abstractos existentes:
public interface GUIFactory {

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

}
Perfecto. Como puedes ver, nada complicado hasta el momento. Entonces todo es igual de sencillo. Por analogía con los productos, creamos diferentes implementaciones de nuestra fábrica para cada sistema operativo. Comencemos con Windows:
public class WindowsGUIFactory implements GUIFactory {
    public WindowsGUIFactory() {
        System.out.println("Creating gui factory 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();
    }
}
Se ha agregado la salida de la consola dentro de los métodos y constructores para demostrar aún más cómo funciona. Ahora para macOS:
public class MacGUIFactory implements GUIFactory {
    public MacGUIFactory() {
        System.out.println("Creating gui factory 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();
    }
}
Nota: cada método, según su firma, devuelve un tipo abstracto. Pero dentro del método creamos una implementación concreta del producto. Este es el único lugar donde controlamos la creación de instancias específicas. Ahora es el momento de escribir la clase de formulario. Esta es una clase Java cuyos campos son elementos de la interfaz:
public class OrderCoffeeForm {
    private final TextField customerNameTextField;
    private final Select coffeTypeSelect;
    private final Button orderButton;

    public OrderCoffeeForm(GUIFactory factory) {
        System.out.println("Creating order coffee form");
        customerNameTextField = factory.createTextField();
        coffeTypeSelect = factory.createSelect();
        orderButton = factory.createButton();
    }
}
Se pasa una fábrica abstracta al constructor del formulario, que crea elementos de interfaz. Pasaremos la implementación de fábrica requerida al constructor para que podamos crear elementos de interfaz para un sistema operativo en particular.
public class Application {
    private OrderCoffeeForm orderCoffeeForm;

    public void drawOrderCoffeeForm() {
        // Определим Nombre операционной системы, получив significado системной проперти через System.getProperty
        String osName = System.getProperty("os.name").toLowerCase();
        GUIFactory guiFactory;

        if (osName.startsWith("win")) { // Для windows
            guiFactory = new WindowsGUIFactory();
        } else if (osName.startsWith("mac")) { // Для mac
            guiFactory = new MacGUIFactory();
        } else {
            System.out.println("Unknown OS, can't draw form :( ");
            return;
        }
        orderCoffeeForm = new OrderCoffeeForm(guiFactory);
    }

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

Creating gui factory for Windows OS
Creating order coffee form
Creating TextField for Windows OS
Creating Select for Windows OS
Creating Button for Windows OS
En una Mac, el resultado será el siguiente:

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

Unknown OS, can't draw form :( 
Bueno, tú y yo sacamos la siguiente conclusión. Escribimos un marco para una aplicación GUI que crea exactamente aquellos elementos de interfaz que son apropiados para un sistema operativo determinado. Repitamos brevemente lo que creamos:
  • Familia de productos: campo de entrada, campo de selección y botón.
  • Varias implementaciones de esta familia de productos, para Windows y macOS.
  • Una fábrica abstracta, dentro de la cual definimos la interfaz para crear nuestros productos.
  • Dos implementaciones de nuestra fábrica, cada una de las cuales se encarga de crear una familia de productos específica.
  • Un formulario, una clase Java cuyos campos son elementos abstractos de la interfaz que se inicializan en el constructor con los valores requeridos usando una fábrica abstracta.
  • Clase de aplicación. Dentro de él, creamos un formulario con el que pasamos la implementación requerida de nuestra fábrica al constructor.
Total: hemos implementado el patrón abstracto de fábrica.

Fábrica abstracta: instrucciones de uso

Abstract Factory es un patrón de diseño para gestionar la creación de diferentes familias de productos sin estar vinculado a clases de productos específicas. Al utilizar esta plantilla, debe:
  1. Definir las propias 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 es:
    • 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 de creación para cada producto definidos en el paso 2. En nuestro caso, estos métodos serán:
    • ProductA createProductA();
    • ProductB createProductB();
  5. Cree implementaciones de la fábrica abstracta para que cada implementación controle la creación de productos de la misma familia. Para hacer esto, dentro de cada implementación de la fábrica abstracta, es necesario implementar todos los métodos de creación, de modo que se creen implementaciones concretas de productos y se devuelvan dentro de ellos.
A continuación se muestra un diagrama UML que ilustra las instrucciones descritas anteriormente: Patrones de diseño: AbstractFactory - 3Ahora escribamos el código para esta instrucción:
// Определим общие интерфейсы продуктов
public interface ProductA {}
public interface ProductB {}

// Создадим различные реализации (семейства) наших продуктов
public class SpecificProductA1 implements ProductA {}
public class SpecificProductB1 implements ProductB {}

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

// Создадим абстрактную фабрику
public interface AbstractFactory {
    ProductA createProductA();
    ProductB createProductB();
}

// Создадим реализацию абстрактной фабрики для создания продуктов семейства 1
public class SpecificFactory1 implements AbstractFactory {

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

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

// Создадим реализацию абстрактной фабрики для создания продуктов семейства 1
public class SpecificFactory2 implements AbstractFactory {

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

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

Tarea

Para consolidar el material puedes hacer 2 cosas:
  1. Mejorar la aplicación de pedido de café para que funcione en Linux.
  2. Crea tu propia fábrica abstracta para producir unidades de cualquier estrategia. Puede ser una estrategia histórica con ejércitos reales o una fantasía con orcos, enanos y elfos. Lo principal es que te resulte interesante. ¡Sea creativo, publique pines en la consola y diviértase aprendiendo los patrones!
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION