範例:計算機 (簡單工廠模式)

Pattern: 簡單工廠模式

Class Diagram: 計算機


需求一:客戶想要一個加法計算機

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

namespace App\FactoryPattern\Calculator;

class Program
{
/**
* @var int
*/
private $firstNumber;

/**
* @var int
*/
private $secondNumber;

/**
* @var string
*/
private $operationString;

public function __construct($firstNumber, $secondNumber)
{
$this->firstNumber = $firstNumber;
$this->secondNumber = $secondNumber;
}

public function run()
{
return $this->firstNumber + $this->secondNumber;
}
}


需求二:客戶想要在既有的計算機上,增加減法功能

我們思路如下:

  • 首先改變建構式,讓參數有運算符號
1
2
3
4
5
6
public function __construct($firstNumber, $secondNumber, $operationString)
{
$this->firstNumber = $firstNumber;
$this->secondNumber = $secondNumber;
$this->operationString = $operationString;
}
  • 由運算符號,執行對應的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function run()
{
$operationString = $this->operationString;

switch ($operationString) {
case '+':
return $this->firstNumber + $this->secondNumber;
break;

case '-':
return $this->firstNumber - $this->secondNumber;
break;
}
}

需求三:客戶想要除法的功能

  • 於是我們在函式中,再添加一個除法的功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public function run()
{
$operationString = $this->operationString;

switch ($operationString) {
case '+':
return $this->firstNumber + $this->secondNumber;
break;

case '-':
return $this->firstNumber - $this->secondNumber;
break;

case "/":
if ($this->secondNumber === 0) {
return 'Division by zero';
}
return $this->firstNumber / $this->secondNumber;
break;
}
}

這時候功能是完成了,但有沒有覺得哪裡怪怪的?

  1. 除法的case很醜,要額外判斷除數不為0,增加了嵌套層數

  2. 每當客戶要新增/修改/刪除一種運算時,其餘的運算都會被修改到。
    違反了開放封閉原則

讓我們用簡單工廠改造它。


  • 首先定義運算的介面
1
2
3
4
5
6
7
8
<?php

namespace App\FactoryPattern\Calculator\Contracts;

interface Operable
{
public function execute();
}
  • 實作加法運算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php

namespace App\FactoryPattern\Calculator\Operation;

use App\FactoryPattern\Calculator\Contracts\Operable;

class AddOperation implements Operable
{
public $firstNumber;
public $secondNumber;

public function execute()
{
return $this->firstNumber + $this->secondNumber;
}
}
  • 實作減法運算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php

namespace App\FactoryPattern\Calculator\Operation;

use App\FactoryPattern\Calculator\Contracts\Operable;

class MinusOperation implements Operable
{
public $firstNumber;
public $secondNumber;

public function execute()
{
return $this->firstNumber - $this->secondNumber;
}
}
  • 實作除法運算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

namespace App\FactoryPattern\Calculator\Operation;

use App\FactoryPattern\Calculator\Contracts\Operable;

class DivideOperation implements Operable
{
public $firstNumber;
public $secondNumber;

public function execute()
{
if ($this->secondNumber === 0) {
return 'Division by zero';
}
return $this->firstNumber / $this->secondNumber;
}
}
  • 最後做一個創造運算的工廠
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 App\FactoryPattern\Calculator;

use App\FactoryPattern\Calculator\Contracts\Operable;
use App\FactoryPattern\Calculator\Operation\AddOperation;
use App\FactoryPattern\Calculator\Operation\MinusOperation;
use App\FactoryPattern\Calculator\Operation\DivideOperation;

class SimpleOperationFactory
{
/**
* @param string $operationString
* @return Operable
*/
public function create($operationString): Operable
{
switch ($operationString) {
case '+':
return new AddOperation();
break;

case '-':
return new MinusOperation();
break;

case '/':
return new DivideOperation();
break;
}
}
}
  • 修改原本的產品程式碼,讓運算由工廠產生
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
<?php

namespace App\FactoryPattern\Calculator;

use App\FactoryPattern\Calculator\Contracts\Operable;
use App\FactoryPattern\Calculator\SimpleOperationFactory;

class Program
{
/**
* @var int
*/
private $firstNumber;

/**
* @var int
*/
private $secondNumber;

/**
* @var string
*/
private $operationString;

/**
* @var Operable
*/
private $operation;

public function __construct($firstNumber, $secondNumber, $operationString)
{
$this->firstNumber = $firstNumber;
$this->secondNumber = $secondNumber;
$this->operationString = $operationString;
}

public function run()
{
$simpleOperationFactory = new SimpleOperationFactory();
$this->operation = $simpleOperationFactory->create($this->operationString);

return $this->execute();
}

/**
* @return int|string
*/
private function execute()
{
$this->operation->firstNumber = $this->firstNumber;
$this->operation->secondNumber = $this->secondNumber;
return $this->operation->execute();
}
}

需求四:客戶想要乘法的功能

  • 實作乘法運算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php

namespace App\FactoryPattern\Calculator\Operation;

use App\FactoryPattern\Calculator\Contracts\Operable;

class MultiplyOperation implements Operable
{
public $firstNumber;
public $secondNumber;

public function execute()
{
return $this->firstNumber * $this->secondNumber;
}
}
  • 修改運算工廠
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* @param string $operationString
* @return Operable
*/
public function create($operationString): Operable
{
switch ($operationString) {
case '+':
return new AddOperation();
break;

case '-':
return new MinusOperation();
break;

case '/':
return new DivideOperation();
break;

case '*':
return new MultiplyOperation();
break;
}
}

[單一職責原則]
創造運算的職責運算本身的職責分離,就是工廠模式的精神!

[開放封閉原則]
這下子,我們終於不會每次新增/修改/刪除一個運算時,
影響到其餘所有運算了。

ʕ •ᴥ•ʔ:函式出現switch時,很可能就是使用工廠模式的時機。

Share