laravel 有 service 模式封裝外部行為與商業邏輯
而php 本身物件的觀念也有trait 的概念,可以將部分class function 注入到其他class 當中以達到達到重複使用程式碼的用法。
就我覺得在實際使用情形當中,這兩個有點像......
所以想要請問大家都在如何的使用場景或者情境中,分別使用這些功能?
我google 到發現說trait 很強調"copy" 這件事情;而問了身旁友人有提說要看這使用情景中的重複程度而定? 然而這樣的重複程度該如何定義? traits 是要一模一樣才比較適合使用嗎?
舉一個實際例子來說
我有兩個實體:像是學生和老師; 這兩個實體都要有匯入和匯出的功能
就這兩者的匯出與匯入功能來說,他們分別就欄位、還有匯入匯出成功的訊息與導向以及匯入匯出的紀錄有不一樣外,其實其他幾乎一模一樣,這讓我有點猶豫到底該使用trait 還是 service,感覺兩個都可以用;但就不知道到底哪一種對於日後的維護擴充有益?
service 是很常用的東西,幾乎沒有一個系統是不會用到 service 的。不管是你要使用到第三方的服務,或是你有一些不同專案通用的程式碼,都可以考慮做成一個 service。
另一方面,trait 適用的情境非常非常的少,以你舉的例子來看我一時想不到有什麼地方能用到 trait。所以我會稍微修改一下情境。
如你所列的需求,老師與學生這兩個類別都有匯出資料的功能。不過各自要匯出的欄位是不同的,因此讓他們實做 Exportable 界面處理各自的資料。
Interface Exportable
{
public function exportArray(): array;
}
接著你可能需要視情況能匯出不同的格式(csv, json, excel...),因此這邊設計一個 ExportService 界面。
Interface ExportService
{
public function exportDataTo(Exportable $obj, string $filename): void;
}
而老師與學生就分別設計成這樣子:
class Teacher implements Exportable
{
public $name = "teacher name";
protected $id = "teacher id";
public function exportArray(): array
{
// 只匯出 public property
return json_decode(json_encode($this), true);
}
}
class Student implements Exportable
{
public $name = "student name";
protected $id = "student id";
public function exportArray(): array
{
// 匯出全部 property
return (array)$this;
}
}
接著就可以(很偷懶的)做一個 dumpExportService 來匯出資料:
$dumpExportService = new class implements ExportService {
public function exportDataTo(Exportable $obj, string $filename): void
{
var_dump($obj->exportArray());
}
};
$t = new Teacher();
$dumpExportService->exportDataTo($t, "ignored");
$s = new Student();
$dumpExportService->exportDataTo($s, "ignored");
這邊之所以採用 ExportService 而不是 ExportTrait,是因為匯出不同格式這件事情並不是老師與學生類別的職責,做成 Service 更合理一些。執行結果如下:
array(1) {
["name"]=>
string(12) "teacher name"
}
array(2) {
["name"]=>
string(12) "student name"
["*id"]=>
string(10) "student id"
}
這時我們發現學生那邊匯出的資料多了些奇怪的符號,這是因為直接轉型成陣列造成的,所以做了些修正:
class StudentV2 implements Exportable
{
public $name = "student name";
protected $id = "student id";
public function exportArray(): array
{
// 匯出全部 property, 修正 key
$data = [];
foreach ((array)$this as $key => $val) {
$key = preg_replace('#^.*\0#', '', $key);
$data[$key] = $val;
}
return $data;
}
}
執行結果:
array(1) {
["name"]=>
string(12) "teacher name"
}
array(2) {
["name"]=>
string(12) "student name"
["id"]=>
string(10) "student id"
}
到目前為止都沒有 trait 派上用場的地方,所以我另外加上了一個 ClassRoom 類別,也要能匯出資料,且作法與學生一樣:
class ClassRoom implements Exportable
{
public $name = "class room";
public $location = "main building";
private $secret = "something secret";
public function exportArray(): array
{
// 匯出全部 property, 修正 key
$data = [];
foreach ((array)$this as $key => $val) {
$key = preg_replace('#^.*\0#', '', $key);
$data[$key] = $val;
}
return $data;
}
}
由於這邊匯出資料的作法與學生一模一樣,因此出現了重複的程式碼。為了消除重複的程式碼,一般會考慮:
我認為 trait 就是當你沒有其他更好的選擇的時候,才考慮使用:
trait CastToArrayExportTrait
{
public function exportArray(): array
{
// 匯出全部 property, 修正 key
$data = [];
foreach ((array)$this as $key => $val) {
$key = preg_replace('#^.*\0#', '', $key);
$data[$key] = $val;
}
return $data;
}
}
class StudentV3 implements Exportable
{
use CastToArrayExportTrait;
public $name = "student name";
protected $id = "student id";
}
class ClassRoomV2 implements Exportable
{
use CastToArrayExportTrait;
public $name = "class room";
public $location = "main building";
private $secret = "something secret";
}
最後整理一下個人認為的 trait 使用時機:
我自己的習慣是都用 service 處理,
因為需求多變,哪一天學生和老師就會有不一樣的需求 XD,
而 Trait 我會用在更核心的元件上,或是共用的功能模組,
因為元件相對商業邏輯而言不會變動那麼大,結構相對穩定,很多東西可以抽離共用,
以上是小弟的一些經驗,也還在學習摸索中。