Построение правильной модели в дизайне MVC - PullRequest
6 голосов
/ 11 октября 2011

Я выкладываю здесь призрака, который пугает меня несколько лет. Это вопрос о том, как построить правильную модель, правильные объекты.

Позвольте мне объяснить. Предположим, у меня есть класс статьи. Статья имеет название, рейтинг, основную копию и комментарии.

Комментарий класса имеет автора, метку времени, текст.

Статья может иметь 0 или более комментариев. Все идет нормально. Нет проблем с этой концепцией. Но ...

  • При показе статьи я показываю все. Свойства статьи, включая комментарии.
  • При отображении списка статей я показываю только название статьи и несколько основных текстов.

Здесь я запутался, потому что мне не нужно загружать информацию о комментариях, и это может существенно повлиять на производительность, когда у меня много статей и множество комментариев.

Должен ли я построить две модели? Один для статьи и один для ArticlesInList? Должен ли я делегировать загрузку комментариев в ленивый режим (это возможно), извлекая их только при необходимости?

Как правильно решить эту проблему?

Thx.

Ответы [ 3 ]

4 голосов
/ 11 октября 2011

При попытке смоделировать ваши бизнес-объекты нужно сделать много компромиссов.

Учитывая ваш пример, я могу вспомнить несколько подходов, которые в основном вращаются вокруг ленивых комментариев. Вот как бы я это сделал, если бы был уверен, что все не станет сложнее:

Сначала вы создаете сущности для Article и Comment, которые просто представляют данные в каждой таблице в вашей базе данных. Написать сеттеры и геттеры. Реализуйте метод loadComments () в статье.

Реализация одного или нескольких классов Collection, таких как ArticleCollection. У вас может быть класс обслуживания, который выбирает статьи, которые соответствуют некоторым критериям. ArticleService :: fetchArticles () будет возвращать статьи без загруженных комментариев. Затем реализуйте метод ArticleCollection loadComments (), который загружает все комментарии для всех статей в коллекции. Сначала это может просто перебирать статьи, вызывающие loadComments, но позже вы можете заменить его реализацией с одним запросом.

Вот начало статьи и коллекции статей. Если вы реализуете класс 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;
     }
}

Вышеприведенное довольно простое - вы можете применять другие шаблоны проектирования для разграничения доступа к базе данных, чтобы он, например, не был так тесно связан. Но я просто пытаюсь описать стратегию ленивой загрузки ваших комментариев здесь в здравом уме.

Несколько заключительных советов: не попадитесь в ловушку, которую делают многие фреймворки, и подумайте, что существует некоторая божественная корреляция между таблицами в вашей базе данных и моделями. Модели - это просто объекты. Они могут делать разные вещи (представлять простую вещь, например, комментарий или пользователя), или представлять вещи, такие как сервис, который работает с такими простыми вещами, или они могут быть такими, как группы (коллекции) этих отдельных вещей. .

Одно забавное упражнение - просто написать классы и заполнить их фиктивными данными. Постарайтесь полностью забыть, что база данных будет задействована. Создавайте объекты, которые поддерживают нужные вам варианты использования. Затем, как только вы это сделаете, выясните, как сохранить и загрузить данные в / из БД.

0 голосов
/ 12 октября 2011

Это зависит от структуры, но ваши бизнес-требования имеют смысл. Вот как я бы структурировал вещи (по логике 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 Logic

В Agile Toolkit представление управляется классами "Page". В вашем случае вам потребуется 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, которые содержат такие теги, как и т. Д.

0 голосов
/ 11 октября 2011

Я думаю, что нет лучшего способа, но есть способы, которые лучше других. В любом случае это мои два цента:

Прежде всего, я хотел бы создать 2 класса, но один для статей, а второй для комментариев. Таким образом, вы (по моему мнению) идете в соответствии с Законом Деметры ( Законом Деметры ). Теперь в вашем контроллере вы можете получить список статей (без комментариев ... хорошо с производительностью), и, когда вам нужно, для каждой статьи вы можете использовать модель Комментарий, чтобы получить связанные комментарии. Кроме того, вы должны иметь в виду этот принцип разработки программного обеспечения : "Низкая связь , высокая сцепление "

Это моё мнение. Я надеюсь, что это может помочь вам.

...