模型类应该代表一个实体还是返回它

Posted

技术标签:

【中文标题】模型类应该代表一个实体还是返回它【英文标题】:Should a model class represent an entity or return it 【发布时间】:2012-08-03 16:45:05 【问题描述】:

我正在使用CodeIgniter 设计一个网络应用程序(但我认为这个问题通常适用于网络应用程序中的MVC 模式)。

当我为某些数据库实体设计模型类时(例如,BlogEntry 用于说明),我基本上有两种选择:

“经典 OOP”方法是,让类表示实体,即类的一个实例一个 BlogEntry。在 CodeIgniter 中,这会导致类似的代码

class Blogentry extends CI_Model 
    function load($id) 
      // Access database
      $this->id = $dbresult.id;
      $this->title = $dbresult.title;
      $this->author = $dbresult.author;
    

要访问一些博客条目,我会做一个$this->load->model('blogentry');,调用$this->blogentry->load($id),然后使用模型类实例。

我在野外经常看到的另一种方法是让模型返回某些数据结构中的实体。在代码中:

class Blogentry extends CI_Model 
  function get_entry($id) 
    // Access database, yielding $dbresult
    return $dbresult;

我会这样写

$this->load->model('blogentry');
$the_entry = $this->blogentry->get_entry($id);

在控制器中使用$the_entry。在第二种情况下,该类更像是factory 或builder 类。

这两种方法中的任何一种是否被认为是在 MVC 中执行 M 的“正确方法”?我想,我经常看到第二种方法,但我没有看到那么多 MVC-Webapps。任何想法为什么第一种或第二种方法可能更合适?

【问题讨论】:

参考this epic answer。 【参考方案1】:

首先,CodeIgniter 不是 MVC 框架。最适合的范例可能是 MVP 或类似 ORM-Template-Adapter 的东西。所以让我根据这个命名谬误分解我的答案:

在经典/传统 MVC(不是框架声称的 MVC)中,模型不是一个类或单个文件,而是包含所有领域业务逻辑的 一个层,通常仅由Domain Objects、Data Mappers 之类的东西组成,以及处理调用实体和域逻辑之间的接口的东西,以便将此类代码排除在控制器之外(例如在 MVC 中)。 Tereško names them "services",一个准官方的名字就够了。

所以在传统的 MVC 中,对模型层的请求是通过“服务”进入的,而这些服务又与域对象和数据映射器进行交互。

显然,所有这些都与 CodeIgniter 和其他框架所称的“MVC”无关。在这种设计模式(我简称为“CIMVC”)中,Model 是一个由 Controller 调用和操作的类,它直接与数据库交互。将数据存储在模型的成员变量中并没有太多的语义意义,但如果您对类似的东西感兴趣,请查看任何用 CIMVC 编写的 ORM 插件/库。 (虽然 ORM 解决方案在符合传统 MVC 方面不会更好......)

至于常见的做法,我见过的大多数 CI 应用程序会将“原始”数据库数据返回给控制器进行处理;这将域业务逻辑保留在模型中。我个人对 CIMVC 的偏好是尽量减少/消除模型中除领域业务逻辑之外的任何内容。

tl;dr - CodeIgniter 中的“传统/正确”MVC 基本上是不可能的,因此试图强制您的 CI 代码符合传统 MVC 范例是愚蠢的(并且可能会让您发疯)

编辑:为了澄清之前对模型层中业务逻辑的一些混淆,是的,您应该在模型层中拥有所有的业务逻辑。但是,如果正在进行额外的数据处理(排序、操作等),现在我们进入了违反 DRY 和 SRP 的危险区域;您希望模型是可重用的,因此要小心添加太多或如此专门的逻辑,以至于它在应用程序的其他部分变得无法使用。

【讨论】:

不同意模型中几乎没有业务逻辑,这正是业务逻辑应该驻留的地方(胖模型/瘦控制器)。这样就可以在需要它的应用程序中的任何地方共享 biz 逻辑。 @MikePurcell 你是对的,我显然没有很好地校对答案......该模型将处理所有域业务逻辑,但我试图开车回家的重点是关于 SOLID 编程,特别是 SRP。我会更新答案以更清楚。编辑:当我再次阅读该段时,我不知道我在想什么我在哪里写“业务逻辑”......这完全与我之前的想法相矛盾:P 大声笑,没有问题,我想肯定有人在你的水里滑了一些东西。 虽然我从未使用过 CI,但我在 Smalltalk/传统 MVC 定义中看不到任何不包括 Cakephp 和其他 MVC 框架的内容。在 Cake 中,模型层 主要由域对象和数据映射器组成。在 Web 框架中,模型层由模型类组成的事实是无关紧要的。它仍然是相同的架构模式。事实上,领域对象职责的项目符号列表几乎是对标准 Cake 模型的逐点描述。 @Lèsemajesté , in CakePHP “模型”是活动记录(ORM)的实例,它直接暴露给控制器。从而导致域业务逻辑在控制器中泄漏。那里没有。另外,您能否解释一下,您是如何得出结论的,php 框架实现了 MVC 模式?风景在哪里?【参考方案2】:

这是一个与 MVC 范式相关的通用答案,因为我在 CI 方面的经验为 0,但在 Symfony、Zend、Propel 和 Doctrine 方面有丰富的经验。

我发现,在使用原则时,在特定结构中返回对象数据最好添加一个指定所述结构的方法。例如,Doctrine_Record 对象有一个toArray() 方法,它允许我将对象转换为数组,因此我可以使用对象访问器,或调用toArray() 并将对象作为数组处理。

// Accessing object data through available methods
foreach ($object->categories() as $category) 
    var_dump((string) $category);


// Accessing object data as an array
$objectAsArray = $object->toArray(true);

foreach ($objectAsArray as $element) 
    var_dump($element['Category']);

最终,您访问数据的方式取决于您的偏好,完全取决于您希望如何处理数据以满足您的需求。在某些情况下,处理数组格式的数据会更快,因为对象水合往往会更多地影响性能。

无论你如何访问数据,我建议你将业务逻辑保留在模型层,这样你就不会重复代码(DRY:Don't Repeat Yourself)。

【讨论】:

据我所知,CodeIgniter 模型中没有类似的方法,但您可以使用(例如)从 db 调用中指定返回类型:$query->result() vs $query->result_array() @orourkek:感谢您的具体用法,我的回答更笼统,因为我不使用 CI。很高兴看到它们具有相同的灵活性。【参考方案3】:

首先要了解的是,CodeIgniter 与正确的 MVC 或受 MVC 启发的设计模式相去甚远。 CodeIgniter 实现的模式没有正式名称,但它与 Ruby on Rails 密切相关。

最严重的问题源于模型层的实现方式。通篇文档建议使用active record 模式,它本身就引入了几个设计问题:

Active Record vs Objects ORM anti-patterns - Part 1: Active Record Advanced OO Patterns [slides]

另一个主要问题是缺乏意见。视图应该是包含表示逻辑的实例。这包括选择使用哪些模板来表示从模型层获取的信息。 视图不是模板,它们是使用模板的结构。

在 CodeIgniter 之上创建适当的模型层的最佳方法是忽略 CI_Model,因为恕我直言,扩展此类没有任何好处。而是创建类似Services 的东西。这些结构可以充当与模型层交互的接口(不是 OO 意义上的)。每个服务可以处理多个domain objects和mappers。

基本上服务从控制器接收命令/消息,改变模型层的状态并提供从模型层提取信息的方法。通常提取会发生在 view 实例中,但这在 CodeIgniter 中是不可能的。信息的提取也会发生在“控制器”中,将数据传递给模板。

代码看起来像这样:

class Library 


    protected $current_document = null;

    public function acquire_document( $id )
    
        $document = load_class('Document', 'domain_object');
        $document->set_id( $id );

        $mapper = load_class('Document', 'persistence');
        $mapper->fetch( $document );

        $this->current_document = $document;
    

    public function get_document_information()
    
        $document = $this->current_document;        

        if ( $document === null )
        
            return 'empty';
            // you either did not acquire the document
            // or document was not found
        
        return $document->get_details();
    

MVC 设计模式实际上是两层的组合:模型层和表示层(应该主要由控制器和视图组成)。

控制器不应该直接接触域对象(它们是模型层的一部分),因为这意味着你有一个泄漏的抽象。

因此,总而言之,您所谓的“模型类”应该返回信息。

【讨论】:

您对 MVC 的描述与 Trygve Reenskaug 对 MVC 的描述并不真正匹配,也不符合 Smalltalk-80 MVC 类层次结构,其中有 模型类/对象控制器直接与之交互。 @Lèsemajesté,是的,我遵循 Martin Fowler 对 MVC 设计模式的 [1, 2] 的解释。另外你必须记住,这是在网络环境中,任何接近经典 MVC 的东西要么是不可能的,要么几乎是不可能的。

以上是关于模型类应该代表一个实体还是返回它的主要内容,如果未能解决你的问题,请参考以下文章

模型类(在 MVC 中)应该使用静态方法还是实例方法?

PHP MVC 原理

实体模型或 Pojo 类作为 REST API 的返回对象

内部类方法应该返回值还是只修改实例变量?

生日/死亡日期类应该是单个类的组合还是聚合?

实用程序类可以是 MVC 框架中的模型类吗?