Abstract factory pattern
Also known as Abstract Factory
intention
Abstract factory pattern is a creative design pattern, which can create a series of related objects without specifying their specific classes.
problem
Suppose you are developing a furniture store simulator. Your code includes classes that represent:
1. A range of related products, such as Chair, Sofa and Coffee table Table .
2. Different variants of series products. For example, you can use Modern, Victorian, Art deco Deco and other styles generate chairs, sofas and coffee tables.
You need to try to generate each furniture object separately in order to ensure its consistent style. Customers will not be happy if they receive different styles of furniture.
The modern sofa doesn't match the Victorian chair.
In addition, you don't want to modify existing code when adding new products or styles. Furniture suppliers update the product catalogue very frequently. You don't want to modify the core code every time.
Solution
First, the abstract factory pattern suggests explicitly declaring interfaces (such as chairs, sofas, or coffee tables) for each product in the family. Then, make sure that all product variants inherit these interfaces. For example, all styles of chairs implement chair interface; All styles of coffee tables implement the coffee table interface, and so on.
All variants of an object must be placed in the same hierarchy.
Next, we need to declare an abstract factory -- an interface that contains all the product construction methods in the family. For example, create Chair, create Sofa create sofa and create Coffee Table create a coffee table. These methods must return abstract product types, that is, the interfaces we extracted earlier: chairs, sofas, coffee tables, and so on.
Each specific factory class corresponds to a specific product variant.
So how to deal with product variants? For each variant of the series, we will create different factory classes based on the abstract factory interface. Each factory class can only return products of a specific category, for example, Modern furniture factory Furniture Factory can only create Modern chairs Chair, Modern sofa Sofa and Modern coffee table Coffee Table object.
Client code can call factory and product classes through corresponding abstract interfaces. Without modifying the actual client code, you can change the factory class passed to the client and the product variants received by the client code.
The client does not need to know the specific class information of the factory it calls.
Suppose the client wants to create a chair in the factory. The client does not need to know the factory class, nor does it need to care about the type of chair created by the factory class. Whether it is a modern style or a Victorian style chair, there is no difference for the client. It only needs to call the abstract chair interface. In this way, the client only needs to know that the chair implements sit in some way That's enough. In addition, no matter what chair variant the factory returns, it will be consistent with the style of the sofa or coffee table created by the same factory object.
One last note: if the client only touches the abstract interface, who will create the actual factory object? Typically, an application creates a specific factory object during the initialization phase. Before that, the application must select the factory category according to the configuration file or environment settings.
Abstract factory pattern structure
Pseudo code
In the following example, by applying the abstract factory pattern, the client code can create cross platform UI elements without coupling with specific UI classes, and ensure that the created elements match the specified operating system.
Cross platform UI class example.
The functions of the same UI elements in cross platform applications are similar, but their appearance is different under different operating systems. In addition, you need to ensure that the UI elements are consistent with the current operating system style. You must not want to display macOS controls in applications running under Windows.
The abstract factory interface declares a series of construction methods that can be called by client code to generate UI elements of different styles. Each specific factory corresponds to a specific operating system and is responsible for generating UI elements that conform to the style of the operating system.
It works as follows: detect the current operating system after the application starts. Based on this information, the application creates a factory object through a class corresponding to the operating system. The rest of the code uses the factory object to create UI elements. This avoids generating elements of the wrong type.
Using this method, the client code only needs to call the abstract interface without knowing the specific factory classes and UI elements. In addition, the client code also supports the addition of new factories or UI elements in the future.
This way, you don't have to modify the client code every time you add a new UI element variant to the application. You just need to create a factory class that can generate these UI elements, and then slightly modify the initial code of the application to select the appropriate factory class.
// The abstract factory interface declares a set of methods that can return different abstract products. These products belong to the same family // And relevant in high-level themes or concepts. Products of the same series can usually be used together. Series production // The product can have multiple variants, but products with different variants cannot be used together. interface GUIFactory is method createButton():Button method createCheckbox():Checkbox // Specific factories can generate a series of products belonging to the same variant. The factory will ensure that the products they create can match each other // use. The concrete factory method signature will return an abstract product, but the concrete product will be modified inside the method // Row instantiation. class WinFactory implements GUIFactory is method createButton():Button is return new WinButton() method createCheckbox():Checkbox is return new WinCheckbox() // Each specific factory will contain a corresponding product variant. class MacFactory implements GUIFactory is method createButton():Button is return new MacButton() method createCheckbox():Checkbox is return new MacCheckbox() // A specific product in the family must have a basic interface. All product variants must implement this interface. interface Button is method paint() // Specific products are created by corresponding specific factories. class WinButton implements Button is method paint() is // Render button according to Windows style. class MacButton implements Button is method paint() is // Render button according to macOS style // This is the basic interface of another product. All products can interact, but only products with the same specific variants // Products can interact correctly. interface Checkbox is method paint() class WinCheckbox implements Checkbox is method paint() is // Render to Windows style check box. class MacCheckbox implements Checkbox is method paint() is // Render according to macOS style check box. // Client code uses factories only through abstract types (GUIFactory, Button, and Checkbox) // And products. This allows you to pass it to the client code without modifying any factory or product subclasses. class Application is private field factory: GUIFactory private field button: Button constructor Application(factory: GUIFactory) is this.factory = factory method createUI() is this.button = factory.createButton() method paint() is button.paint() // The program selects the plant type according to the current configuration or environment settings, and creates the plant at run time (usually at the beginning) // Initial stage). class ApplicationConfigurator is method main() is config = readApplicationConfigFile() if (config.OS == "Windows") then factory = new WinFactory() else if (config.OS == "Mac") then factory = new MacFactory() else throw new Exception("Wrong! Unknown operating system.") Application app = new Application(factory)
The abstract factory pattern is suitable for application scenarios
-
If the code needs to interact with multiple related products of different series, but you don't want the code to be built based on the specific classes of the product because you can't get the relevant information in advance, or for the sake of future scalability, in this case, you can use the abstract factory.
-
The abstract factory provides you with an interface that can be used to create objects for each series of products. As long as the code creates objects through this interface, you will not generate products that are inconsistent with the product types that the application has generated.
-
If you have a class based on a set of abstract methods and its main function becomes unclear, consider using the abstract factory pattern in this case.
-
In a well-designed program, each class is responsible for only one thing. If a class interacts with multiple types of products, you can consider extracting factory methods into independent factory classes or abstract factory classes with complete functions.
Implementation mode
1. Draw the matrix with different product types and product variants as dimensions.
2. Declare abstract product interfaces for all products. Then let the specific classes implement all these interfaces.
3. Declare the abstract factory interface, and provide a set of construction methods for all abstract products in the interface.
4. Implement a specific factory class for each product variant.
5. Develop initialization code in the application. This code initializes a specific factory class according to the application configuration or the current environment. Then pass the factory object to all classes that need to create products.
6. Find out all direct calls to the product constructor in the code and replace them with calls to the corresponding construction methods in the factory object.
Advantages and disadvantages of abstract factory pattern
advantage
- You can make sure that the products produced by the same factory match each other.
- You can avoid the coupling between the client and the specific product code.
- Single responsibility principle. You can extract the product generated code to the same location, making the code easy to maintain.
- Opening and closing principle. When introducing new product variants into your application, you do not need to modify the client code.
shortcoming - Since adopting this pattern requires introducing many interfaces and classes into the application, the code may be more complex than before.
Relationship with other modes
-
Factory method patterns are used at the beginning of many design work (simpler and more convenient to customize through subclasses), and then evolve to use abstract factory patterns, prototype patterns or generator patterns (more flexible but more complex).
-
The generator focuses on how to generate complex objects step by step. Abstract factories are dedicated to producing a series of related objects. The abstract factory will return the product immediately, and the generator allows you to perform some additional construction steps before obtaining the product.
-
Abstract factory patterns are usually based on a set of factory methods, but you can also use prototype patterns to generate methods for these classes.
-
When you only need to create objects for the client code hiding subsystem, you can use the abstract factory instead of the appearance mode.
-
You can use abstract factories with bridge patterns. This pattern collocation is the first mock exam if the bridge definition is only able to work with specific implementations. In this case, the abstract factory can encapsulate these relationships and hide their complexity from the client code.
-
Abstract factories, generators, and prototypes can all be implemented in singleton mode.
Using patterns in C + +
Complexity:**
Popularity:***
Usage example: abstract factory pattern is very common in C + + code. Many frameworks and libraries use it as a way to extend and customize their standard components.
Identification method: we can identify the pattern through the method -- it will return a factory object. Next, the factory will be used to create specific subcomponents.
Conceptual example
This example illustrates the structure of the abstract factory design pattern and focuses on answering the following questions:
- What classes does it consist of?
- What roles do these classes play?
- How do the elements in the schema relate to each other?
main.cc: concept example
/** * Each distinct product of a product family should have a base interface. All * variants of the product must implement this interface. */ class AbstractProductA { public: virtual ~AbstractProductA(){}; virtual std::string UsefulFunctionA() const = 0; }; /** * Concrete Products are created by corresponding Concrete Factories. */ class ConcreteProductA1 : public AbstractProductA { public: std::string UsefulFunctionA() const override { return "The result of the product A1."; } }; class ConcreteProductA2 : public AbstractProductA { std::string UsefulFunctionA() const override { return "The result of the product A2."; } }; /** * Here's the the base interface of another product. All products can interact * with each other, but proper interaction is possible only between products of * the same concrete variant. */ class AbstractProductB { /** * Product B is able to do its own thing... */ public: virtual ~AbstractProductB(){}; virtual std::string UsefulFunctionB() const = 0; /** * ...but it also can collaborate with the ProductA. * * The Abstract Factory makes sure that all products it creates are of the * same variant and thus, compatible. */ virtual std::string AnotherUsefulFunctionB(const AbstractProductA &collaborator) const = 0; }; /** * Concrete Products are created by corresponding Concrete Factories. */ class ConcreteProductB1 : public AbstractProductB { public: std::string UsefulFunctionB() const override { return "The result of the product B1."; } /** * The variant, Product B1, is only able to work correctly with the variant, * Product A1. Nevertheless, it accepts any instance of AbstractProductA as an * argument. */ std::string AnotherUsefulFunctionB(const AbstractProductA &collaborator) const override { const std::string result = collaborator.UsefulFunctionA(); return "The result of the B1 collaborating with ( " + result + " )"; } }; class ConcreteProductB2 : public AbstractProductB { public: std::string UsefulFunctionB() const override { return "The result of the product B2."; } /** * The variant, Product B2, is only able to work correctly with the variant, * Product A2. Nevertheless, it accepts any instance of AbstractProductA as an * argument. */ std::string AnotherUsefulFunctionB(const AbstractProductA &collaborator) const override { const std::string result = collaborator.UsefulFunctionA(); return "The result of the B2 collaborating with ( " + result + " )"; } }; /** * The Abstract Factory interface declares a set of methods that return * different abstract products. These products are called a family and are * related by a high-level theme or concept. Products of one family are usually * able to collaborate among themselves. A family of products may have several * variants, but the products of one variant are incompatible with products of * another. */ class AbstractFactory { public: virtual AbstractProductA *CreateProductA() const = 0; virtual AbstractProductB *CreateProductB() const = 0; }; /** * Concrete Factories produce a family of products that belong to a single * variant. The factory guarantees that resulting products are compatible. Note * that signatures of the Concrete Factory's methods return an abstract product, * while inside the method a concrete product is instantiated. */ class ConcreteFactory1 : public AbstractFactory { public: AbstractProductA *CreateProductA() const override { return new ConcreteProductA1(); } AbstractProductB *CreateProductB() const override { return new ConcreteProductB1(); } }; /** * Each Concrete Factory has a corresponding product variant. */ class ConcreteFactory2 : public AbstractFactory { public: AbstractProductA *CreateProductA() const override { return new ConcreteProductA2(); } AbstractProductB *CreateProductB() const override { return new ConcreteProductB2(); } }; /** * The client code works with factories and products only through abstract * types: AbstractFactory and AbstractProduct. This lets you pass any factory or * product subclass to the client code without breaking it. */ void ClientCode(const AbstractFactory &factory) { const AbstractProductA *product_a = factory.CreateProductA(); const AbstractProductB *product_b = factory.CreateProductB(); std::cout << product_b->UsefulFunctionB() << "\n"; std::cout << product_b->AnotherUsefulFunctionB(*product_a) << "\n"; delete product_a; delete product_b; } int main() { std::cout << "Client: Testing client code with the first factory type:\n"; ConcreteFactory1 *f1 = new ConcreteFactory1(); ClientCode(*f1); delete f1; std::cout << std::endl; std::cout << "Client: Testing the same client code with the second factory type:\n"; ConcreteFactory2 *f2 = new ConcreteFactory2(); ClientCode(*f2); delete f2; return 0; }
Output.txt: execution result
Client: Testing client code with the first factory type: The result of the product B1. The result of the B1 collaborating with the (The result of the product A1.) Client: Testing the same client code with the second factory type: The result of the product B2. The result of the B2 collaborating with the (The result of the product A2.)