开发者

Building the right model in MVC design

开发者 https://www.devze.com 2023-04-11 21:48 出处:网络
I\'m posting a ghost here, that daunts me for some years. It\'s a question about how to build the right model, the right objects.

I'm posting a ghost here, that daunts me for some years. It's a question about how to build the right model, the right objects.

Let me explain. Suppose I have a class Article. An Article has title, rating, body copy and comments.

The class Comment has author, timestamp, text.

An Article can have 0 or more comments. So far, so good. No problem with this concept. But...

  • When showing the Article, I show everything. Article properties, including its comme开发者_C百科nts.
  • When showing a list of articles, I show only article name and a few of body copy.

Here is where I got confused because I don't need to load the comments information, and this can make a significant performance difference when I have lots of articles and tons of comments.

Should I build two models? One for Article and one for ArticlesInList? Should I delegate the load of comments to a lazy mode (is this possible), retrieving them only when necessary?

What is the right way to face and solve this?

Thx.


There are a lot of trade-offs to be made when trying to model your business objects.

Given your example, I can think of a few approaches, that mostly revolve around lazy-loading comments. Here's how I'd do it, if I were reasonably sure that things weren't going to get more complex:

First, you create entities for Article and Comment that simply represent the data in each table in your database. Write setters and getters. Implement a loadComments() method on Article.

Implement one or more Collection classes, such as ArticleCollection. You might have a service class that fetches articles that match some criteria. ArticleService::fetchArticles() would return articles without comments loaded. Then implement a loadComments() method of ArticleCollection that loads all the comments for all the articles in the collection. At first, this can just iterate over the articles calling loadComments -- but you can replace it later with a single-query implementation.

Here's the beginning of an Article and ArticleCollection. If you implement a CommentCollection class, you could use that inside Article to hold the comments, etc.

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

The above is pretty basic -- you can apply other design patterns to factor out your database-access so it's not so tightly coupled, for instance. But I'm just trying to describe a strategy for lazy-loading your comments here in a sane way.

Some final advice: Don't fall into the trap that many framework do and think there is some divine correlation between tables in your database and models. Models are just objects. They can do different kinds of things (represent a simple thing like a comment, or a user), or represent things like a service that operates on those kinds of simple things, or they can be things like groups (collections) of those individual things.

One fun exercise is to just write up classes, and fill them up with dummy-data. Do your best to completely forget that a database will be involved. Craft objects that support the use-cases you need. Then, once you've got that done, figure out how to save and load the data to/from the DB.


I think there isn't a best way but there are ways that are better than others. Anyway this is my two cents:

First of all, I would like to create 2 classes but one for the articles and the second for the comments. In this way, you are (in my opinion) going in accord to the Law of Demetra (Law of Demetra). Now, in your controller, you can retrieve the article list (without comments...ok with performance) and, when you need, for every article, you can use the model Comment to retrieve the linked comments. In addition, you've to bear in mind this principle of the software engineering: "Low coupling, high cohesion"

This is my opinion. I hope it can help you.


It depends on the framework, but your business requirements make perfect sense. Here is how I would structure things (on the logic of Agile Toolkit):

Business Logic

Business logic is always reflecting your real-life objects such as articles and comments. It is not affected by the presentation requirements:

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 Logic

In Agile Toolkit presentation is controlled by "Page" classes. In your case you would need 2 pages, although both pages rely on both models:

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

This code relies on 4 HTML templates, which contains tags such as , etc.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号