美文网首页
Laravel框架中使用Service模式

Laravel框架中使用Service模式

作者: 陈德良EGG | 来源:发表于2019-08-13 09:44 被阅读0次

    若将商业逻辑都写在controller,会造成controller肥大而难以维护,基于SOLID原则,我们应该使用Service模式辅助controller,将相关的商业逻辑封装在不同的service,方便中大型项目的维护。

    Version

    Laravel 5.1.22

    商业逻辑

    商业逻辑中,常见的如:

    牵涉到外部行为:如发送Email,使用外部API…。

    使用PHP写的逻辑:如根据购买的件数,有不同的折扣。

    若将商业逻辑写在controller,会造成controller肥大,日后难以维护。

    Service

    牵涉到外部行为

    如发送Email,初学者常会在controller直接调用Mail::queue():

    publicfunctionstore(Request $request)

    {

    Mail::queue('email.index', $request->all(),function(Message $message){

    $message->sender(env('MAIL_USERNAME'));

    $message->subject(env('MAIL_SUBJECT'));

    $message->to(env('MAIL_TO_ADDR'));

        });

    }

    在中大型项目,会有几个问题:

    将牵涉到外部行为的商业逻辑写在controller,造成controller的肥大难以维护。

    违反SOLID的单一职责原则:外部行为不应该写在controller。

    controller直接相依于外部行为,使得我们无法对controller做单元测试。

    比较好的方式是使用service:

    将外部行为注入到service。

    在service使用外部行为。

    将service注入到controller。

    EmailService.php

    app/Services/EmailService.php

    namespaceApp\Services;

    useIlluminate\Mail\Mailer;

    useIlluminate\Mail\Message;

    classEmailService

    {

    /**@varMailer */

    private$mail;

    /**

        * EmailService constructor.

    *@paramMailer $mail

        */

    publicfunction__construct(Mailer $mail)

        {

    $this->mail = $mail;

        }

    /**

        * 發送Email

    *@paramarray $request

        */

    publicfunctionsend(array $request)

        {

    $this->mail->queue('email.index', $request,function(Message $message){

    $message->sender(env('MAIL_USERNAME'));

    $message->subject(env('MAIL_SUBJECT'));

    $message->to(env('MAIL_TO_ADDR'));

            });

        }

    }

    第 8 行

    /**@varMailer */

    private$mail;

    /**

    * EmailService constructor.

    *@paramMailer $mail

    */

    publicfunction__construct(Mailer $mail)

    {

    $this->mail = $mail;

    }

    将相依的Mailer注入到EmailService。

    20 行

    /**

    * 發送Email

    *@paramarray $request

    */

    publicfunctionsend(array $request)

    {

    $this->mail->queue('email.index', $request,function(Message $message){

    $message->sender(env('MAIL_USERNAME'));

    $message->subject(env('MAIL_SUBJECT'));

    $message->to(env('MAIL_TO_ADDR'));

        });

    }

    将发送Emai的商业逻辑写在send()。

    不是使用Mail facade,而是使用注入的$this->mail

    UserController.php

    app/Http/Controllers/UserController.php

    namespaceApp\Http\Controllers;

    useApp\Http\Requests;

    useIlluminate\Http\Request;

    useMyBlog\Services\EmailService;

    classUserControllerextendsController

    {

    /**@varEmailService */

    protected$emailService;

    /**

        * UserController constructor.

    *@paramEmailService $emailService

        */

    publicfunction__construct(EmailService $emailService)

        {

    $this->emailService = $emailService;

        }

    /**

        * Store a newly created resource in storage.

    *@param\Illuminate\Http\Request  $request

    *@return\Illuminate\Http\Response

        */

    publicfunctionstore(Request $request)

        {

    $this->emailService->send($request->all());

        }

    }

    第9行

    /**@varEmailService */

    protected$emailService;

    /**

    * UserController constructor.

    *@paramEmailService $emailService

    */

    publicfunction__construct(EmailService $emailService)

    {

    $this->emailService = $emailService;

    }

    将相依的EmailService注入到UserController。

    22行

    /**

    * Store a newly created resource in storage.

    *@param\Illuminate\Http\Request  $request

    *@return\Illuminate\Http\Response

    */

    publicfunctionstore(Request $request)

    {

    $this->emailService->send($request->all());

    }

    从原本直接相依于Mail facade,改成相依于注入的EmailService

    改用这种写法,有几个优点

    将外部行为写在service,解决controller肥大问题。

    符合SOLID的单一职责原则:外部行为写在service,没写在controller。

    符合SOLID的依赖反转原则:controller并非直接相依于service,而是将service依赖注入进controller。

    使用PHP写的逻辑

    如根据购买的件数,有不同的折扣,初学者常会在controller直接写if…else逻辑。

    publicfunctionstore(Request $request)

    {

    $qty = $request->input('qty');

    $price =500;

    if($qty ==1) {

    $discount =1.0;

    }

    elseif($qty ==2) {

    $discount =0.9;

    }

    elseif($qty ==3) {

    $discount =0.8;

    }

    else{

    $discount =0.7;

    }

    $total = $price * $qty * $discount;

    echo($total);

    }

    在中大型项目,会有几个问题:

    将PHP写的商业逻辑直接写在controller,造成controller的肥大难以维护。

    违反SOLID的单一职责原则:商业逻辑不应该写在controller。

    违反SOLID的单一职责原则:若未来想要改变折扣与加总的算法,都需要改到此method,也就是说,此method同时包含了计算折扣与计算加总的职责,因此违反SOLID的单一职责原则。

    直接写在controller的逻辑无法被其他controller使用。

    比较好的方式是使用service。

    将相依物件注入到service。

    在service写PHP逻辑使用相依物件。

    将service注入到controller。

    OrderService.php

    app/Services/OrderService.php

    namespaceApp\Services;

    classOrderService

    {

    /**

        * 計算折扣

    *@paramint $qty

    *@returnfloat

        */

    publicfunctiongetDiscount($qty)

        {

    if($qty ==1) {

    return1.0;

    }elseif($qty ==2) {

    return0.9;

    }elseif($qty ==3) {

    return0.8;

    }else{

    return0.7;

            }

        }

    /**

        * 計算最後價錢

    *@paraminteger $qty

    *@paramfloat $discount

    *@returnfloat

        */

    publicfunctiongetTotal($qty, $discount)

        {

    return500* $qty * $discount;

        }

    }

    第 5 行

    /**

    * 計算折扣

    *@paramint $qty

    *@returnfloat

    */

    publicfunctiongetDiscount($qty)

    {

    if($qty ==1) {

    return1.0;

    }elseif($qty ==2) {

    return0.9;

    }elseif($qty ==3) {

    return0.8;

    }else{

    return0.7;

        }

    }

    为了符合SOLID的单一职责原则,将计算折扣独立成getDiscount(),将PHP写的判断逻辑写在里面。

    23行

    /**

    * 計算最後價錢

    *@paramint $qty

    *@paramfloat $discount

    *@returnfloat

    */

    publicfunctiongetTotal($qty, $discount)

    {

    return500* $qty * $discount;

    }

    为了符合SOLID的单一职责原则,将计算加总独立成getTotal(),将PHP写的计算逻辑写在里面。

    OrderController.php

    app/Http/Controllers/OrderController.php

    namespaceApp\Http\Controllers;

    useApp\Http\Requests;

    useApp\MyBlog\Services\OrderService;

    useIlluminate\Http\Request;

    classOrderControllerextendsController

    {

    /**@varOrderService */

    protected$orderService;

    /**

        * OrderController constructor.

    *@paramOrderService $orderService

        */

    publicfunction__construct(OrderService $orderService)

        {

    $this->orderService = $orderService;

        }

    /**

        * Store a newly created resource in storage.

    *@param\Illuminate\Http\Request  $request

    *@return\Illuminate\Http\Response

        */

    publicfunctionstore(Request $request)

        {

    $qty = $request->input('qty');

    $discount =$this->orderService->getDiscount($qty);

    $total =$this->orderService->getTotal($qty, $discount);

    echo($total);

        }

    }

    第 9 行

    /**@varOrderService */

    protected$orderService;

    /**

    * OrderController constructor.

    *@paramOrderService $orderService

    */

    publicfunction__construct(OrderService $orderService)

    {

    $this->orderService = $orderService;

    }

    将相依的OrderService注入到UserController。

    21行

    /**

    * Store a newly created resource in storage.

    *@param\Illuminate\Http\Request  $request

    *@return\Illuminate\Http\Response

    */

    publicfunctionstore(Request $request)

    {

    $qty = $request->input('qty');

    $discount =$this->orderService->getDiscount($qty);

    $total =$this->orderService->getTotal($qty, $discount);

    echo($total);

    }

    将原本的if…else逻辑改成呼叫OrderService,controller变得非常干净,也达成原本controller接收HTTP request,调用其他class的责任。

    改用这种写法,有几个优点:

    将PHP写的商业逻辑写在service,解决controller肥大问题。

    符合SOLID的单一职责原则:商业逻辑写在service,没写在controller。

    符合SOLID的单一职责原则:计算折扣与计算加总分开在不同method,且归属于OrderService,而非OrderController。

    符合SOLID的依赖反转原则:controller并非直接相依于service,而是将service依赖注入进controller。

    其他controller也可以重复使用此段商业逻辑。

    Controller

    牵涉到外部行为

    publicfunctionstore(Request $request)

    {

    $this->emailService->send($request->all());

    }

    使用PHP写的逻辑

    publicfunctionstore(Request $request)

    {

    $qty = $request->input('qty');

    $discount =$this->orderService->getDiscount($qty);

    $total =$this->orderService->getTotal($qty, $discount);

    echo($total);

    }

    若使用了service辅助controller,再搭配依赖注入与service container,则controller就非常干净,能专心处理接收HTTP request,调用其他class的职责了。

    Conclusion

    实务上会有很多service,须自行依照SOLID原则去判断是否该建立service。

    Service使得商业逻辑从controller中解放,不仅更容易维护、更容易扩展、更容易重复使用,且更容易测试。

    相关文章

      网友评论

          本文标题:Laravel框架中使用Service模式

          本文链接:https://www.haomeiwen.com/subject/fezmjctx.html