範例:運輸系統(工廠方法模式)

Pattern: 工廠方法模式

Class Diagram: 運輸系統


前情提要:鐵路運輸系統,參考範例:運輸系統(簡單工廠模式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

namespace App\FactoryPattern\Transport\SimpleFactoryPattern;

use App\FactoryPattern\Transport\SimpleFactoryPattern\RailwayModelFactory;

class Program
{
/**
* @param string $modelName
* @return string
*/
public function getModel($modelName)
{
$railwayModelFactory = new RailwayModelFactory();
$model = $railwayModelFactory->createModel($modelName);
return $model->getName();
}
}

需求一:隨著公司擴大,運輸系統不再僅限鐵路,將引進波音747,進軍航空業

此時發現,原本的簡單工廠,
從創建對象都是火車,到有飛機,
邏輯越來越複雜了。

常常違反開放封閉原則
讓我們用工廠方法模式修正它。

  • 首先定義機型工廠介面(可生產火車、飛機等各種機型)
1
2
3
4
5
6
7
8
9
10
<?php

namespace App\FactoryPattern\Transport\FactoryMethodPattern\Contracts;

use App\FactoryPattern\Transport\FactoryMethodPattern\Contracts\Model;

interface ModelFactory
{
public function createModel(): Model;
}
  • 實作區間車工廠
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

namespace App\FactoryPattern\Transport\FactoryMethodPattern\ModelFactories;

use App\FactoryPattern\Transport\FactoryMethodPattern\Contracts\Model;
use App\FactoryPattern\Transport\FactoryMethodPattern\Contracts\ModelFactory;
use App\FactoryPattern\Transport\FactoryMethodPattern\Model\LocalTrain;

class LocalTrainFactory implements ModelFactory
{
public function createModel(): Model
{
//取得生產材料...
//招募技術團隊...
return new LocalTrain();
}
}
  • 實作復興號工廠
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

namespace App\FactoryPattern\Transport\FactoryMethodPattern\ModelFactories;

use App\FactoryPattern\Transport\FactoryMethodPattern\Contracts\Model;
use App\FactoryPattern\Transport\FactoryMethodPattern\Contracts\ModelFactory;
use App\FactoryPattern\Transport\FactoryMethodPattern\Model\SemiExpress;

class SemiExpressFactory implements ModelFactory
{
public function createModel(): Model
{
//取得生產材料...
//招募技術團隊...
return new SemiExpress();
}
}
  • 實作自強號工廠
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

namespace App\FactoryPattern\Transport\FactoryMethodPattern\ModelFactories;

use App\FactoryPattern\Transport\FactoryMethodPattern\Contracts\Model;
use App\FactoryPattern\Transport\FactoryMethodPattern\Model\LimitedExpress;
use App\FactoryPattern\Transport\FactoryMethodPattern\Contracts\ModelFactory;

class LimitedExpressFactory implements ModelFactory
{
public function createModel(): Model
{
//取得生產材料...
//招募技術團隊...
return new LimitedExpress();
}
}
  • 修改原本的程式碼
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 App\FactoryPattern\Transport\FactoryMethodPattern;

use App\FactoryPattern\Transport\FactoryMethodPattern\ModelFactories\LimitedExpressFactory;
use App\FactoryPattern\Transport\FactoryMethodPattern\ModelFactories\LocalTrainFactory;
use App\FactoryPattern\Transport\FactoryMethodPattern\ModelFactories\SemiExpressFactory;

class Program
{
/**
* @param string $modelName
* @return string
*/
public function getModel($modelName)
{
$modelFactory = $this->createModelFactory($modelName);
$model = $modelFactory->createModel();
return $model->getName();
}

/**
* @param string $modelName
* @return ModelFactory
*/
private function createModelFactory($modelName)
{
switch ($modelName) {
case 'LimitedExpress':
return new LimitedExpressFactory();
break;

case 'LocalTrain':
return new LocalTrainFactory();
break;

case 'SemiExpress':
return new SemiExpressFactory();
break;
}
}
}
  • 運用反射 (Reflection) 機制,讓客戶端的程式碼不再修改
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\Transport\FactoryMethodPattern;

use App\FactoryPattern\Transport\FactoryMethodPattern\Contracts\ModelFactory;
use ReflectionClass;

class Program
{
/**
* @param string $modelName
* @return string
*/
public function getModel($modelName)
{
$modelFactory = $this->createModelFactory($modelName);
$model = $modelFactory->createModel();
return $model->getName();
}

/**
* @param string $modelName
* @return ModelFactory
*/
private function createModelFactory($modelName)
{
$namespace = 'App\FactoryPattern\Transport\FactoryMethodPattern\ModelFactories';
$className = $modelName . 'Factory';

$reflector = new ReflectionClass($namespace . '\\' . $className);
return $reflector->newInstance();
}
}

這下子,改造工程結束,讓我們回到波音747!

  • 實作波音747機型
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

namespace App\FactoryPattern\Transport\FactoryMethodPattern\Model;

use App\FactoryPattern\Transport\FactoryMethodPattern\Contracts\Model;

class Boeing747 implements Model
{
public function getName()
{
return '波音747';
}
}
  • 最後實作波音747工廠
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

namespace App\FactoryPattern\Transport\FactoryMethodPattern\ModelFactories;

use App\FactoryPattern\Transport\FactoryMethodPattern\Contracts\ModelFactory;
use App\FactoryPattern\Transport\FactoryMethodPattern\Contracts\Model;
use App\FactoryPattern\Transport\FactoryMethodPattern\Model\Boeing747;

class Boeing747Factory implements ModelFactory
{
public function createModel(): Model
{
return new Boeing747();
}
}

[單一職責原則]
我們將創建類別 (Creator)產品類別 (Product) 視作兩種不同的職責。

[開放封閉原則]
當新增新的產品時,我們僅需新增產品類別及新增創建類別。
當修改既有產品時,我們僅需修改其產品類別,不會影響到其他的產品類別。

[介面隔離原則]
拆分出工廠介面與機型介面。

[依賴反轉原則]
客戶依賴於抽象工廠介面與機型介面。
工廠類別實作抽象的工廠介面。
機型類別實作抽象的機型介面。

ʕ •ᴥ•ʔ:不久之後,我們會再一次看到這個範例,敬請期待。