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

时间:2011-10-11 01:23:00

标签: php model-view-controller oop design-patterns

我在这里发布了一个幽灵,这让我感到困惑多年。这是一个关于如何构建正确模型,正确对象的问题。

让我解释一下。假设我有一篇文章。文章有标题,评级,正文和评论。

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

文章可以有0个或更多评论。到现在为止还挺好。这个概念没问题。但...

  • 在展示文章时,我会展示一切。文章属性,包括其评论。
  • 在显示文章列表时,我只显示文章名称和一些正文副本。

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

我应该建两个型号吗?一个用于Article,一个用于ArticlesInList?我应该将评论的负载委托给懒惰模式(这可能),只在必要时检索它们吗?

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

THX。

3 个答案:

答案 0 :(得分:4)

在尝试为业务对象建模时,需要做出许多权衡。

考虑到你的例子,我可以想到一些方法,主要围绕延迟加载注释。如果我有理由相信事情不会变得更复杂,我就是这样做的:

首先,您为Article和Comment创建实体,它们只表示数据库中每个表中的数据。写二传手和吸气剂。在Article上实现loadComments()方法。

实现一个或多个Collection类,例如ArticleCollection。您可能有一个服务类可以获取符合某些条件的文章。 ArticleService :: fetchArticles()将返回没有加载注释的文章。然后实现ArticleCollection的loadComments()方法,该方法加载集合中所有文章的所有注释。首先,这可以迭代调用loadComments的文章 - 但您可以稍后用单查询实现替换它。

这是Article和ArticleCollection的开头。如果您实现了CommentCollection类,则可以使用Article中的内容来保存注释等。

<?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;
     }
}

以上是非常基本的 - 您可以应用其他设计模式来分解您的数据库访问,因此它不是那么紧密耦合。但我只是想以一种理智的方式描述一种懒惰加载你的评论的策略。

一些最终的建议:不要陷入许多框架所做的陷阱,并认为数据库和模型中的表之间存在一些神圣的相关性。模型只是对象。他们可以做不同类型的事情(代表一个简单的事情,如评论或用户),或代表像服务于这些简单事物的服务,或者它们可以是诸如个别事物的组(集合)之类的事物

一个有趣的练习就是编写类,并用虚拟数据填充它们。尽力完全忘记涉及数据库。支持您需要的用例的工艺对象。然后,完成后,找出如何在数据库中保存和加载数据。

答案 1 :(得分:0)

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

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

这是我的意见。我希望它可以帮到你。

答案 2 :(得分:0)

这取决于框架,但您的业务需求非常有意义。以下是我将如何构建事物(关于Agile Toolkit的逻辑):

业务逻辑

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

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');
    }
}

UI逻辑

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模板,其中包含诸如等等的标签