iT邦幫忙

2023 iThome 鐵人賽

DAY 14
0
自我挑戰組

我推的Laravel系列 第 14

【Day-13】我推的Laravel-進階篇-Service & Repository Pattern

  • 分享至 

  • xImage
  •  

簡介

上篇介紹完SOLID,本想接著介紹Service Container & Service Provider
但想想,一直在理論方面可能太過乏味(絕對不是筆者想偷懶

Service 和 Repository 分別有不同的定義,要不要用取決於個人及專案,也可以只用其中一個

如果兩者同時存在,順序一般是
Controller → Service → Repository

  • Service : 資料處理、呼叫第三方、Repository等等
  • Repository : 整理要處理的query,查詢條件、排序、page等

實作

以先前的PostController:index為例:
https://ithelp.ithome.com.tw/upload/images/20230926/20163286Eq86zhNaRl.png
先以API測試
https://ithelp.ithome.com.tw/upload/images/20230926/20163286Pjo7jVMx5S.png
這是原始測試結果

PostRepository

app\Repositories\PostRepository.php
https://ithelp.ithome.com.tw/upload/images/20230926/20163286A4nTNX9Zu2.jpg

<?php 
namespace App\Repositories;

use App\Models\Post;
use Illuminate\Support\Collection;

class PostRepository
{
    public function get() : Collection
    {
        return Post::orderBy('id', 'desc')->get();
    }
}

PostService

app\Services\PostService.php
https://ithelp.ithome.com.tw/upload/images/20230926/20163286A4Qv9QRvV1.jpg
請手動建立檔案及資料夾

<?php 
namespace App\Services;

use App\Repositories\PostRepository;
use Illuminate\Support\Collection;

class PostService
{
    public function get(): Collection
    {
        $postRepository = new PostRepository;
        return $postRepository->get();
    }
}

PostController

引用PostService()

use App\Services\PostService;
public function index() {
    // $posts = Post::all();
    // return view('posts.index', ['posts' => $posts]);

    $postService = new PostService();
    $posts = $postService->get();

    return response()->json(['data' => $posts], 200);
}

測試結果(排序變了):
https://ithelp.ithome.com.tw/upload/images/20230926/20163286KDUMU2NbzP.jpg

咦?

看起來各個模組已經完成了,但是聰明的你可能看出來了

$postService = new PostService();
$postRepository = new PostRepository;
這兩行並不符合上篇提到的依賴反轉原則

為甚麼?

因為new PostService()已經將其實例化,而不是使用它的抽象
還記得檯燈和按鈕的例子嗎,PostRepository在這裡是檯燈,PostService是按鈕
所以PostService不該實例化PostRepository,而是依靠依賴注入

我們先把程式碼修改一下,再來討論問題

改用依賴注入

透過Laravel的依賴注入,注入(inject)了一個反射(reflection: PostService or PostRepository)給$postService

PostController

public function index(PostService $postService) {
    // $posts = Post::all();
    // return view('posts.index', ['posts' => $posts]);

    // $postService = new PostService();

    $posts = $postService->get();

    return response()->json(['data' => $posts], 200);
}

PostService

<?php 
namespace App\Services;

use App\Repositories\PostRepository;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;

class PostService
{
    private $postRepository;

    public function __construct(PostRepository $postRepository)
    {
        $this->postRepository = $postRepository;
    }

    public function get(): array
    {
        try {
            return $this->postRepository->get()->toArray();
        } catch (\Throwable $th) {
            Log::info($th);
            return [];
        }
    }
}

這邊增加了try catch,改變回傳格式,演示Service的角色定位

PostRepository

<?php 
namespace App\Repositories;

use App\Models\Post;
use Illuminate\Database\Eloquent\Collection;

class PostRepository
{
    public function getOrderByID() : Collection
    {
        return Post::orderBy('id', 'desc')->get();
    }
}

會有甚麼問題?

以這個範例來說,程式碼確實都能正常運作,沒有問題
如果只說這違反了依賴反轉原則,筆者自己也無法接受
問了幾次ChatGPT也是一直鬼打牆

以下就筆者自己的親身經驗及心得:
假設今天PostService中使用五個Repositories
你可以在任何地方new Repository()沒錯
但是今天新來一個技術經理說:「這個專案耦合性太高了,必須解耦」
眾人:「???耦合是甚麼,能吃嗎」
剛好那個技術經理叫做Ryan(筆者
技術經理:「給我解就對了!」
技術經理:「(拿了自己的技術文章給各位看,誇讚自己好棒棒),現在懂了吧,給我解!」
OK,問題來了
所有用new PostService()、Repository()的地方通通要改成

public function __construct(PostRepository $postRepository, UserRepository $userRepository)
{
    $this->postRepository = $postRepository;
    $this->userRepository = $userRepository;
}

就頭痛了
那你可能說,我們專案就打死都不碰依賴注入,可行嗎?
Maybe、Maybe not,取決於專案是否有一天會遇到,測試、維護、介面化的痛點
可以說是與其花時間做、遵循規範,避免未來的麻煩

百利而無一害?
SOLID痛點

  1. 單一職責原則(Single Responsibility Principle - SRP):一個類別應該只有一個原因需要變動。這有助於保持類別的簡單性和易於測試,但過度拆分可能導致過多的小類別,難以維護。
  2. 開放/封閉原則(Open/Closed Principle - OCP):軟體實體(類別、模組等)應該是可擴展的,但不可修改的。這鼓勵使用抽象和介面,但有時需要額外的代碼來實現擴展性。
  3. 里氏替換原則(Liskov Substitution Principle - LSP):子類別應該能夠替換其基類別而不影響程序的正確性。這要求符合繼承和多態性的設計,但錯誤的實現可能導致不正確的行為。
  4. 介面隔離原則(Interface Segregation Principle - ISP):不應該強迫類別實現它們不需要的接口。這有助於避免過多的依賴和不必要的方法,但可能需要更多的介面定義。
  5. 依賴反轉原則(Dependency Inversion Principle - DIP):高層次的模組不應該依賴於低層次的模組,兩者都應該依賴於抽象。這有助於實現鬆散耦合,但可能需要引入更多的介面和抽象。
    by ChatGPT

總結

本來想偷懶,結果又寫了幾個小時
主要是筆者不想都是以這是理論規則就是這樣的方式說服讀者
過去也是這樣說服自己,這是理論,記住、會用就好
但心中就是有個聲音:別告訴我為甚麼,告訴我好和不好的例子
Anyway,今天介紹手動新增Service、Repository,以及依賴注入到高層組件使用

下篇真的會接著介紹Service Container,繼續深入依賴注入、依賴反轉的課題
也許會更進一步了解到解耦的重要性


上一篇
【Day-12】我推的Laravel-進階篇-OOP & SOLID
下一篇
【Day-14】我推的Laravel-進階篇-Service Container & Service Provider
系列文
我推的Laravel31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言