Laravel框架中使用Service模式

若将商业逻辑都写在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模式)