Static Factory methods aka named constructors
Create Object Instance
We have many ways how to create an object instance. You can use “new” word, use a Factory or Builder Method, Inject it using IOC Container.
Sometimes we have a situation where an object can be initialized in multiple ways, mostly based on the parameters passed in. It is still the same type of object, therefore there is no need to inberitance and split it into multiple parent-child classes. The solution can be to create multiple constructors.
In many languages like Java, C++, etc we have something called method overloading. But let’s focus on PHP, which doesn’t contain such feature. How we can handle such a case?
Named Constructors
The name of this pattern is most popular in the PHP world. Its original name is Static Factory Pattern and it is a great solution to this problem. The basic idea is that we hide the __constructor method by making it private, but instead, we create several methods that will create an instance of the object. I must admit that the basic idea is quite similar to the Singleton pattern, with a few differences. We have multiple initialization methods and inside them we always create a new instance of the object (calling the private constructor).
To details
The main thing here is that those multiple initialization methods should also be properly named. The name should present context or intention of our object initialization.
As an example, let’s consider how such a class can be improved to avoid so many IF statements. And Several other problems associated with this case. (Hopefully you’ll spot at least two):
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 |
class SystemPowerCompany { private $kwh; /** * SystemPower constructor. * @param float $khw. * @param int $amount. * @param int $persons */ public function __construct(float $khw = null, int $amount = null, int $persons == null) { if($amount |= null) { $this->kwh = $amount / config('gross_1kwh'); } else if ($persons |= null) { $this->kwh = $persons * config('person_1kwh'); } else { $this->kwh = $kwh; } } /** * @return float */ public function getKwp(): float { return $this->kwh; } } |
Solution
Below is the code that demonstrates an example class. I think the intent will be quite clear and needs no further explanation.
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 |
class SystemPowerCompany implements ISystemPower { private $kwh; /** * SystemPower constructor. * @param float $khw */ protected function __construct(float $khw) { $this->kwh = $khw; } /** * @return float */ public function getKwp(): float { return $this->kwh; } /** * @param float $kwh * @return ISystemPower */ public static function fromKwh(float $kwh): ISystemPower { return new self($kwh); } /** * @param int $amount * @return ISystemPower */ public static function fromInvoice(int $amount): ISystemPower { $kwh = $amount / config('gross_1kwh'); return new self($kwh); } /** * @param int $persons * @return ISystemPower */ public static function fromPersons(int $persons): ISystemPower { $kwh = $persons * config('person_1kwh'); return new self($kwh); } } |
This way we have small closed blocks, and we are sure that at least one of it will be called to create the object instance.
Wondering about those two issues from initial code?
This code which is presented before refactor contains at least two main issues: 1. From outside, when you want to create the object, the params intention is not quite clear. It doesn’t say that a) you have to set at least one param b) you shouldn’t set multiple params 2. There is a risk, that no params will be set – it’s not covered whith the if-else statements, and you can’t predict the results of it.
Subsequent articles
- Static Factory Methods aka Named Constructors: Read more…
- Refactoring Example – guard clauses: Read more…
- Hard-coded parts: Read more…
- Dependency Injection code smells: Read more…
- Naming conventions: Read more…
- Improving conditions clauses – few small things with hudge impact: Read more…
- Clean Code – how to start: Read more…