2.1. Abstract Factory

2.1.1. Intent

According to the Gang of Four, the Abstract Factory pattern “provide an interface for creating families of related or dependent objects without specifying their concrete classes” (Design Patterns: Elements of Reusable Object-Oriented Software, 2013, p. 87).

2.1.2. When to use it?

The Abstract Factory pattern should be used when :

  • an application has to be independent from the way objects are created
  • you need to deal with families of objects and you wand to easily switch from one to another
  • families of products were designed to work together only

Abstract Factory pattern is a very central design pattern for Dependency Injection (DI).

2.1.3. Diagram

Created using PhpStorm and yFiles.

Class diagram of the Abstract Factory pattern

2.1.4. Implementation

BeerInterface.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?php

namespace Phpatterns\Creational\AbstractFactory;

interface BeerInterface
{
    /**
     * Name of the beer (Heineken, Budweiser, Guinness, etc.)
     * @return string
     */
    public function getName();
}

Budweiser.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php

namespace Phpatterns\Creational\AbstractFactory\Beer;

use Phpatterns\Creational\AbstractFactory\BeerInterface;

class Budweiser implements BeerInterface
{
    /** @var string */
    private $name;

    public function __construct()
    {
        $this->name = 'Budweiser';
    }

    /**
     * Name of the beer (Heineken, Budweiser, Guinness, etc.)
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }
}

Heineken.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php

namespace Phpatterns\Creational\AbstractFactory\Beer;

use Phpatterns\Creational\AbstractFactory\BeerInterface;

class Heineken implements BeerInterface
{
    /** @var string */
    private $name;

    public function __construct()
    {
        $this->name = 'Heineken';
    }

    /**
     * Name of the beer (Heineken, Budweiser, Guinness, etc.)
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }
}

CocktailInterface.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<?php

namespace Phpatterns\Creational\AbstractFactory;

interface CocktailInterface
{
    /**
     * Name of the cocktail (Blue Lagoon, Long Island, Mojito, etc.)
     * @return string
     */
    public function getName();

    /**
     * Get all ingredients that were mixed to obtain that cocktail
     * @return array
     */
    public function getIngredients();
}

LongIsland.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?php

namespace Phpatterns\Creational\AbstractFactory\Cocktail;

use Phpatterns\Creational\AbstractFactory\CocktailInterface;

class LongIsland implements CocktailInterface
{
    /** @var string */
    private $name;

    /** @var array */
    private $ingredients;

    public function __construct()
    {
        $this->name = 'Long Island';
        $this->ingredients = [
            'Tequila',
            'Vodka',
            'Light rum',
            'Triple sec',
            'Gin',
            'Coca-Cola'
        ];
    }

    /**
     * Name of the cocktail (Blue Lagoon, Long Island, Mojito, etc.)
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Get all ingredients that were mixed to obtain that cocktail
     * @return array
     */
    public function getIngredients()
    {
        return $this->ingredients;
    }
}

Mojito.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?php

namespace Phpatterns\Creational\AbstractFactory\Cocktail;

use Phpatterns\Creational\AbstractFactory\CocktailInterface;

class Mojito implements CocktailInterface
{
    /** @var string */
    private $name;

    /** @var array */
    private $ingredients;

    public function __construct()
    {
        $this->name = 'Mojito';
        $this->ingredients = [
            'White rum',
            'Sugar cane juice',
            'Lime juice',
            'Sparkling water',
            'Mint'
        ];
    }

    /**
     * Name of the cocktail (Blue Lagoon, Long Island, Mojito, etc.)
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Get all ingredients that were mixed to obtain that cocktail
     * @return array
     */
    public function getIngredients()
    {
        return $this->ingredients;
    }
}

AbstractBarFactory.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<?php

namespace Phpatterns\Creational\AbstractFactory;

abstract class AbstractBarFactory
{
    /**
     * Creates a fresh Beer
     * @return BeerInterface
     */
    abstract public function createBeer();

    /**
     * Creates a nice Cocktail
     * @return CocktailInterface
     */
    abstract public function createCocktail();
}

FirstCheapBar.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php

namespace Phpatterns\Creational\AbstractFactory\Bar;

use Phpatterns\Creational\AbstractFactory;
use Phpatterns\Creational\AbstractFactory\Beer;
use Phpatterns\Creational\AbstractFactory\Cocktail;

class FirstCheapBar extends AbstractFactory\AbstractBarFactory
{
    /**
     * Creates a fresh Beer
     * @return AbstractFactory\BeerInterface
     */
    public function createBeer()
    {
        return new Beer\Budweiser();
    }

    /**
     * Creates a nice Cocktail
     * @return AbstractFactory\CocktailInterface
     */
    public function createCocktail()
    {
        return new Cocktail\LongIsland();
    }
}

SecondCheapBar.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php

namespace Phpatterns\Creational\AbstractFactory\Bar;

use Phpatterns\Creational\AbstractFactory;
use Phpatterns\Creational\AbstractFactory\Beer;
use Phpatterns\Creational\AbstractFactory\Cocktail;

class SecondCheapBar extends AbstractFactory\AbstractBarFactory
{
    /**
     * Creates a fresh Beer
     * @return AbstractFactory\BeerInterface
     */
    public function createBeer()
    {
        return new Beer\Heineken();
    }

    /**
     * Creates a nice Cocktail
     * @return AbstractFactory\CocktailInterface
     */
    public function createCocktail()
    {
        return new Cocktail\Mojito();
    }
}

2.1.5. Tests

AbstractBarFactoryTest.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php

namespace Test\Phpatterns\Creational\AbstractFactory;

use Phpatterns\Creational\AbstractFactory;
use Phpatterns\Creational\AbstractFactory\Bar;

class AbstractBarFactoryTest extends \PHPUnit_Framework_TestCase
{
    /**
     * All objects created must be instances of interfaces
     * (the client isn't aware of concrete classes)
     * @param AbstractFactory\AbstractBarFactory $barFactory
     * @dataProvider barFactoriesProvider
     */
    public function testBarFactories($barFactory)
    {
        $beer = $barFactory->createBeer();
        $this->assertInstanceOf(AbstractFactory\BeerInterface::class, $beer);

        $cocktail = $barFactory->createCocktail();
        $this->assertInstanceOf(AbstractFactory\CocktailInterface::class, $cocktail);
    }

    public function barFactoriesProvider()
    {
        return [
            [new Bar\FirstCheapBar()],
            [new Bar\SecondCheapBar()]
        ];
    }
}