JavaRush /Java Blog /Random EN /Design Patterns: AbstractFactory

Design Patterns: AbstractFactory

Published in the Random EN group
Hello! Today we will continue to study design patterns and talk about the abstract factory . Design Patterns: AbstractFactory - 1What we will do during the lecture:
  • Let's discuss what an abstract factory is and what problem this pattern solves;
  • we will create the framework of a cross-platform application for ordering coffee with a user interface;
  • Let's study the instructions for using this pattern with a diagram and code;
  • As a bonus, there is an Easter egg hidden in the lecture, thanks to which you will learn to determine the name of the operating system using Java and, depending on the result, perform one or another action.
To fully understand this pattern, you need to have a good understanding of the following topics:
  • inheritance in Java;
  • abstract classes and methods in Java.

What problems does the abstract factory pattern solve?

The abstract factory, like all factory patterns, helps us organize the creation of new objects correctly. With its help, we manage the “release” of various families of interconnected objects. Various families of interrelated objects...What is it? Don't worry: in practice everything is simpler than it might seem. Let's start with what might be a family of related objects? Suppose we are developing a strategy with you, and there are several combat units in it:
  • infantry;
  • cavalry;
  • archers.
These types of combat units are related to each other, because they serve in the same army. We can say that the categories listed above are a family of interrelated objects. That's sorted out. But the abstract factory pattern is used to organize the creation of various families of interconnected objects. Nothing complicated here either. Let's continue the example with strategy. They usually have several different opposing sides. Different sides' combat units may differ significantly in appearance. The foot soldiers, horsemen and archers of the Roman army are not the same as the foot soldiers, horsemen and archers of the Vikings. Within the framework of strategy, soldiers of different armies are different families of interconnected objects. It would be funny if, by a programmer’s mistake, a soldier in a French uniform from the time of Napoleon, with a musket at the ready, was walking around among the Roman infantry. It is to solve such a problem that the abstract factory design pattern is needed. No, not the problems of time travel embarrassment, but the creation of various groups of interconnected objects. An abstract factory provides an interface for creating all existing products (family objects). An abstract factory typically has multiple implementations. Each of them is responsible for creating products of one of the variations. As part of the strategy, we would have an abstract factory that creates abstract infantry, archers and cavalry, as well as implementations of this factory. A factory that creates Roman legionnaires and, for example, a factory that creates Carthaginian warriors. Abstraction is the most important principle of this pattern. Factory clients work with it and with products only through abstract interfaces. Therefore, we don’t have to think about what kind of warriors we are currently creating, but transfer this responsibility to some specific implementation of the abstract factory.

We continue to automate the coffee shop

In the last lecture, we studied the factory method pattern, with the help of which we were able to expand the coffee business and open several new coffee sales points. Today we will continue our work to modernize our business. Using the abstract factory pattern, we will lay the foundation for a new desktop application for ordering coffee online. When we write an application for the desktop, we should always think about cross-platform. Our application should work on both macOS and Windows (spoiler: Linux will be left for you as homework). What will our application look like? Quite simple: this will be a form that consists of a text field, a select field, and a button. If you have experience using different operating systems, you have definitely noticed that on Windows the buttons are rendered differently than on the Mac. Like everything else... So, let's begin. In the role of product families, as you probably already understood, we will have graphical interface elements:
  • buttons;
  • text fields;
  • fields for selection.
Disclaimer. Within each interface we could define methods like onClick, onValueChangedor onInputChanged. Those. methods that will allow us to handle various events (clicking a button, entering text, selecting a value in a select box). All this is deliberately omitted so as not to overload the example and to make it more visual for studying the factory pattern. Let's define abstract interfaces for our products:
public interface Button {}
public interface Select {}
public interface TextField {}
For each operating system, we must create interface elements in the style of that operating system. We write for Windows and MacOS. Let's create implementations for Windows:
public class WindowsButton implements Button {
}

public class WindowsSelect implements Select {
}

public class WindowsTextField implements TextField {
}
Now the same for MacOS:
public class MacButton implements Button {
}

public class MacSelect implements Select {
}

public class MacTextField implements TextField {
}
Great. Now we can start our abstract factory, which will create all existing abstract product types:
public interface GUIFactory {

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

}
Perfect. As you can see, nothing complicated so far. Then everything is just as simple. By analogy with products, we create different implementations of our factory for each OS. Let's start with 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();
    }
}
Console output inside methods and constructors has been added to further demonstrate how it works. Now for 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();
    }
}
Note: each method, according to its signature, returns an abstract type. But inside the method we create a concrete implementation of the product. This is the only place where we control the creation of specific instances. Now it's time to write the form class. This is a Java class whose fields are interface elements:
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();
    }
}
An abstract factory is passed to the form constructor, which creates interface elements. We will pass the required factory implementation to the constructor so that we can create interface elements for a particular OS.
public class Application {
    private OrderCoffeeForm orderCoffeeForm;

    public void drawOrderCoffeeForm() {
        // Определим Name операционной системы, получив meaning системной проперти через 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();
    }
}
If we run the application on Windows, we will get the following output:

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
On a Mac the output will be as follows:

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

Unknown OS, can't draw form :( 
Well, you and I draw the following conclusion. We wrote a framework for a GUI application that creates exactly those interface elements that are appropriate for a given OS. Let's repeat briefly what we created:
  • Product family: input field, selection field and button.
  • Various implementations of this family of products, for Windows and macOS.
  • An abstract factory, within which we defined the interface for creating our products.
  • Two implementations of our factory, each of which is responsible for creating a specific family of products.
  • A form, a Java class whose fields are abstract interface elements that are initialized in the constructor with the required values ​​using an abstract factory.
  • Application class. Inside it, we create a form with which we pass the required implementation of our factory to the constructor.
Total: we have implemented the abstract factory pattern.

Abstract Factory: instructions for use

Abstract Factory is a design pattern for managing the creation of different product families without being tied to specific product classes. When using this template, you must:
  1. Define the product families themselves. Let's assume we have two of them:
    • SpecificProductA1,SpecificProductB1
    • SpecificProductA2,SpecificProductB2
  2. For each product within the family, define an abstract class (interface). In our case it is:
    • ProductA
    • ProductB
  3. Within each product family, each product must implement the interface defined in step 2.
  4. Create an abstract factory, with create methods for each product defined in step 2. In our case, these methods will be:
    • ProductA createProductA();
    • ProductB createProductB();
  5. Create implementations of the abstract factory so that each implementation controls the creation of products of the same family. To do this, inside each implementation of the abstract factory, it is necessary to implement all the create methods, so that concrete implementations of products are created and returned inside them.
Below is a UML diagram that illustrates the instructions described above: Design Patterns: AbstractFactory - 3Now let’s write the code for this instruction:
// Определим общие интерфейсы продуктов
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();
    }
}

Homework

To consolidate the material you can do 2 things:
  1. Improve the coffee ordering application so that it works on Linux.
  2. Create your own abstract factory to produce units of any strategy. This can be either a historical strategy with real armies or a fantasy with orcs, dwarves and elves. The main thing is that you find it interesting. Get creative, post pins to the console, and have fun learning the patterns!
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION