如何更优雅的给控制器 “减负”

MVC是一个非常伟大的概念,但是最近我发现一个现象,包括我自己,我们在最开始接触MVC概念时,我们非常严谨地贯彻这种分层思想,Controller层处理业务逻辑,而Model层只是单纯的处理数据I/O。但是,伴随着我们项目体量的逐渐增大,控制器的负担也越来越大。这样一来会有一个非常明显的弊端,当我们在定位BUG时,我们总是需要对照着代码查看许久。除此之外,彼此的业务代码并没有太好的关联,这使得我们想要抽出一个Service时就显得极为困难。
因此,是时候给我们的控制器做一些“减负”了。这里的减负并不意味着会违背MVC的设计思想,而是把我们的控制器层的业务适当的分给其他部分。
有使用过一些主流框架的朋友应该都知道,其实很多框架都给Controller层做了一些“减负”的工作,比如KOA里面的middleware,抑或是Laravel里面的Event,Policy等。
但是事与愿违,即使这些框架提供了这些帮助,但是许多人在实际项目中使用到的却很少,当然,也有可能是我接触到的代码不够多。究其原因,窃以为尚未意识到这种理念的重要性。因此,我在这里总结了自己这些年来“减负”的一些经验,同时我也会配合一些代码予以解释。当然,我所写的未必全对,因此希望有幸看到的读者能保持自己的独立性。

Model分流

我们在写代码时往往会有这样一种场景,我们需要对从Model取出来的数据进行加工,但是,加工数据的部分我们经常会放到控制器,毕竟这属于业务逻辑,确实无可厚非,如下伪代码所示:

// controller
public function userList()
{
    $users = array_map(function ($user){
//        这里会对我们的代码进行业务逻辑的加工
        $user['created_at'] = date('Y-m-d' , $user['created_at']);
        // ...
        return $user;
    } ,$model->availableList());
}

// model
public function availableList()
{
    // 从数据库取数据  
    return $users;
}

但是我们有没有考虑过这样一个问题,当我们同事来接手我们项目或者我们debug时,我们需要了解的代码量非常大,特别是涉及到一些数据加工的格式问题,我们并不需要关心。或者换个角度,当我们遇到数据加工的bug时,我们能第一联想到这段代码是放在Model层时,是不是更加快捷呢?

// controller
public function userList()
{
    $users = $model->availableList();
//    处理其他逻辑
}

// model
public function availableList()
{
    // 从数据库取数据 $users
    return array_map(function ($user){
//        这里会对我们的代码进行业务逻辑的加工
        $user['created_at'] = date('Y-m-d' , $user['created_at']);
        // ...
        return $user;
    } , $users);
}

如上代码所示,在Model层中已经帮我们封装好了我们所需要的数据以及其格式,当我们在浏览他人代码时,我们并不需要关心他的格式是怎么加工的,我们只需要根据他对方法的命名就能知道是获取的怎样的数据。

分离Controller

在写具体的方法之前,我想要阐述的一点是,我们在写代码的时候需要保持一定的前瞻性。什么意思?虽然我们的大部分工作都是跟具体的业务逻辑打交道,但是我们经常会发现总会有重复的工作,那么有的人会直接把这段代码复制。但是,在我们复制之前,我们是不是可以问自己这样一个问题:如果接下来还有类似的业务,我们还是复制吗?我们是不是可以把这段基于我们项目的代码抽象出一个Service呢?
我举个例子,比如一个网站,可能会有打赏功能,可能也有付费阅读功能,我们不难发现,这两种付费有着相似的地方,比如创建本平台订单系统的业务逻辑,再比如回掉时可能存在的相同业务逻辑,所以这段代码我们是不是可以以一个trait的形式做一个Service

trait PayService
{
    private $_callback = null;

    public function createOrder()
    {
//        处理你的业务逻辑,配置调用三方支付接口的参数等
    }

    public function callback()
    {
//        处理共同的回调逻辑

        $this->handler();
    }
}

这里我们保留了一个handler方法来处理每个功能独有的业务逻辑,至此,我们就可以非常方便的扩展我们的支付服务了。

给控制器减负的方法还有很多,比如对我们加工数据的部分,其实我们也可以不放到Model,我们也可以单独开辟一层来处理我们的数据加工。让控制器变得清晰明朗,每个人阅读代码时都能非常快速的了解控制器下的每个方法在处理什么业务逻辑。这便是我们给控制器减负的目的。
我很喜欢「包」的概念和设计思想,当我们在使用包时,不仅仅意味着方便,更重要的是,他做为一个独立的“组件”存在于我们的代码逻辑中,与我们项目的代码不存在任何的耦合,同时我们也无需知道他的具体实现。

欢迎关注我的公众号,每周至少一篇比较有深度的原创文章:

如何更优雅的给控制器 “减负”

文章首发地址:我的博客

本作品采用《CC 协议》,转载必须注明作者和本文链接
Nine
本帖由系统于 6年前 自动加精
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 14

深有体会的是,第三方登录,普通登录,注册登等这些方式都是有相同的后置操作,我当时做的是将这些成功后的操作封装在Model中创建了一个successLogin()方法。

6年前 评论

我们公司,增加了服务层,和数据库打交道就在服务层中,不用model, 直接用DB::table()->

6年前 评论

@xuanjiang1985 增加服务层不意味着要放弃model啊 , eloquent那么强大的功能不用可惜了

6年前 评论

@xuanjiang1985 orm是很强大的功能,也是使得laravel这么慢的原因, 如果放弃orm,推荐使用lumen 框架

6年前 评论

在laravel开发中可以说不存在重复代码这种概念,任何多余的架构设计都是耍流氓! 我不是针对谁, 我就是针对的仓库层~
laravel其实使用的并不是mvc架构, 而是 MVCRPMMNRELH 层架构. 当然原来还有service层.

Request(表单验证), Policy(权限验证),Mail(邮件服务), Middleware, notification, resource event 层 尤其是事件层可以稀释大量的控制器的代码,用户不关心的副作用,基本上都可以丢到事件层,监听层继承queue可以集快增加响应速度

6年前 评论

我觉得时刻记得请求就是获取资源这个概念,而controller只是检查输入并调取资源,所以 controller 里代码会很少的,我觉得 controller 层不需要关注资源结构之类

5年前 评论

楼主的意思如果我没理解错的话应该是Repository
https://github.com/zhaohehe/z-repository

5年前 评论
Jourdon

写的不错,把 窃以为 改成 妾以为 就完美了!

5年前 评论

@Max
@ykenny
其实我写这篇文章并不是针对Laravel来的,而是说不管我们用什么框架和什么语言,当我们在处理业务逻辑时应该保持抽象和解耦的理念。

5年前 评论

可以看一哈这个视频,里面的做法也挺好的 https://www.youtube.com/watch?v=MF0jFKvS4S...

5年前 评论

@young 好的,感谢分享?

5年前 评论

各位.我之前也发现这个问题,所以,我把项目分成了servces,models,repository,controller,等层.并且做了几个项目.但是,我发现这么分代码量增加了很多,增加很多不必要的操作.后来我发现国外的coder和国内的有很多的分别.他们的代码短小精悍.我发现我虽然写了几个项目,但是对laravel 并不十分了解.看了laravel 的文档,我现在框架本身带了很多东西没用到.使用框 架自带的controller,model,transformer,request,eloqents,可以很好的处理问题了.另外看到很多人说eloqents不好用的.我以前也这么认为,现在我改变了看法.分享相关的视频给大家看看.https://www.youtube.com/channel/UC0dhWGm6PI0piTDbhtGlirQ/videos,希望与大家共同进步

4年前 评论

你代码数量总共有这么多,有bug时,你不都要去一个个定位?难道你放comtroller里要一行行去定位,那你放model里就不要去定位吗?

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!