iT邦幫忙

0

有關laravel service 模式與trait 的應用場景

laravel 有 service 模式封裝外部行為與商業邏輯

而php 本身物件的觀念也有trait 的概念,可以將部分class function 注入到其他class 當中以達到達到重複使用程式碼的用法。

就我覺得在實際使用情形當中,這兩個有點像......

所以想要請問大家都在如何的使用場景或者情境中,分別使用這些功能?

我google 到發現說trait 很強調"copy" 這件事情;而問了身旁友人有提說要看這使用情景中的重複程度而定? 然而這樣的重複程度該如何定義? traits 是要一模一樣才比較適合使用嗎?

舉一個實際例子來說
我有兩個實體:像是學生和老師; 這兩個實體都要有匯入和匯出的功能
就這兩者的匯出與匯入功能來說,他們分別就欄位、還有匯入匯出成功的訊息與導向以及匯入匯出的紀錄有不一樣外,其實其他幾乎一模一樣,這讓我有點猶豫到底該使用trait 還是 service,感覺兩個都可以用;但就不知道到底哪一種對於日後的維護擴充有益?

看更多先前的討論...收起先前的討論...
q00153 iT邦新手 3 級 ‧ 2017-12-06 15:19:15 檢舉
依照您舉的例子,應該使用 service 製作匯入匯出的功能,比較好的將控制與業務邏輯部份做分離,比如你要寄信,就要找到郵局, service 就是實做的那間郵局。

trait 您可以想成一種類似通用補丁的東西,對於大家都會用到的功能,可以用這個來"合併到"物件 (Class) 內,變成該物件的一部份,您可以想像成信封上的郵票。

以上是俺淺顯的理解,可能有錯誤的地方 O.O~

對了~可以參考下面大大的部落格,超專業的~
http://oomusou.io/laravel/laravel-architecture/
http://oomusou.io/php/php-trait/
r567tw iT邦研究生 5 級 ‧ 2017-12-06 19:27:49 檢舉
哈哈! 那個點燈坊有google 到,很清楚~~ 其實上來提問也是有點猶豫 因為好像也知道答案但也不太知道答案,自己寫程式的經驗太少;所以上來問問XD
r567tw iT邦研究生 5 級 ‧ 2017-12-06 19:29:27 檢舉
等等 你的Server 指的是service 嗎? php 有這種server 的東西和概念嗎?
q00153 iT邦新手 3 級 ‧ 2017-12-07 09:36:52 檢舉
@@....是我打錯字了。。。已修正
小克 iT邦新手 4 級 ‧ 2022-08-26 12:16:53 檢舉
點燈坊的文章已經移轉至下列位置

Laravel 的中大型專案架構
https://old-oomusou.goodjack.tw/laravel/architecture/

如何使用 Trait?
https://old-oomusou.goodjack.tw/php/php-trait/
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 個回答

4
weiclin
iT邦高手 4 級 ‧ 2017-12-06 16:49:54
最佳解答

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;
    }
}

由於這邊匯出資料的作法與學生一模一樣,因此出現了重複的程式碼。為了消除重複的程式碼,一般會考慮:

  1. 建立一個父類別來放共用的程式碼,但是學生與教室共用一個父類別,老師用另一個類別,這是很奇怪的設計。
  2. 把功能委派給其他類別,或是建立一個 helper 來使用,在這個例子算是可以接受,但是稍微麻煩了些。且每個用到的類別還是會有重複的程式去呼叫 helper。

我認為 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 使用時機:

  1. 沒有其他更好方法來處理重複的程式碼。
  2. 所有這些重複程式碼需要修改時,全部都要一起改。有時程式只是碰巧長的一樣,但可能會個別修改就不適用。
r567tw iT邦研究生 5 級 ‧ 2017-12-06 19:24:17 檢舉

哇! 太感謝你了~超用心的,還花時間做code 示例! 恩恩,我大概明白甚麼時候該用trait 甚麼時候該用service 了~~ 超級感謝你的教學與指導^^

1
小碼農米爾
iT邦高手 1 級 ‧ 2017-12-06 12:58:19

我自己的習慣是都用 service 處理,
因為需求多變,哪一天學生和老師就會有不一樣的需求 XD,
而 Trait 我會用在更核心的元件上,或是共用的功能模組,
因為元件相對商業邏輯而言不會變動那麼大,結構相對穩定,很多東西可以抽離共用,
以上是小弟的一些經驗,也還在學習摸索中。

我要發表回答

立即登入回答