2.3. Factory Method

2.3.1. Intent

According to the Gang of Four, the Factory Method pattern is a way to “define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses” (Design Patterns: Elements of Reusable Object-Oriented Software, 2013, p. 107).

2.3.2. When to use it?

The Factory Method pattern should be used to avoid coupling between client and objects creation or if you don’t know in advance all the concrete classes to instantiate.

2.3.3. Diagram

Created using PhpStorm and yFiles.

Class diagram of the Factory Method pattern

2.3.4. Implementation

InformationMatrix.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
<?php

namespace Phpatterns\Creational\FactoryMethod;

abstract class InformationMatrix
{
    /** @var int */
    protected $height;

    /** @var int */
    protected $length;

    public function __construct()
    {
        $this->height = 100;
        $this->length = 100;
    }

    /**
     * Set the height of the information matrix
     *
     * @param int $height
     * @return $this
     */
    public function setHeight($height)
    {
        $this->height = $height;
        return $this;
    }

    /**
     * Set the length of the information matrix
     *
     * @param int $length
     * @return $this
     */
    public function setLength($length)
    {
        $this->length = $length;
        return $this;
    }
}

Code128.php (a special type of barcode)

 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
<?php

namespace Phpatterns\Creational\FactoryMethod\Matrix;

use Phpatterns\Creational\FactoryMethod;

class Code128 extends FactoryMethod\InformationMatrix
{
    /** @var int */
    private $barLength;

    public function __construct()
    {
        parent::__construct();
        $this->barLength = 2;
    }

    /**
     * Set the bars' length for the Code 128 barcode
     *
     * @param int $barLength
     * @return $this
     */
    public function setBarLength($barLength)
    {
        $this->barLength = $barLength;
        return $this;
    }
}

DataMatrix.php

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

namespace Phpatterns\Creational\FactoryMethod\Matrix;

use Phpatterns\Creational\FactoryMethod;

class DataMatrix extends FactoryMethod\InformationMatrix
{
    public function __construct()
    {
        parent::__construct();
    }
}

QRCode.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
<?php

namespace Phpatterns\Creational\FactoryMethod\Matrix;

use Phpatterns\Creational\FactoryMethod;

class QRCode extends FactoryMethod\InformationMatrix
{
    /** @var string */
    private $template;

    public function __construct()
    {
        parent::__construct();
        $this->template = 'default';
    }

    /**
     * Set the template of the QR Code
     *
     * @param string $template
     * @return $this
     */
    public function setTemplate($template)
    {
        $this->template = $template;
        return $this;
    }
}

AbstractCodeGenerator.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
<?php

namespace Phpatterns\Creational\FactoryMethod;

/**
 * It's the class that handles the Factory Method
 */
abstract class AbstractCodeGenerator
{
    const ONE_DIMENSIONAL = 1;
    const TWO_DIMENSIONAL = 2;

    /**
     * Create a new information matrix
     * (in our case : a QRCode or a DataMatrix or a Code128)
     *
     * @param int $type (one or two dimensional)
     * @return InformationMatrix
     */
    public function renderCode($type)
    {
        /** @var $code InformationMatrix */
        $code = $this->createCode($type);

        return $code
            ->setHeight(50)
            ->setLength(80);
    }

    /**
     * Abstract code creation. That method must be implemented by subclasses.
     *
     * @param int $type (one or two dimensional)
     * @return InformationMatrix
     */
    abstract protected function createCode($type);
}

BinaryCodeGenerator.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
<?php

namespace Phpatterns\Creational\FactoryMethod\Generator;

use Phpatterns\Creational\FactoryMethod;
use Phpatterns\Creational\FactoryMethod\Matrix;

/**
 * It's a factory used to create codes that can handle binary data
 */
class BinaryCodeGenerator extends FactoryMethod\AbstractCodeGenerator
{
    /**
     * Creates a code that can handle binary data
     *
     * @param int $type (one or two dimensional)
     * @return FactoryMethod\InformationMatrix
     */
    protected function createCode($type)
    {
        if ($type === parent::TWO_DIMENSIONAL) {
            return (new Matrix\QRCode())->setTemplate('binary');
        } else {
            throw new \InvalidArgumentException("This type ($type) is not able to handle binary data!");
        }
    }
}

NumericCodeGenerator.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
<?php

namespace Phpatterns\Creational\FactoryMethod\Generator;

use Phpatterns\Creational\FactoryMethod;
use Phpatterns\Creational\FactoryMethod\Matrix;

/**
 * It's a factory used to create codes that can handle numeric data
 */
class NumericCodeGenerator extends FactoryMethod\AbstractCodeGenerator
{
    /**
     * Creates a code that can handle numeric data
     *
     * @param int $type (one or two dimensional)
     * @return FactoryMethod\InformationMatrix
     */
    protected function createCode($type)
    {
        if ($type === parent::ONE_DIMENSIONAL) {
            return (new Matrix\Code128())->setBarLength(5);
        } elseif ($type === parent::TWO_DIMENSIONAL) {
            return new Matrix\DataMatrix();
        } else {
            throw new \InvalidArgumentException("This type ($type) is not able to handle numeric data!");
        }
    }
}

2.3.5. Tests

CodeGeneratorTest.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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
<?php

namespace Test\Phpatterns\Creational\FactoryMethod;

use Phpatterns\Creational\FactoryMethod;
use Phpatterns\Creational\FactoryMethod\Generator;

class CodeGeneratorTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @var Generator\BinaryCodeGenerator
     */
    private $binaryGenerator;

    /**
     * @var Generator\NumericCodeGenerator
     */
    private $numericGenerator;

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

    protected function setUp()
    {
        $this->binaryGenerator = new Generator\BinaryCodeGenerator();
        $this->numericGenerator = new Generator\NumericCodeGenerator();

        $this->types = [
            'one' => FactoryMethod\AbstractCodeGenerator::ONE_DIMENSIONAL,
            'two' => FactoryMethod\AbstractCodeGenerator::TWO_DIMENSIONAL
        ];
    }

    /**
     * Testing that concrete factories extend CodeGenerator abstract class
     */
    public function testConcreteFactoriesSuperClass()
    {
        $concreteGenerators = [
            $this->binaryGenerator,
            $this->numericGenerator
        ];

        foreach ($concreteGenerators as $generator) {
            $this->assertInstanceOf(FactoryMethod\AbstractCodeGenerator::class, $generator);
        }
    }

    /**
     * Testing information matrix creation mechanism using binary data
     */
    public function testCreationMechanismWithBinaryData()
    {
        $this->setExpectedException(
            "InvalidArgumentException",
            "This type ({$this->types['one']}) is not able to handle binary data!"
        );
        $this->binaryGenerator->renderCode($this->types['one']);

        $this->assertInstanceOf(
            FactoryMethod\InformationMatrix::class,
            $this->binaryGenerator->renderCode($this->types['two'])
        );

        $this->setExpectedException(
            "InvalidArgumentException",
            "This type (whatever) is not able to handle binary data!"
        );
        $this->binaryGenerator->renderCode('whatever');
    }

    /**
     * Testing information matrix creation mechanism using numeric data
     */
    public function testCreationMechanismWithNumericData()
    {
        $this->assertInstanceOf(
            FactoryMethod\InformationMatrix::class,
            $this->numericGenerator->renderCode($this->types['one'])
        );

        $this->assertInstanceOf(
            FactoryMethod\InformationMatrix::class,
            $this->numericGenerator->renderCode($this->types['two'])
        );

        $this->setExpectedException(
            "InvalidArgumentException",
            "This type (whatever) is not able to handle numeric data!"
        );
        $this->numericGenerator->renderCode('whatever');
    }
}