Introduction
Replace Constructors with Creation Methods pattern is one of the most used creational design patterns. It is used mainly for these reasons:
- Simplifies code structure by using more descriptive methods instead of generic constructor
- Removes the limitation of having several constructors with the same signature
Initial setup
To describe the refactoring, I proposed a very simple use case. Suppose to have a ParamManager class with a constructor accepting from 0 to 2 parameters. It behaves as follows:
- It returns the string 'both' if two arguments are provided
- It returns the string 'first' if one argument is provided
- It returns the string 'none' if no arguments are provided
The behaviour is described by the following tests:
1class ExampleTest extends TestCase
2{
3 /** @test */
4 public function it_builds_param_manager_with_two_parameters()
5 {
6 $manager = new ParamManager('a', 'b');
7
8 $this->assertEquals('both', $manager->getValue());
9 }
10
11 /** @test */
12 public function it_builds_param_manager_with_one_parameter()
13 {
14 $manager = new ParamManager('a');
15
16 $this->assertEquals('first', $manager->getValue());
17 }
18
19 /** @test */
20 public function it_builds_param_manager_with_no_parameters()
21 {
22 $manager = new ParamManager();
23
24 $this->assertEquals('none', $manager->getValue());
25 }
26}
and the ParamManager class is defined as:
1class ParamManager
2{
3 private $param1;
4 private $param2;
5
6 public function __construct($param1 = null, $param2 = null)
7 {
8 $this->param1 = $param1;
9 $this->param2 = $param2;
10 }
11
12 public function getValue()
13 {
14 if ($this->param1 && $this->param2) {
15 return 'both';
16 }
17
18 if ($this->param1) {
19 return 'first';
20 }
21
22 return 'none';
23 }
24}
The main purpose of Creation Methods is to have more descriptive and easier to remember methods (w.r.t. constructors) for objects instantiation.
Refactoring
In order to implement the pattern we must follow these steps:
1. Locate a client class constructor call and extract it to a new method which will represent the creation method
For example, start from the first constructor call in the tests:
1 /** @test */
2 public function it_builds_param_manager_with_two_parameters()
3 {
4- $manager = new ParamManager('a', 'b');
5+ $manager = $this->withTwoArguments('a', 'b');
6
7 $this->assertEquals('both', $manager->getValue());
8 }
9
10+ private function withTwoArguments($param1, $param2)
11+ {
12+ return new ParamManager($param1, $param2);
13+ }
2. Move the method to a static or non-static method in the target class
Refactor the new method by moving it to the target class (ParamManager) making it public and, perhaps, static
1class ParamManager
2{
3 private $param1;
4 private $param2;
5
6 public function __construct($param1 = null, $param2 = null)
7 {
8 $this->param1 = $param1;
9 $this->param2 = $param2;
10 }
11
12+ public static function withTwoArguments($param1, $param2)
13+ {
14+ return new ParamManager($param1, $param2);
15+ }
16
17 public function getValue()
18 {
19 if ($this->param1 && $this->param2) {
20 return 'both';
21 }
22
23 if ($this->param1) {
24 return 'first';
25 }
26
27 return 'none';
28 }
29}
the client code then reduces to:
1 /** @test */
2 public function it_builds_param_manager_with_two_parameters()
3 {
4- $manager = $this->withTwoArguments('a', 'b');
5+ $manager = ParamManager::withTwoArguments('a', 'b');
6
7 $this->assertEquals('both', $manager->getValue());
8 }
9
10- private function withTwoArguments($param1, $param2)
11- {
12- return new ParamManager($param1, $param2);
13- }
3. Replace the same occurrence of the constructor (if any)
If any, remove any identical occurrence of the very same constructor, in the proposed example, no more occurrence exist
4. Repeat for the next constructor call
Using the same approach, generate creational methods for the other constructors' invocations, thus defining withOneArgument and withNoArguments methods:
1class ParamManager
2{
3 private $param1;
4 private $param2;
5
6 public function __construct($param1 = null, $param2 = null)
7 {
8 $this->param1 = $param1;
9 $this->param2 = $param2;
10 }
11
12 public static function withTwoArguments($param1, $param2)
13 {
14 return new ParamManager($param1, $param2);
15 }
16
17+ public static function withOneArgument($param1)
18+ {
19+ return new ParamManager($param1);
20+ }
21
22+ public static function withNoArguments()
23+ {
24+ return new ParamManager();
25+ }
26
27 public function getValue()
28 {
29 if ($this->param1 && $this->param2) {
30 return 'both';
31 }
32
33 if ($this->param1) {
34 return 'first';
35 }
36
37 return 'none';
38 }
39}
and you are done!
Final notes
If you don't need the generic constructor anymore, I suggest making it private to enforce the creational method usage, thus avoiding a mix of the two instantiation alternatives (creational methods and constructors)
The resulting code is far from being clean, for example, you could remove conditionals in getValue() by applying another common design pattern, Strategy, which I will describe in the next tutorial.
If you have doubts or corrections, please leave a comment below.