MVC之患上肥胖症的Controller

来这边负责做的一个项目,用了一个叫做 Yii Framework 的 MVC 框架,刚开始的时候自以为结构很稳健(当然,是相对于现有的烂系统来讲)。但是随着对业务逻辑理解的深入,我开始意识到问题的严重,我错误地理解了 MVC 中的 Controller,想当然地根据以往的经验,把所有的业务逻辑都放在 Controller 的 action 中去实现,于是,每一个 Controller 的代码都上千行,越来越臃肿。

最终导致我下定决心对这两个月工作进行重构的,是一个对外开放 API 接口的需求。按照现在的架构,代码基本无法复用,我需要把很多功能再重复写一遍,这实在是无法接受的。面向对象编程不仅仅是课本上的名词啊!真正开始实践才发现要有面向对象意识,有全局观,是多么难得的一件事情。

一、到底什么是 MVC?

模型-视图-控制器(MVC)是一种设计模式。

MVC 的目标是将业务逻辑从用户界面的考虑中分离,这样开发者就可以更容易地改变每一部分而不会影响其他。在 MVC 中,Model 代表信息(数据)和业务规则;View 包含了用户界面元素,例如文本,表单等;Controller 则管理模型和视图中的通信。

MVC 在各种编程语言中均有实现,例如 J2EE 应用开发中,View 可能由 jsp 实现;Controller 是一个 servlet,现在一般用 Struts 实现;Model 则是由一个实体 Bean 来实现。

二、我遇到了什么问题?

Yii Framework 是一个流行的 PHP 框架,它借鉴了 Ruby on Rails 的 ActiveRecord(AR) 概念,数据库中的每一个 table 都可以用 AR 类来方便地进行增删改查操作,它把 AR 当做 Model,并推荐放在一个名为 models 的目录下面。于是,我在自动生成表对应的 AR 之后,便望文生义想当然地认为已经拥有了 Model 层。其实,AR只不过是 DAO (数据访问层),并不是 Model 层。

我们的业务几乎全放在了 Controller 里:对用户提交上来的表单进行各种逻辑判断,进行计算,实例化 AR 对数据进行存储…… 因为一个 Controller 中会有多个 action,每个 action 都有这样的业务处理,最后,我发现我的 Controller 代码已经超过了 1000 行。

突然有一天,leader 说我们这个系统要开放 API 给现有的旧系统调用,要给第三方接口。第三方只是要给定一个参数,本系统给出个结果值而已,这其中的业务处理它是不关心的。坏就坏在这里,Controller 已经实现了那些业务,但它是接受表单提交的,怎样能够也接受 SOAP 的 xml 文档呢?

Controller 和套套一样,应该越薄越好。它的职责应该只是接受用户的输入,然后立刻转发给别的类来处理。这样 Controller 只负责提供不同的接口,我们才能算是将业务逻辑分离出去,而分离出去的业务也很容易进行重用。分离出来的这部分业务由谁来处理呢?答案应该是 Model。

三、我是怎么解决的?

不要片面地理解了 Model,它不仅仅只是对数据表进行增删改查。是的,AR 是最自然的基于 DB table 的 model。对大多数应用而言,使用 AR 就足够了。但对于复杂分层还要开放接口的场景,我们需要考虑在 AR 基础上进一步写各种 model,封装基于各种领域的业务逻辑。

为了给 Controller 减肥,有很多重构工作需要做。我为每一个 Controller 抽象出了一个 Utility 类,每当需要进行业务处理时就实例化调用这个类,同时 API 接口部分也会使用到这个类,从而保证了面向对象代码复用。

重构过程中遇到很多设计模式方面的问题,这篇文章给出了多参数方法的重构方案,受益匪浅。

Auer 曾在文献【AUER95】中指出重构时应当遵守的原则:

  1. 应当根据行为而不是状态定义一个类。一个类的实现首先建立在行为的基础之上,而不是建立在状态的基础之上。
  2. 在实现行为时,是用抽象状态而不是用具体状态。如果一个行为涉及到对象的状态时,使用间接的引用而不是直接的引用。换言之,应当使用取值方法而不是直接引用属性。
  3. 给操作划分层次。一个类的行为应当放到一个小组核心方法(Kernel Methods)里面,这些方法可以很方便地在子类中加以置换。
  4. 将状态属性的确认推迟到子类中。不要在抽象类中过早地声明属性变量,应将它们尽量地推迟到子类中去声明。在抽象超类中,如果需要状态属性的话,可以调用抽象的取值方法,而将抽象的取值方法的实现放到具体子类中。

四、得到的启示

Yii Framework 的官方文档中有这么一段——

In a well-designed MVC application, controllers are often very thin, containing probably only a few dozen lines of code; while models are very fat, containing most of the code responsible for representing and manipulating the data.

简言之,Rich Model is Better

参考链接:
我们丢失了 Model 层
MVC演化史