Design Patterns – Abstract Factory

This Creational Pattern provides an interface to create families of related or dependent objects without specifying their concrete classes. This pattern could be used with other creational patterns, such as Factory Method, or could be implemented also using Prototype Pattern and a concrete factory could also be a singleton.

Wow, that’s so much information in the first paragraph, let’s try to simplify. Imagine that this pattern is a factory of object factories. (It’s getting better, but not enough yet)

This pattern isolates concrete classes, makes an easy change of product families but is difficult to add new types of products and it’s only useful when we have to group processes or objects


Intent

Provide an interface for creating families of related or dependent objects without specifying their concrete classes.


Diagram

The diagram above tell us that we have some actors here.

Client – A concrete object that receives our abstract factory and could call concrete products.

AbstractFactory – This is our factory of objects, in this case, we have two contracts.

AbstractProductX – These are our contracts that are used in AbstractFactory.

ConcreteFactoryX – Here are our implementations, our concrete factories and should give us our concrete Products.

ProductX – These are our concrete products and they implement our AbstractProductX.

* I’m calling here contracts because no matter if it’s an abstract class or an interface in this example.

Well, things are going more easily now and are making more sense. However, it’s not the end, let’s try to implement a more “real world example”.


Real world example

In this example, we are developing a system for a notebook factory. Each notebook must have some objects related, for now, let’s implement just memory, display, and processor. It’s obvious and I know that notebook should have more information, but not in our ACME company. Now we can have an idea of what we could do with this pattern.

Check the new diagram.

ACME Notebook Factory Example

In this diagram, we have our Client (ACME Factory that will consume all notebook factories)

So, as the diagram show us, we’ll have some notebooks here

  1. Notebook 1 with 8Gb Memory, Display 13 and I7 Processor
  2. Notebook 2 with 16Gb Memory, Display 13 and I7 Processor
  3. Notebook 3 with 32Gb Memory, Display 15 and I9 Processor

First let’s create right folders and files in our project, and it should be like this.

. designpatterns
.. src
... Creational
.... AbstractFactory
..... NotebookExample
...... Contracts
...... Displays
...... Factories
...... Memories
...... Processors
.. tests
... Creational
.... AbstractFactory
..... NotebookExample

Let’s code

As we have dependencies in our NotebookAbstractFactory, we’ll start creating our interfaces as MemoryInterface, DisplayInterface and ProcessorInterface on Contracts folder. Then we’ll be able to create our NotebookAbstractFactory.

DisplayInterface.php

<?php



declare(strict_types=1);


namespace DesignPatterns\Creational\AbstractFactory\NotebookExample\Contracts;



interface DisplayInterface
{

    public function getName(): string;
}

MemoryInterface.php

<?php



declare(strict_types=1);



namespace DesignPatterns\Creational\AbstractFactory\NotebookExample\Contracts;



interface MemoryInterface
{

    public function getName(): string;
}

ProcessorInterface.php

<?php



declare(strict_types=1);



namespace DesignPatterns\Creational\AbstractFactory\NotebookExample\Contracts;



interface ProcessorInterface

{

    public function getName() : string;
}

NotebookAbstractFactory.php

<?php

declare(strict_types=1);

namespace DesignPatterns\Creational\AbstractFactory\NotebookExample\Contracts;

interface NotebookAbstractFactory
{
    public function createMemory(): MemoryInterface;

    public function createDisplay(): DisplayInterface;

    public function createProcessor(): ProcessorInterface;
}

As the main part of our system is already implemented, we will continue with our displays in their respective folders.

Display13.php

<?php

declare(strict_types=1);

namespace DesignPatterns\Creational\AbstractFactory\NotebookExample\Displays;

use DesignPatterns\Creational\AbstractFactory\NotebookExample\Contracts\DisplayInterface;

class Display13 implements DisplayInterface
{
    public function getName(): string
    {
        return "13 Display";
    }
}

Display15.php

<?php

declare(strict_types=1);

namespace DesignPatterns\Creational\AbstractFactory\NotebookExample\Displays;

use DesignPatterns\Creational\AbstractFactory\NotebookExample\Contracts\DisplayInterface;

class Display15 implements DisplayInterface
{
    public function getName(): string
    {
        return "15 Display";
    }
}

We still need to implement memories and processors, we’ll create concrete memory classes in their respective folder according to these following examples:

Memory8Gb.php

<?php

declare(strict_types=1);

namespace DesignPatterns\Creational\AbstractFactory\NotebookExample\Memories;

use DesignPatterns\Creational\AbstractFactory\NotebookExample\Contracts\MemoryInterface;

class Memory8Gb implements MemoryInterface
{
    public function getName(): string
    {
        return "8Gb";
    }
}

Memory16Gb.php

<?php

declare(strict_types=1);

namespace DesignPatterns\Creational\AbstractFactory\NotebookExample\Memories;

use DesignPatterns\Creational\AbstractFactory\NotebookExample\Contracts\MemoryInterface;

class Memory16Gb implements MemoryInterface
{
    public function getName(): string
    {
        return "16Gb";
    }
}

Memory32Gb.php

<?php

declare(strict_types=1);

namespace DesignPatterns\Creational\AbstractFactory\NotebookExample\Memories;

use DesignPatterns\Creational\AbstractFactory\NotebookExample\Contracts\MemoryInterface;

class Memory32Gb implements MemoryInterface
{
    public function getName(): string
    {
        return "32Gb";
    }
}

We will continue as we made before, now working with concrete processors, remember to create inside Processors folder:

I7.php

<?php

declare(strict_types=1);

namespace DesignPatterns\Creational\AbstractFactory\NotebookExample\Processors;

use DesignPatterns\Creational\AbstractFactory\NotebookExample\Contracts\ProcessorInterface;

class I7 implements ProcessorInterface
{
    public function getName(): string
    {
        return "I7";
    }
}

I9.php

<?php

declare(strict_types=1);

namespace DesignPatterns\Creational\AbstractFactory\NotebookExample\Processors;

use DesignPatterns\Creational\AbstractFactory\NotebookExample\Contracts\ProcessorInterface;

class I9 implements ProcessorInterface
{
    public function getName(): string
    {
        return "I9";
    }
}

Last but not least, we’re almost done, let’s create our concrete factories that will be our notebook classes.

Notebook1.php

<?php

declare(strict_types=1);

namespace DesignPatterns\Creational\AbstractFactory\NotebookExample\Factories;

use DesignPatterns\Creational\AbstractFactory\NotebookExample\Contracts\DisplayInterface;
use DesignPatterns\Creational\AbstractFactory\NotebookExample\Contracts\MemoryInterface;
use DesignPatterns\Creational\AbstractFactory\NotebookExample\Contracts\NotebookAbstractFactory;
use DesignPatterns\Creational\AbstractFactory\NotebookExample\Contracts\ProcessorInterface;
use DesignPatterns\Creational\AbstractFactory\NotebookExample\Displays\Display13;
use DesignPatterns\Creational\AbstractFactory\NotebookExample\Memories\Memory8Gb;
use DesignPatterns\Creational\AbstractFactory\NotebookExample\Processors\I7;

class Notebook1 implements NotebookAbstractFactory
{
    public function createMemory(): MemoryInterface
    {
        return new Memory8Gb();
    }

    public function createDisplay(): DisplayInterface
    {
        return new Display13();
    }

    public function createProcessor(): ProcessorInterface
    {
        return new I7();
    }
}

Notebook2.php

<?php

declare(strict_types=1);

namespace DesignPatterns\Creational\AbstractFactory\NotebookExample\Factories;

use DesignPatterns\Creational\AbstractFactory\NotebookExample\Contracts\DisplayInterface;
use DesignPatterns\Creational\AbstractFactory\NotebookExample\Contracts\MemoryInterface;
use DesignPatterns\Creational\AbstractFactory\NotebookExample\Contracts\NotebookAbstractFactory;
use DesignPatterns\Creational\AbstractFactory\NotebookExample\Contracts\ProcessorInterface;
use DesignPatterns\Creational\AbstractFactory\NotebookExample\Displays\Display13;
use DesignPatterns\Creational\AbstractFactory\NotebookExample\Memories\Memory16Gb;
use DesignPatterns\Creational\AbstractFactory\NotebookExample\Processors\I7;

class Notebook2 implements NotebookAbstractFactory
{
    public function createMemory(): MemoryInterface
{
        return new Memory16Gb();
    }

    public function createDisplay(): DisplayInterface
    {
        return new Display13();
    }

    public function createProcessor(): ProcessorInterface
    {
        return new I7();
    }
}

Notebook3.php

<?php

declare(strict_types=1);

namespace DesignPatterns\Creational\AbstractFactory\NotebookExample\Factories;

use DesignPatterns\Creational\AbstractFactory\NotebookExample\Contracts\DisplayInterface;
use DesignPatterns\Creational\AbstractFactory\NotebookExample\Contracts\MemoryInterface;
use DesignPatterns\Creational\AbstractFactory\NotebookExample\Contracts\NotebookAbstractFactory;
use DesignPatterns\Creational\AbstractFactory\NotebookExample\Contracts\ProcessorInterface;
use DesignPatterns\Creational\AbstractFactory\NotebookExample\Displays\Display15;
use DesignPatterns\Creational\AbstractFactory\NotebookExample\Memories\Memory32Gb;
use DesignPatterns\Creational\AbstractFactory\NotebookExample\Processors\I9;

class Notebook3 implements NotebookAbstractFactory
{
    public function createMemory(): MemoryInterface
    {
        return new Memory32Gb();
    }

    public function createDisplay(): DisplayInterface
    {
        return new Display15();
    }

    public function createProcessor(): ProcessorInterface
    {
        return new I9(); 
   }
}

Always … test it!

Now, we still need our Client, in our case we’re using the tests as our clients in this series of posts. We need to create our NotebookExampleTest inside designpatterns/tests/Creational/AbstractFactory/NotebookExample

Our test will contain a dataProvider method that will return an array with our concrete factories (Notebooks) that will be tested by our testItCanCreateNotebooks method.

NotebookExampleTest.php

<?php

declare(strict_types=1);

namespace Tests\Creational\AbstractFactory\NotebookExample;

use DesignPatterns\Creational\AbstractFactory\NotebookExample\Contracts\DisplayInterface;
use DesignPatterns\Creational\AbstractFactory\NotebookExample\Contracts\MemoryInterface;
use DesignPatterns\Creational\AbstractFactory\NotebookExample\Contracts\NotebookAbstractFactory;
use DesignPatterns\Creational\AbstractFactory\NotebookExample\Contracts\ProcessorInterface;
use DesignPatterns\Creational\AbstractFactory\NotebookExample\Factories\Notebook1;
use DesignPatterns\Creational\AbstractFactory\NotebookExample\Factories\Notebook2;
use DesignPatterns\Creational\AbstractFactory\NotebookExample\Factories\Notebook3;
use PHPUnit\Framework\TestCase;

class NotebookExampleTest extends TestCase
{
    public function dataProvider(): array
    {
        return [
            [
                new Notebook1()
            ],
            [
                new Notebook2()
            ],
            [
                new Notebook3()
            ]
        ];
    }

    /**
     * @param NotebookAbstractFactory $factory
     * @dataProvider dataProvider
     */
    public function testItCanCreateNotebooks(NotebookAbstractFactory $factory)
    {
        $this->assertInstanceOf(MemoryInterface::class, $factory->createMemory());
        $this->assertInstanceOf(DisplayInterface::class, $factory->createDisplay());
        $this->assertInstanceOf(ProcessorInterface::class, $factory->createProcessor());
    }
}

As you can see the test asserts if your notebook is returning an instance of the correct interface in each method of its factory.


You can follow our repository https://github.com/diegosm/designpatterns, everything is there.

I hope you understood this concept and feel free to ask something or do your contribution. There are many examples on the internet of this pattern.

This article is a part of our Design Pattern Series, follow our Design Patterns – What, when, why? link 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *




Enter Captcha Here :