在 MVC 设计中构建正确的模型

Posted

技术标签:

【中文标题】在 MVC 设计中构建正确的模型【英文标题】:Building the right model in MVC design 【发布时间】:2011-12-04 22:45:13 【问题描述】:

我在这里发布了一个幽灵,这让我害怕了好几年。这是一个关于如何构建正确的模型、正确的对象的问题。

让我解释一下。假设我有一个班级文章。一篇文章有​​标题、等级、正文和 cmets。

Comment 类有作者、时间戳、文本。

一篇文章可以有 0 个或多个 cmets。到目前为止,一切都很好。这个概念没有问题。但是……

显示文章时,我会显示所有内容。文章属性,包括其 cmets。 在显示文章列表时,我只显示文章名称和一些正文。

这是我感到困惑的地方,因为我不需要加载 cmets 信息,当我有很多文章和大量 cmets 时,这会产生显着的性能差异。

我应该构建两个模型吗?一个用于文章,一个用于 ArticlesInList?我是否应该将 cmets 的负载委托给惰性模式(这可能),仅在必要时检索它们?

面对和解决这个问题的正确方法是什么?

谢谢。

【问题讨论】:

【参考方案1】:

在尝试为您的业务对象建模时需要做出很多权衡。

根据您的示例,我可以想到几种方法,主要围绕延迟加载 cmets。如果我有理由确定事情不会变得更复杂,我会这样做:

首先,您为文章和评论创建实体,它们仅表示数据库中每个表中的数据。编写 setter 和 getter。在 Article 上实现 loadComments() 方法。

实现一个或多个 Collection 类,例如 ArticleCollection。您可能有一个服务类来获取符合某些条件的文章。 ArticleService::fetchArticles() 将返回未加载 cmets 的文章。然后实现 ArticleCollection 的 loadComments() 方法,该方法加载集合中所有文章的所有 cmets。起初,这可以只遍历调用 loadComments 的文章——但您可以稍后将其替换为单个查询实现。

这里是 Article 和 ArticleCollection 的开头。如果你实现了一个 CommentCollection 类,你可以在 Article 中使用它来保存 cmets 等。

<?php
/**
 * Extends a base model class that provides database-related methods -- not ideal, 
 * but trying to stay focused here.
 */
class Article extends Model 
    private $_data;
    private $_fields = array('id','title','body','author');

    /** 
     * Constructor can take an array of values to initialize.
     */
    public function __construct($data=null)
        if (is_array($data))
            foreach($this->_fields as $field)
                $this->_data[$field] = $data[$field];
             
         
    

    public function getId() return $this->_data['id']; 
    // more getters, and setters, here.

    public function loadComments()
        $result = $this->query('SELECT * FROM Comment WHERE article_id = ' . $this->getId());
        $this->_comments = array();

        foreach($result as $c)
            //instantiate a new comment (imagine Comment's constructor is very similar to Article's
            $this->_comments[] = new Comment($c);
         
    


class ArticleCollection extends Model 
    /**
     * An array of Articles, indexed by article_id
     */
    private $_articles = array();

    /**
     * Naive implementation.  A better one would grab all article IDs from $this->_articles, and
     * do a single query for comments WHERE article_id IN ($ids), then attach them to the 
     * right articles.
     */
    public function loadComments()
        foreach($this->_articles as $a)
            $a->loadComments();
        
    

    /**
     * Add article to collection
     */
     public function addArticle(Article $article)
         if (empty($article->id)) throw new \Exception('Can\'t add non-persisted articles to articlecollection!');
         $this->_articles[$article->id] = $article;
     

以上内容非常基本——例如,您可以应用其他设计模式来分解您的数据库访问,这样它就不会那么紧密耦合。但我只是想以一种理智的方式在这里描述一种延迟加载您的 cmets 的策略。

一些最后的建议:不要陷入许多框架所做的陷阱,并认为数据库中的表和模型之间存在某种神圣的关联。模型只是对象。它们可以做不同类型的事情(表示一个简单的事情,如评论或用户),或表示对这些简单事物进行操作的服务之类的事情,或者它们可以是这些个体事物的组(集合)之类的事情.

一个有趣的练习是编写类,然后用虚拟数据填充它们。尽最大努力完全忘记将涉及数据库。制作支持您需要的用例的对象。然后,一旦你完成了,弄清楚如何将数据保存到/从数据库中加载。

【讨论】:

感谢您的时间。确实,您在最后的建议中直截了当。 你在父Model类中做什么?仅存储$db 对象?使用静态方法查询方法并在其中使用$db singleton 怎么样?【参考方案2】:

我认为没有最好的方法,但有些方法比其他方法更好。无论如何,这是我的两分钱:

首先,我想创建 2 个类,但一个用于文章,第二个用于 cmets。这样,您(在我看来)就符合Demetra 法则 (Law of Demetra)。现在,在您的控制器中,您可以检索文章列表(没有 cmets...性能还可以),并且,当您需要时,对于每篇文章,您可以使用模型 Comment 来检索链接的 cmets。此外,您必须牢记软件工程的这条原则:“低coupling,高cohesion

这是我的看法。希望对你有帮助。

【讨论】:

谢谢你的时间,朋友。抱歉,但这并没有解决我的问题,尽管你引用了一个或另一个原则,但大局只是错过了问题的核心。 @Dave 你不接受我的回答没有问题,但我想问你为什么说我没有抓住重点。如果您提取以下短语,您将获得问题的答案(好与否)。 “首先,我想创建 2 个类,但一个用于文章,第二个用于 cmets。(因此,两种类型的文章没有两个类)。现在,在您的控制器中,您可以检索文章列表(没有 cmets...性能还可以),并且,当您需要时,对于每篇文章,您可以使用模型 Comment 来检索链接的 cmets。” 您的答案,尽管它是正确的,但听起来像是关于设计原则的课程。我想读一些更有机的东西,没有太多的样板、原则和设计模式。我尽量不要粗鲁,希望你能理解。你的回答很好。在我看来,Timdev 的答案更好。 @Dave 别担心。正如我所说,我只会更好地理解你的观点。谢谢。【参考方案3】:

这取决于框架,但您的业务需求非常合理。以下是我将如何构建事物(基于敏捷工具包的逻辑):

业务逻辑

业务逻辑始终反映您的真实对象,例如文章和 cmets。它不受演示要求的影响:

class Model_Article extends Model_Table 
    function init()
        parent::init();
        $this->addField('title');
        $this->addField('rating')->type('int');
        $this->addField('body')->type('text');
    
    function getComments()
        return $this->add('Model_Comment')
            ->setMasterField('article_id',$this->get('id'));
    

class Model_Comment extends Model_Table 
    function init()
        parent::init();
        $this->addField('name');
        $this->addField('body')->type('text');
        $this->addField('article_id')->refModel('Model_Article');
    

界面逻辑

在Agile Toolkit 中,演示文稿由“页面”类控制。在您的情况下,您需要 2 个页面,尽管两个页面都依赖于两种模型:

class page_article extends Page 
    function init()
        parent::init();
        $m=$this->add('Model_Article')->loadData($_GET['id']);
        $this->add('View',null,null,array('view/article/body'))
            ->setModel($m);
        $this->add('MVCLister',null,null,array('view/article/comments'))
            ->setModel($m->getComments());
    

class page_article_comment extends Page 
        $m=$this->add('Model_Comment')->loadData($_GET['id']);
        $this->add('View',null,null,array('view/comment/header'))
            ->setModel($m->getRef('article_id'));
        $this->add('View',null,null,array('view/comment/full'))
            ->setModel($m);

此代码依赖于4个html模板,其中包含诸如等标签。

【讨论】:

谢谢你的时间,伙计,但是......这真的没有回答我的问题。我没有建模或实施的问题。这个问题在实现及其含义方面更具概念性。

以上是关于在 MVC 设计中构建正确的模型的主要内容,如果未能解决你的问题,请参考以下文章

控制器/模型上的 MVC 设计查询

传统的MVC设计模式

Spring MVC 完整示例

一步一步学毕业设计:简单的MVC登录

iOS开发设计模式之MVC

iOS中的MVC设计模式