Benefits of building your code with a Factory Pattern
In an effort to keep our work DRY (Don’t repeat yourself), it is a best practice to use design patterns. The Factory pattern is often used in the creation of objects. A developer can do all of the work of creating the object in the factory, instead of repeating it every time you want to create a new instance.
If you later need to change, rename, or replace the class responsible for the object building…
…you can do so and you will only have to modify the code in the factory. Not having to to hunt down every place in your project that uses the class to change the code can be a real benefit.
Using the factory pattern is not always needed or the “right thing to do”. At times a factory will simply be adding unneeded complexity.
However when making a fairly large or complex project you may save a lot of trouble down the road by using factories.
Why Factory Method Pattern
- Encapsulate the logic for instantiating complex objects.
- Reduce tight coupling between our application classes.
- Makes it easier to add additional types of creation classes.
How SOLID is the Factory Pattern?
[S] The Factory Pattern permits each subclass to focus on the single responsibility of building a specific object type.
[O] With the Factory Method Pattern, Well tested base classes are easily extended without breaking the existing object building logic. Building with TTD (Test Driven Design) or at least a reasonable degree of Unit testing, the Factory Method Pattern is open to extension while remaining closed to modification.
[L] The Factory Method Pattern employs subclasses, implementing the base class’s methods and properties. The subclasses conform to the base class’s implied behavior. By this we can see that the Factory Method Pattern satisfies the Liskov Substitution Principle.
[ I ] When designing a Factory Method Pattern, the developer should promote the Interface Segregation Principle by making sure that abstractions such as abstract base classes and interfaces do not unnecessarily impose methods that could be added by concrete classes.
[D] The Factory Method Pattern is designed to decouple the lower lever concerns associated with the construction of specific objects from the higher level logic involved in making the decision to build objects.
How to implement the Factory Method Pattern
While not a requirement, it is good to start with a class having a name appended with “Factory” to make the pattern obvious. In this example, the class is making products so the name is ProductFactory. Making your pattern obvious is a way of documenting your code and making it easier to find and extend the pattern.
The factory class should also have an obvious build method name such as “build” or “create” that takes parameters. The parameters passed into the build method will be used to make determinations about which concrete classes to use in constructing new objects. Design the parameters with extensibility in mind. Often, is it best to package parameters into arrays so more or less parameters may be used without altering the basic structure of the original builder method. It often makes sense for the factory method pattern to be static or available as a service to other classes.
Create a Factory class
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 |
<?php namespace Pkl\Factories\Product; class ProductFactory { // ... public static function build (string $sku = '', string $type = '', array $data) { if($type == '') { throw new Exception('Invalid Type.'); } else { // Package up the product data $prod_data = ['sku' => $sku, 'type' => $type, 'data' => $data]; $namespace = 'Pkl\\Factories\\Product\\'; // Build the concreate classname based on type $className = $namespace . 'Product'. ucfirst($type); //Attempt to call the appropriate concreate class if(class_exists($className)) { return new $className($prod_data); } else { throw new \Exception($className . ' not found.'); } } } // ... } } |
In this example, the concrete classes will be built based on abstraction in order to keep them consistent with the “contracted” properties and methods for the type of objects being built.
The abstraction of product defaults
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 |
<?php namespace Pkl\Factories\Product; // Define a default product abstraction abstract class DefaultProduct { protected $data; protected $sku; protected $type = null; public function __construct(string $sku, string $type, array $data) { $this->sku = $sku; $this->data = $data; } public function getSku() { return $this->sku; } public function getData() { return $this->data; } public function getType() { return $this->type; } } |
The variations in the objects are built in the various concrete classes. Additional concrete classes may easily be built to satisfy future requirements without breaking the existing logic.
The concrete maker classes
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 |
<?php namespace Pkl\Factories\Product; use Pkl\Factories\Product\ArtPrint AS ArtPrint; use Pkl\Factories\Product\DefaultProduct AS DefaultProduct; class ProductArtPrint extends DefaultProduct implements ArtPrint { // properties ensured by interface public function __construct(array $data){ // Base properties ensured by interface $this->data = $data['data']; $this->sku = $data['sku']; $this->type = $data['type']; } public function getPrintSizeVertical(){ return $this->data['data']['size_vertical']; } public function getPrintSizeHorizontal(){ return $this->data['data']['size_horizontal']; } } |
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 |
<?php namespace Pkl\Factories\Product; use Pkl\Factories\Product\ArtFrame; use Pkl\Factories\Product\DefaultProduct; // interface ArtPrint not shown class ProductFrame extends DefaultProduct implements ArtFrame { public function __construct(array $data){ // Base properties ensured by interface $this->data = $data['data']; $this->sku = $data['sku']; $this->type = $data['type']; } public function getFrameInnerSizeVertical(){ return $this->data['data']['frame_inner_size_vertical']; } public function getFrameInnerSizeHorizontal(){ return $this->data['data']['frame_inner_size_horizontal']; } public function getFrameOuterSizeVertical(){ return $this->data['data']['frame_outer_size_vertical']; } public function getFrameOuterSizeHorizontal(){ return $this->data['data']['frame_outer_size_horizontal']; } } |
Additional concrete classes may easily be built to satisfy future requirements without breaking the existing logic.