繁体   English   中英

模型对象是否应该在控制器类中实例化?

[英]Should a model object instantiate inside a controller class?

为了了解MVC,我写了以下内容:

//Model/Model.php
class Model
{
    private $dbh;

    public function __construct()
    {
        $this->dbh = dbConnect();
    }

    private function dbConnect()
    {
        //return database connection
    }

    public function crudMethod()
    {
        //interact with databse using $this->dbh
    }

}

//Controller/Controller.php
class Controller
{
    public $modelConnection;

    public function __construct()
    {
        $this->modelConnection = new Model();
    }
}


//View/index.php
$obj = new Controller;
$obj->modelConnection->crudMethod();

?>

查看我的代码,我觉得我完全不了解这里的要点。
控制器没有真正的价值,我不妨直接实例化Model类。

是否应该实例化Model类? 在控制器内部还是外部? 还是完全不好的做法? 我将如何改进此结构以支持MVC范例?

您的问题是您将信息放在不应该存在的图层中。 您的数据访问层不需要知道如何连接,他们只需要知道有正确配置的驱动程序即可从中获取数据。

同样,您将赋予控制器不需要的职责:创建模型。

为什么这不好?

想象一下,由于某种原因,您更改了模型类的依赖关系。 就像现在您必须同时将模型中的数据存储在两个不同的数据库中以实现冗余一样。

你会怎么做? 更改所有控制器并更改在其中实例化模型的方式?

要做很多工作! 而且容易出错。

那么,我该如何解决呢?

我喜欢使用依赖注入原理工厂模式来执行此操作。

为什么要建工厂?

使模型的创建与使用它的人脱钩。 我的默认模型工厂创建的模型仅需要一个数据库连接即可工作:

namespace MyApplication\Model\Factory;

use MyApplication\Storage\StorageInterface;

interface FactoryInterface {
    public function create($modelClassName);
}

class WithStorage implements FactoryInterface {
    const MODEL_NAMESPACE = '\\MyApplication\\Model\\';

    private $storage;

    public function __construct(StorageInterface $storage) {
        $this->storage = $storage;
    }

    public function create($modelClassName) {
        $refl = new \ReflectionClass(self::MODEL_NAMESPACE . $modelClassName);
        try {
            return $refl->newInstance($this->storage);
        } catch (\ReflectionException $e) {
            throw new RuntimeException("Model {$modelClassName} not found");
        }
    }
}

现在,对于需要存储的模型类,可以创建如下结构:

namespace MyApplication\Storage;

interface StorageInterface {
    public function insert($container, array $data);
    public function update($container, array $data, $condition);
    // etc..
}

class PdoStorage implements StorageInterface {
    private $dbh;
    public function __construct(\PDO $dbh) {
        $this->dbh = $dbh;
    }

    public function insert($container, array $data) {
        // impl. omitted
    }

    public function update($container, array $data, $condition) {
        // impl. omitted
    }
}

因此,如果您具有以下课程:

namespace MyApplication\Model;

use MyApplication\Storage\StorageInterface;
// Entity impl. will be omitted for brevity.
use MyApplication\Entity\EntityInterface; 

abstract class AbstractApplicationModel {
    private $containerName;
    private $storage;

    protected function __construct($name, StorageInterface $storage) {
        $this->containerName = (string) $name;
        $this->storage = $storage;
    }

    public function save(EntityInterface $entity) {
        // impl. omitted
    }

    public function delete(EntityInterface $entity) {
        // impl. omitted
    }
}

class UserModel extends AbstractApplicationModel {
    public function __construct(StorageInterface $storage) {
        parent::__construct('users', $storage);
    }
}

这样,我们就可以解决模型中的耦合问题。 好。

因此,有了这些,我如何才能获得一个准备好从控制器存储数据的模型组件?

直:

namespace MyApplication\Controller;

use MyApplication\Model\UserModel;
use MyApplication\Storage\PDOStorage;
use MyApplication\Entity\User;

class UserController {
    public function onCreate() {
        $model = new UserModel(new Storage(new \PDO(...))); // Here's our problem
        $entity = User::createFromArray([
            'name' => 'John',
            'surname' => 'Doe',
        ]);

        try {
            $model->save($entity);
        } catch (Exception $e) {
            echo 'Oops, something is wrong: ' . $e;
        }
    }
}

如果只有一种模型可以处理整个应用程序,那么您就可以开始使用。 但是您有几个,那么就会有问题。

如果我不再想将PDO用作存储驱动程序并与MySQLi一起使用怎么办? 如果我不想再使用RDBMS,而是想将数据存储在纯文本文件中怎么办?

这样,您将不得不更改所有控制器的实现(这违反了OCP )。 有很多重复的工作要做。 我讨厌这个!

等一下! 我们有一家怪异的工厂为我们创建模型! 所以,让我们使用它!

namespace MyApplication\Controller;

use MyApplication\Model\Factory\FactoryInterface;
use MyApplication\Entity\User;

abstract class AbstractController {
    private $modelFactory;

    public function __construct(ModelFactory $factory) {
        $this->modelFactory = $factory;
    }

    public function getModelFactory() {
        return $this->modelFactory;
    }
}

class UserController {
    public function onCreate() {
        $model = $this->getModelFactory()->create('UserModel'); // now it's better
        $entity = User::createFromArray([
            'name' => 'John',
            'surname' => 'Doe',
        ]);

        try {
            $model->save($entity);
        } catch (Exception $e) {
            echo 'Oops, something is wrong: ' . $e;
        }
    }
}

现在,要使所有这些协同工作,您必须在引导程序/前端控制器上进行一些设置:

use MyApplication\Storage\PDOStorage;
use MyApplication\Model\Factory\WithStorage as ModelFactory;

$defaultPDODriver = new \PDO(...); 
$defaultStorage = new PdoStorage($defaultPDODriver);
$defaultModelFactory = new ModelFactory($defaultStorage);

$controller = new UserController($defaultModelFactory);

现在,如果要将存储引擎更改为纯文本文件怎么办?

$defaultStorage = new PlainFileStorage('/path/to/file'); // just this

现在,如果要将存储引擎更改为一个具有2个不同数据库来保存相同数据的自定义实现,该怎么办?

$master = new PdoStorage(new \PDO(...));
$slave = new PdoStorage(new \PDO(.......));
$defaultStorage = new RedundancyStorage($master, $slave);

看到? 现在,您存储信息的方式与模型无关。

同样,如果您有一些疯狂的业务逻辑会根据设置更改模型的处理方式,则还可以更改模型工厂:

$defaultModelFactory = new SomeCrazyModelFactory(...);

您的控制器甚至都不知道您的模型已更改(当然,您必须尊重相同的接口才能互换执行此操作)。

这是一种可能的方式,或多或少是我的工作方式,但还有其他几种可能。

您在对模型的解释中混合了各种顾虑。 在Model-View-Controller中,模型是某种类型的知识 ,可以是表示领域概念的单个类,也可以是更复杂的结构,例如CMS上下文中的页面或文档。

但是,在您的示例中,Model类实际上只是数据库连接的Singleton持有者,这不是其目的。

数据库连接是跨MVC层提供的服务,最好应使用依赖项注入进行连接。 您不应在Controller代码中实例化服务。

在控制器中实例化Model对象是否有意义很大程度上取决于上下文,但是没有禁止它的规则。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM