若将商业逻辑都写在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中解放,不仅更容易维护、更容易扩展、更容易重复使用,且更容易测试。