代码之家  ›  专栏  ›  技术社区  ›  etheros

向mvc模型添加持久性的最佳实践是什么?

  •  4
  • etheros  · 技术社区  · 16 年前

    我正在用php实现一个超轻量级mvc框架。从数据库、文件等加载数据应该独立于模型,这似乎是一个普遍的观点,我同意。我不确定的是将这个“数据层”链接到mvc的最佳方式。


    数据存储与模型交互

    //controller
    public function update()
    {
    
     $model = $this->loadModel('foo');
     $data = $this->loadDataStore('foo', $model);
    
     $data->loadBar(9); //loads data and populates Model
     $model->setBar('bar');
     $data->save(); //reads data from Model and saves
    
    }
    

    控制器在模型和数据存储之间进行中介

    似乎有点冗长,需要模型知道数据存储存在。

    //controller
    public function update()
    {
    
     $model = $this->loadModel('foo');
     $data = $this->loadDataStore('foo');
    
     $model->setDataStore($data);
    
     $model->getDataStore->loadBar(9); //loads data and populates Model
     $model->setBar('bar');
     $model->getDataStore->save(); //reads data from Model and saves
    
    }
    

    数据存储扩展模型

    如果我们想保存一个将数据库数据存储扩展到flatfile数据存储的模型,会发生什么?

    //controller
    public function update()
    {
    
     $model = $this->loadHybrid('foo'); //get_class == Datastore_Database
    
     $model->loadBar(9); //loads data and populates
     $model->setBar('bar');
     $model->save(); //saves
    
    }
    

    模型扩展了数据存储

    这允许模型的可移植性,但像这样扩展似乎是错误的。此外,数据存储不能使用模型的任何方法。

    //controller extends model
    public function update()
    {
    
     $model = $this->loadHybrid('foo');  //get_class == Model
    
     $model->loadBar(9); //loads data and populates
     $model->setBar('bar');
     $model->save(); //saves
    
    }
    

    编辑:模型与dao通信

    //model
    public function __construct($dao)
    {
        $this->dao = $dao;
    }
    
    //model
    public function setBar($bar)
    {
        //a bunch of business logic goes here
        $this->dao->setBar($bar);
    }
    
    //controller
    public function update()
    {
        $model = $this->loadModel('foo');
        $model->setBar('baz');
        $model->save();
    }
    

    任何关于“最佳”选择或替代方案的意见都是最值得赞赏的。

    5 回复  |  直到 16 年前
        1
  •  7
  •   duffymo    16 年前

    我不认为它属于控制器——这是观点的一部分,它可以来和去。业务逻辑和持久性不应该依赖于视图或控制器。

    单独的服务层使您有机会将该逻辑作为分布式组件公开给非基于浏览器的客户端(例如,移动视图、批处理等)。

    我不会让模型从面向对象的角度扩展数据存储,因为继承是一种is-a关系。正如您所注意到的,模型不是一个关系数据库;这只是许多持久化信息的选择之一。

    我认为这是更多的构图。持久层可以是一个单独的dao,它可以持久化模型对象,而不要求它们知道它们的寿命更长。或者您有mixin行为,其中一个模型公布了它有crud操作的事实,但它只是将请求传递给给定的实现。

    通常的spring习惯用法是有一个独立于控制器的服务层。它知道用例和工作单元。它使用模型和持久性对象来实现用例的目标。

    mvc包含三个参与者。我想说的是,在一个典型的应用程序中,有更多的层需要添加持久性和服务。

    更新:

    如果模型对象没有从持久性包中导入任何内容,那么它根本不知道。下面是一个简单的示例,使用一个简单的模型类person和它的dao接口,每个都在它自己的包中:

    package persistence.model;
    
    public class Person
    {
        private String name;
    
        public Person(String name)
        {
            this.name = name;
        }
    
        public String getName()
        {
            return name;
        }
    
        public void setName(String name)
        {
            this.name = name;
        }
    }
    

    注意,dao接口导入模型包,但不是相反:

    package persistence.persistence;
    
    import persistence.model.Person;
    
    import java.util.List;
    
    public interface PersonDao
    {
        Person find(Long id);
        List<Person> find();
    
        void saveOrUpdate(Person p);
    }
    
        2
  •  1
  •   Gabor de Mooij    16 年前

    redbeanhp使用一个名为fuse的系统来解决这个问题。 (wiki上的详细信息: http://redbeanphp.com/community/wiki/index.php/Fuse )

    //Controller
    $foo = R::load("foo", 9);
    $foo->bar = "bar";
    R::store( $foo );
    

    在这里,你的模型实际上只是一个豆子。然而,该系统根据需要将bean与成熟的模型相结合。

    class Model_Foo extends RedBean_SimpleModel {
      public function update() {
        if ($this->bar!="bar") do something... 
      }
    }
    

    框架动态地将model_foo绑定到“foo”类型的bean,并在保存之前调用update()方法。这是您可以存储业务规则的地方。除了UpDebug()之外,还可以使用OPEN()和DeleTe()。

        3
  •  0
  •   Pras    16 年前

    我的投票是赞成走模型扩展数据存储的路线。这看起来很自然,因为实际上你的模型 底层数据存储的扩展。但是,您可能希望使用服务设计模式将模型对象的创建与结果对象的使用分离开来。

    class Model {
       protected $_tablename == __CLASSNAME__; // or some derivative of the classname
       protected $_datastore;
       protected $_typeOfDataStore;
    
       function __construct( $type == 'mysql' ) { 
          $this->_typeOfDataStore = $type;
          $this->bindToDataStore();
       }
    
       function bindToDataStore( $ds ) { 
          $this->_datastore = DataStoreFactory::buildDataStore( $this->_typeOfDataStore );
       }
    
    }
    
    class DataStoreFactory {
    
       function buildDataStore( $type ) {
         switch ( $type ) {
           case 'flatfile' : return new FlatFileDataStore();
           case 'mysql': return new MySQLDataStore();
         }
       }
    }
    
    class DataStoreBase {
       function connect() { }
       function disconnect() { }
       function getData() { }
       // ...
    } 
    
    class FlatFileDataStore extends DataStoreBase { }
    class MySQLDataStore extends DataStoreBase { 
       function runQuery() { }
    }
    

    那是建筑部分。当您需要创建/实现新模型时,可以简单地扩展模型:

    class Users extends Model {
       protected $_tablename = 'users';
       protected $_typeOfDataStore = 'flatfile';
    
       function getAllUsers() {
          return $this->runQuery( "SELECT * FROM " . $this->_tablename );
       }
    
    }
    

    在控制器中:

    $usersModel = new Users();
    $usersModel->getAllUsers();
    

    您指出的关于数据存储不能使用任何模型方法的异议是对系统的一个很好的约束。模型是数据存储的使用者,因此数据存储不能直接调用模型对象的函数。模型应该维护控件,并在需要时使用数据存储服务。

        4
  •  0
  •   staticsan    16 年前

    我认为你在试图构建一个与名字和概念联系太紧密的框架。我发现模型-视图-控制器习惯用法中的名称和概念没有严格定义。我还发现,在这种模式下工作的代码有时需要能够改变规则。

    @Duffymo对于模型组件有一个非常重要的观点:模型 不是 一个数据存储,它只是 有一个 数据存储。如果这还不清楚,另一种思考方法是看每个实例应该是什么。“datastore”对象的一个实例表示一个资源,该资源中介访问任意数量的数据。通常是数据库连接。“model”对象的一个实例通常是具有多个数据片段的离散标识。通常它表示表中的数据库行,但它可以是文本文件中的一行,也可以是文件存储中的一个文件。

    对控制器和视图应用相同的规则,你可以看到模型部分是一个完全不同的动物。虽然通常一次只存在一个控制器对象和一个视图对象,但很可能同时存在多个不同类型的不同模型对象。

    不幸的是,我也知道很多mvc“框架”将“模型”定义为api层,在这个api层后面可以执行sql语句。在这些框架中,“模型”是一个静态类或单个实例,只提供几乎无用的名称空间分区。很多程序员认为这应该是有意义的,并与之斗争。 这就是我推荐你的原因 不要 使用mvc习惯用法的名称和概念。

    我更喜欢的编写结构化php的方法只支持mvc范式。它的顶部有dispatcher和controller逻辑,底部有html标记。这工作得很好,因为控制器和视图通常是紧密耦合的,将它们放在同一个文件中非常类似php。是的,在视图ish代码中不执行控制器ish操作确实需要纪律,但我宁愿自己这样做,也不愿让框架强迫我这样做。然而,这些页面并不是独立的,它们包括许多常见的逻辑,可以导入诸如轻量级调度器系统之类的东西,甚至可以调用所有的通用控制器逻辑。

    但通用逻辑提供的主要功能是一个数据抽象层——基本上是模型层,如我的回答顶部所述。我写的数据抽象层基本上是三件事:一个数据库处理程序,一个“对象”对象和一个“集合”对象。

    数据库处理程序是数据库中所有对象的数据存储。它的主要目的是使不进行查询和获取响应的数据库活动都位于同一位置。大多数数据库调用都在对象核心中。数据库处理程序对对象核心一无所知:它只接受sql并返回resultset。

    另外两个对象被设计为子类,如果实例化它们自己,它们将不工作。使用继承和特殊的扩展声明(由静态类方法完成),他们知道如何 将数据库数据放入对象中。继承的“object”对象的实例表示声明类的一行。它在实例化时被赋予一个id,并在需要检索其一行数据时查询数据库。它还跟踪更改,并可以在被告知时进行一行更新。它所呈现的API完全没有SQL:对象有字段-& gt;GET()和-& gt;SET(),并且当你完成时,你可以& & G.

    “collection”对象知道如何获取特定对象的行的筛选列表,并知道如何将其转换为适当的单个对象,每个对象的函数就像它们是单独实例化的一样。(collection对象本身也支持成为对象,但是我很少使用这个特性,因为它目前有一些实现限制。我发现大多数程序员都很难理解这个概念。)

    大多数对象只需要核心继承代码和特定表的声明。

    最终的结果是 控制器代码 做一些显而易见的事情,比如:

    $ticket = Ticket::Create($ticket_id);
    $ticket->set('queue', $new_queue);
    $ticket->set('queue_changed', date('Y-m-d H:i:s'));
    $ticket->save();
    

    …而不是像这样不透明的东西:

    TicketModel::ChangeTicketQueue($ticket_id, $new_queue);
    

    这种方法还允许您在ticket对象中编写代码,当 queue 字段更改,并且在更改该字段时总是会发生,而不必记住在可以更改票的队列的每个函数中进行。这也意味着你可以在票上添加其他方法:这可能是有意义的。 $ticket->get_queue() return TicketQueue::Create($this->get('queue')); 为你。最好的面向对象编程!-)

    所以,为了最终回答你的问题,我建议 没有人 关于你的问题的方法。您的模型对象应该为控制器代码提供一个统一的api,并且取决于它们如何存储数据。如果是在数据库中,那么他们需要组织自己的电话来做到这一点。而是一个模型 不是 数据存储:但是,它很可能 一个。

        5
  •  0
  •   etheros    16 年前

    我最后用一个数据存储类来装饰我的模型:

    //datastore class
    public function __call($name, $arguments)
    {
    
        if (!method_exists($this->model, $name))
            throw new Exception('does not exist!');
    
        return call_user_func_array(array($this->model, $name), $arguments);
    
    }
    
    //controller - decorating
    $model = new Model;
    $datastore = new Datastore($model);
    $datastore->actions();
    
    推荐文章