In the previous article, we learned about the first design pattern called Factory Method Pattern. In this article, let’s learn about a new-creational design pattern called “The Abstract Factory Pattern”.

What is the Abstract Factory Pattern?

Similar to Factory Method Pattern, Abstract Factory Pattern is also a creational design pattern that is widely used nowadays, but it is more advanced than Factory Method Pattern in that it allows creating a super factory to create other factories.

To make things much easier to understand, let’s imagine Abstract Factory as a large factory that houses many small factories and those small ones also have many manufacturing facilities that allow creating different products.

Abstract Factory Pattern
Abstract Factory

Problem

Imagine that you’re creating an application for your furniture shop called FurnitureShopApp. Your code consists of several classes that represent:

  • A family of related products, say: Chair + Sofa + CoffeeTable.
  • Several variants of this family. For example, products Chair + Sofa + CoffeeTable are available in these variants: Modern, Victorian, ArtDeco.

Abstract Factory Pattern

However, you are continuously receiving negative feedback from your customers recently. The main reason for this is that the furniture products they received after ordering from your shop don’t match with others.

Abstract Factory Pattern
A Modern-style sofa doesn’t match Victorian-style chairs.

As the creator of the application, you think it’s time to do something to improve the current situation. What you really need to do first is to create individual furniture objects so that they match other objects of the same family. In addition, because the furniture suppliers of your shop update their product catalog very often, you don’t want to change existing code when adding new products or families of products to the program each time it happens.

Solution

Fortunately, there is a design pattern that can help you out with this problem called “The Abstract Factory Pattern”.

The first thing of this design pattern suggests is to explicitly declare interfaces for each distinct product of the product family (e.g., chair, sofa or coffee table). Then you can make all variants of products follow those interfaces. For example, all chair variants can implement the Chair interface; all coffee table variants can implement the CoffeeTable interface, and so on.

Abstract Factory Pattern

Next, we have to declare the Abstract Factory – an interface with a list of creation methods for all products that are part of the product family (for example, createChair, createSofa, and createCoffeeTable). These methods must return abstract product types represented by the interfaces we extracted previously: Chair, Sofa, CoffeeTable and so on.

So, how about the product variants? For each variant of a product family, we create a separate factory class based on the AbstractFactory interface. A factory is a class that returns products of a particular kind. For example, the ModernFactory can only create ModernChair, ModernSofa, and ModernCoffeeTable objects.

Now, we see that the client code has to work with both factories and products via their respective abstract interfaces. This lets you change the type of a factory that you pass to the client code, as well as the product variant that the client code receives, without breaking the actual client code.

Structure

The structure of the Factory Method Pattern consists of 5 important components: Abstract Factory, Concrete Factory, Abstract Product, Concrete Product, and Client.

the structure of Abstract Factory Pattern

  • Abstract Products declare interfaces for a set of distinct but related products which make up a product family.
  • Concrete Products are various implementations of abstract products, grouped by variants.
  • The Abstract Factory interface declares a set of methods for creating each of the abstract products.
  • Concrete Factories implement creation methods of the abstract factory. Each concrete factory corresponds to a specific variant of products and creates only those product variants.
  • The Client can work with any concrete factory/product variant, as long as it communicates with their objects via abstract interfaces.

Solution to the FurnitureStoreApp

That’s enough theories for today, let’s use the Factory Method Pattern to solve the current problem that our FurnitureStoreApp is facing.

First of all, we will create 3 distinct interfaces that represent Chair, Sofa, and CoffeeTable.

<?php
       interface Chair {
             public function hasLegs(): string;
             public function sitOn(): string;
       }

       interface Sofa {
             public function hasLegs(): string;
             public function sitOn(): string;
       }

       interface CoffeeTable {
             public function hasLegs(): string;
             public function putOn(): string;
       }
?>

Next, let’s declare distinct class following those interfaces to represent variants of the above products. For example, all chair variants can implement the Chair interface; all coffee table variants can implement the CoffeeTable interface, and so on.

<?php
       class VictorianChair implements Chair {
             public function hasLegs(): string {
                   return 'Victorian Chair has four long legs.'
             }

             public function sitOn(): string {
                   return 'Sit on Victorian Chair.';
             }

       }

       class ArtDecoChair implements Chair {
             public function hasLegs(): string {
                   return 'Art Deco Chair has four short legs.';
             }

             public function sitOn(): string {
                   return 'Sit on Art Deco Chair.';
             }
       }

       class ModernChair implements Chair {
             public function hasLegs(): string {
                   return 'Modern Chair has no legs.';
             }

             public function sitOn(): string {
                   return 'Sit on Modern Chair.';
             }
       }

       class VictorianSofa implements Sofa {
             public function hasLegs(): string {
                   return 'Victorian Sofa has four long legs.';
             }
             public function sitOn(): string {
                   return 'Sit on Victorian Sofa.';
             }
       }

       class ArtDecoSofa implements Sofa {
             public function hasLegs(): string {
                   return 'Art Deco Sofa has four short legs.';
             }

             public function sitOn(): string {
                   return 'Sit on Art Deco Sofa.';
             }
       }

       class ModernSofa implements Sofa {
             public function hasLegs(): string {
                   return 'Modern Sofa has no legs.';
             }

             public function sitOn(): string {
                   return 'Sit on Modern Sofa.';
             }
       }

       class VictorianCoffeeTable implements CoffeeTable {
             public function hasLegs(): string {
                   return 'Victorian Coffee Table has four long legs.';
             }

             public function putOn(): string {
                   return 'Put something on Victorian Coffee Table.';
             }
       }

       class ArtDecoCoffeeTable implements CoffeeTable {
             public function hasLegs(): string {
                   return 'Art Deco Coffee Table has only one leg.';
             }

             public function putOn(): string {
                   return 'Put something on Art Deco Table.';
             }
       }

       class ModernCoffeeTable implements CoffeeTable {
             public function hasLegs(): string {
                   return 'Modern Coffee Table has four short legs.';
             }

             public function putOn(): string {
                   return 'Put something on Modern Coffee Table.';
             }
       }
?>

Then, we will declare the FurnitureFactory – an interface with a list of creation methods for all products that are part of the product family. These methods will return abstract product types represented by the interfaces we extracted previously: Chair, Sofa, CoffeeTable and so on.

<?php
       interface FurnitureFactory {
             public function createChair(): Chair;
             public function createSofa(): Sofa;
             public function createCoffeeTable(): CoffeeTable;
       }
?>

After that, let’s create separate factory classes based on the FurnitureFactory interface. Each of them will have to override the factory methods of the FurnitureFactory interface and return different products of a particular variant.

<?php
       class VictorianFurnitureFactory {
             public function createChair(): Chair {
                   return new VictorianChair();
             }

             public function createSofa(): Sofa {
                   return new VictorianSofa();
             }

             public function createCoffeeTable(): CoffeeTable {
                   return new VictorianCoffeeTable();
             }
       }

       class ArtDecoFactory {
             public function createChair(): Chair {
                   return new ArtDecoChair();
             }

             public function createSofa(): Sofa {
                   return new ArtDecoSofa();
             }

             public function createCoffeeTable(): CoffeeTable {
                   return new ArtDecoCoffeeTable();
             }

       }

       class ModernFactory {
             public function createChair(): Chair {
                   return new ModernChair();
             }

             public function createSofa(): Sofa {
                   return new ModernSofa();
             }

             public function createCoffeeTable(): CoffeeTable {
                   return new ModernCoffeeTable();
             }
       }

?>

That’s it! We have completed setting up the Abstract Factory Method for our FurnitureStoreApp. Let’s execute the code below to see the final result:

function clientCode(FurnitureFactory $factory)
{
      $chair = $factory->createChair();
      $sofa = $factory->createSofa();
      $coffeeTable = $factory->createCoffeeTable();

      echo $chair->hasLegs() . "\n";
      echo $chair->sitOn() . "\n";
      echo $sofa->hasLegs() . "\n";
      echo $sofa->sitOn() . "\n";
      echo $coffeeTable->hasLegs() . "\n";
      echo $coffeeTable->sitOn() . "\n";
}

      echo "* Victorian Furniture Factory\n";
      clientCode(new VictorianFurnitureFactory());

      echo "\n";

      echo "* Art Deco Factory\n";
      clientCode(new ArtDecoFactory());

      echo "\n";

      echo "* Modern Factory\n";
      clientCode(new ModernFactory());

Result:

* Victorian Furniture Factory
Victorian Chair has four long legs.
Sit on Victorian Chair.
Victorian Sofa has four long legs.
Sit on Victorian Sofa.
Victorian Coffee Table has four long legs.
Put something on Victorian Coffee Table.

* Art Deco Factory
Art Deco Chair has four short legs.
Sit on Art Deco Chair.
Art Deco Sofa has four short legs.
Sit on Art Deco Sofa.
Art Deco Coffee Table has only one leg.
Put something on Art Deco Table.

* Modern Factory
Modern Chair has no legs.
Sit on Modern Chair.
Modern Sofa has no legs.
Sit on Modern Sofa.
Modern Coffee Table has four short legs.
Put something on Modern Coffee Table.

Now, we can see that the client code has to work with both factories and products via their respective abstract interfaces. From now on, we can easily change the type of a factory that is passed to the client code, as well as the product variant that the client code receives, without breaking the actual existing client code.

The Pros and Cons of the Abstract Factory Pattern

Pros

  • Abstract Factory Pattern ensures that the products you’re getting from a factory are compatible with each other.
  • Factory Method Pattern helps prevent tight coupling between the creator and concrete products.
  • With Factory Method Pattern, you can centralize the product creation code into one place in the program, making the code easier to support and interact with.
  • By using Factory Method Pattern, you can introduce new types of products into the program without breaking existing client code.

Cons

  • The code may become much more complicated since you will need to introduce a lot of new subclasses to implement the pattern.

Conclusion

In this article, we have learned a lot about the Abstract Factory Pattern. I hope that this design pattern will be a perfect solution to one of your software development problems sometime in the future.

In the next article of this series, we will learn about another creational design pattern called Abstract Factory Pattern. Stay tuned!

Leave a Reply