如何使用變數建立物件?
傳統我們會使用 if else
判斷,建立不同的物件,但由於 JavaScript 與 PHP 動態語言的特性,我們可以將要建立的物件的 class 名稱以變數表示,直接以該變數建立物件。
Version
ECMAScript 5
PHP 7.0
Laravel 5.2.22
JavaScript
if else
傳統若要根據不同的變數值,建立不同的物件,我們會使用 if else
方式 :1 1GitHub Commit : JavaScript : 傳統使用 if else 建立物件
1 | function Customer() { |
15行1
2
3
4
5if (type == 'admin') {
var user = new Admin();
} else {
var user = new Customer();
}
使用 if else
去判斷變數值,建立不同物件。
Variable
若將 class 名稱使用變數表示,則不需要 if else
判斷 : 2 2GitHub Commit : JavaScript:動態使用變數建立物件
1 | function Customer() { |
14行1
2var className = (type == 'admin') ? 'Admin' : 'Customer';
var user = new this[className];
判斷 type
,並將要建立物件的 class 名稱存入 className
變數。
搭配 this
,將 class 名稱以變數方式傳入 []
。
PHP
PHP 也可以達到類似 JavaScript 的功能。
if else
傳統若要根據不同的變數值,建立不同的物件,我們會使用 if else
方式 :3 3GitHub Commit : PHP : 傳統使用 if else 建立物件
1 | <?php |
20行1
2
3
4
5if ($type == 'admin') {
$user = new Admin();
} else {
$user = new Customer();
}
使用 if else
去判斷變數值,建立不同物件。
Variable
若將 class 名稱使用變數表示,則不需要 if else
判斷 : 4 4GitHub Commit : PHP : 動態使用變數建立物件
1 | class Customer |
18行1
2$className = ($type == 'admin') ? 'Admin' : 'Customer';
$user = new $className;
判斷 $type
,並將要建立物件的 class 名稱存入 $className
變數。
在 PHP,允許我們 new
之後直接加上變數建立物件。
Type Hint
談到這裡,雖然我們已經可以在 JavaScript 與 PHP 使用變數來建立物件,由於 class 名稱是字串,我們甚至可以將 class 名稱存在設定檔內,如config/app.php
,將來若因為需求改變,需改變建立物件的 class 名稱時,只需修改設定檔即可,並透過 config::get()
讀取 class 名稱,原來程式碼完全不用修改,也算是達到開放封閉原則的要求。
不過這種方式有個致命傷,就是那個 show()
函式並沒有硬性規定(如 interface 或 abstract class),也就是動態語言所謂的 duck type,也因為並沒有任何機制規定 show()
,只有程式設計師自己知道必須要有 show()
,也因此 PhpStorm 將無法幫你對 show()
做語法提示與語法檢查。
但隨著 PHP 的 Java 化之後 (type hint + service container),PHP成為唯一一個同時擁有 duck type 與 strong type 的程式語言,尤其 PHP 7 之後的type hint 更加完整(scalar type hint + return type),若搭配 service container,將可解決之前 show()
的問題。
routes.php5 5GitHub Commit : 修改 routes.php
1 | Route::get('/show', 'UserController@show'); |
在 routes.php 加上 URI 與其對應的 controller action。
UserController.php6 6GitHub Commit : 建立 UserController.php1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24namespace App\Http\Controllers;
use App\Http\Requests;
use App\Services\UserService;
class UserController extends Controller
{
/** @var UserService */
private $userService;
/**
* UserController constructor.
* @param UserService $userService
*/
public function __construct(UserService $userService)
{
$this->userService = $userService;
}
public function show()
{
$this->userService->show('admin');
}
}
第8行1
2
3
4
5
6
7
8
9
10
11/** @var UserService */
private $userService;
/**
* UserController constructor.
* @param UserService $userService
*/
public function __construct(UserService $userService)
{
$this->userService = $userService;
}
注入 UserService
。
20行1
2
3
4public function show()
{
$this->userService->show('admin');
}
在 show()
呼叫 $this->userService
的 show()
,並將 admin
變數傳入。
UserService.php7 7GitHub Commit : 建立 UserService.php1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24namespace App\Services;
use App\Services\User\UserFactory;
class UserService
{
/** @var UserFactory */
private $userFactory;
/**
* UserService constructor.
* @param UserFactory $userFactory
*/
public function __construct(UserFactory $userFactory)
{
$this->userFactory = $userFactory;
}
public function show(string $type)
{
$user = $this->userFactory->create($type);
$user->show();
}
}
第7行1
2
3
4
5
6
7
8
9
10
11 /** @var UserFactory */
private $userFactory;
/**
* UserService constructor.
* @param UserFactory $userFactory
*/
public function __construct(UserFactory $userFactory)
{
$this->userFactory = $userFactory;
}
注入 UserFactory
,專門負責建立 Admin
或 Customer
物件。
19行1
2
3
4
5public function show(string $type)
{
$user = $this->userFactory->create($type);
$user->show();
}
使用 $this->userFactory
的 create()
建立物件,將 $type
變數傳入,由 UserFactory
決定要建立哪個物件。
無論建立什麼物件,都使用相同的 show()
,這就是物件導向的多型。
AbstractUser.php8 8GitHub Commit : 建立 AbstractUser.php
1 | namespace App\Services\User; |
由 abstract class
定義 show()
,如此 PhpStorm 就能幫我們做語法提示與語法檢查了。
Admin.php9 9GitHub Commit : 建立 Admin.php
1 | namespace App\Services\User; |
Admin
繼承 AbstractUser
,因為之前定義了 show()
abstract method,所以必須在此實作 show()
。
Customer.php10 10GitHub Commit : 建立 Customer.php
1 | namespace App\Services\User; |
Customer
繼承 AbstractUser
,因為之前定義了 show()
abstract method,所以必須在此實作 show()
。
UserFactory11 11GitHub Commit : 建立 UserFactory.php
1 | namespace App\Services\User; |
重點在此,使用了 App::bind()
,將 abstract class 與 concrete class 綁定,其中 concrete class 的 class 名稱為變數。
然後再使用App::make()
建立綁定後的物件。
Conclusion
- 實務上 PHP 建議先考慮使用 type hint 的 strong type,這種方式 PhpStorm 支援較好,但是 duck type 也不是不能用,可以當成泛型使用,也就是當你不想考慮其型別時,就不要加上 type hint,故意模糊其型別。
- 在 PHP 與 Laravel,透過
App::bind()
與App::make()
也可以使用變數建立物件,還額外得到 type hint 與 PhpStorm 的支援。
Sample Code
完整的範例可以在我的GitHub上找到。