在 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 设计中构建正确的模型的主要内容,如果未能解决你的问题,请参考以下文章