当前位置:  开发笔记 > 编程语言 > 正文

设计模式:如何仅在需要时创建数据库对象/连接?

如何解决《设计模式:如何仅在需要时创建数据库对象/连接?》经验,为你挑选了1个好方法。

我有一个简单的应用程序,说它有一些类和一个处理数据库请求的"额外".目前我每次使用应用程序时都会创建数据库对象,但在某些情况下,不需要数据库连接.我这样做(PHP顺便说一句):

$db = new Database();    
$foo = new Foo($db); // passing the db

但有时该$foo对象不需要db访问,因为只调用没有数据库操作的方法.所以我的问题是:处理这种情况的专业方法是什么/如何仅在需要时创建数据库连接/对象?

我的目标是避免不必要的数据库连接.



1> Jimbo..:

注意:尽管ops 的直接答案是问题,"我什么时候才能在需要时创建/连接到数据库,而不是在每次请求时" 在需要时注入它,只是说这没有帮助.我在这里解释你是如何正确地解决这个问题的,因为在非特定框架环境中确实没有很多有用的信息来帮助这方面.


更新:此问题的"旧"答案如下所示.这鼓励了服务定位器模式,这是非常有争议的,并且很多都是"反模式".新的答案补充了我从研究中学到的东西.请先阅读旧答案,看看这是如何进展的.

新答案

使用疙瘩一段时间后,我学到了很多关于它是如何工作的,以及它如何并不是真正是惊人的毕竟.它仍然很酷,但它只有80行代码的原因是因为它基本上允许创建一个闭包数组.疙瘩被广泛用作服务定位器(因为它在实际上可以做到如此有限),这是一种"反模式".

首先,什么是服务定位器?

服务定位器模式是软件开发中使用的设计模式,用于封装获得具有强抽象层的服务所涉及的过程.此模式使用称为"服务定位器"的中央注册表,根据请求返回执行特定任务所需的信息.

我在引导程序中创建了疙瘩,定义了依赖项,然后将此容器传递给我实例化的每个单独的类.

为什么服务定位器不好?

你说这有什么问题?主要问题是这种方法隐藏了类的依赖关系.因此,如果开发人员要更新此类并且之前没有看过它们,他们将会看到包含未知数量对象的容器对象.此外,测试这个课程将是一个噩梦.

我为什么最初这样做?因为我认为控制器开始执行依赖注入之后.这是错的.你可以直接在控制器级别启动它.

如果这是我的应用程序中的工作方式:

前端控制器 - > Bootstrap - > 路由器 - > 控制器/方法 - > 模型[服务|域对象|映射器] - > 控制器 - > 视图 - > 模板

...然后依赖注入容器应该立即开始在第一个控制器级别工作.

所以,如果我仍然使用疙瘩,我将定义将要创建哪些控制器,以及它们需要什么.因此,您可以将视图和任何内容从模型层注入控制器,以便它可以使用它.这是控制反转,使测试更容易.来自Aurn wiki,(我将很快谈到):

在现实生活中,您不会通过将整个五金店(希望)运送到施工现场来建造房屋,以便您可以访问所需的任何部件.取而代之的是,工头(__construct())请求,将需要的特定部位(门窗),去了解他们采购.你的对象应该以相同的方式运作; 他们应该只询问完成工作所需的具体依赖性.让房子进入整个五金店是最糟糕的OOP风格,最糟糕的是可维护性的噩梦. - 来自Auryn Wiki

输入Auryn

关于这一点,我想向您介绍一些精彩称为Auryn,以书面Rdlowrey,我被介绍给周末.

Auryn'自动连接'类依赖于基于类构造函数签名.这意味着,对于每个请求的类,Auryn发现它,计算出它的构造函数需要,创建它需要什么,然后再创建你问原来是类的一个实例.以下是它的工作原理:

Provider基于其构造方法签名中指定的参数type-hints递归地实例化类依赖项.

...如果你对PHP的反思有所了解,你会发现有些人称之为'慢'.所以这就是Auryn对此的看法:

你可能听说过"反思很慢".让我们清楚一点:如果你做错了,任何东西都可能"太慢".反射比磁盘访问快一个数量级,比从远程数据库检索信息(例如)快几个数量级.此外,如果您担心速度,每次反射都可以缓存结果.Auryn缓存它产生的任何反射,以最大限度地减少潜在的性能影响.

所以现在我们已经跳过了"反思很慢"的论点,这就是我一直在使用它的方式.

我如何使用Auryn

我让Auryn成为我自动加载器的一部分.这样,当一个类被要求时,Auryn可以离开并读取类和它的依赖项,它是依赖项的依赖项(等),并将它们全部返回到类中进行实例化.我创建了Auyrn对象.

$injector = new \Auryn\Provider(new \Auryn\ReflectionPool);

我在数据库类的构造函数中使用数据库接口作为要求.所以我告诉Auryn要使用哪个具体实现(如果要在代码中的单个点实例化不同类型的数据库,这是您更改的部分,并且它仍然可以工作).

$injector->alias('Library\Database\DatabaseInterface', 'Library\Database\MySQL');

如果我想改成MongoDB并且我为它编写了一个类,我只需要Library\Database\MySQL改为Library\Database\MongoDB.

然后,我将其传递$injector到我的路由器,并在创建控制器/方法时,这是自动解决依赖关系的地方.

public function dispatch($injector)
{
    // Make sure file / controller exists
    // Make sure method called exists
    // etc...

    // Create the controller with it's required dependencies
    $class = $injector->make($controller);
    // Call the method (action) in the controller
    $class->$action();
}

最后,回答OP的问题

好了,所以使用这种技术,让我们说你有用户控制器,它要求用户服务(让我们说的usermodel),这需要访问数据库.

class UserController
{
    protected $userModel;

    public function __construct(Model\UserModel $userModel)
    {
        $this->userModel = $userModel;
    }
}

class UserModel
{
    protected $db;

    public function __construct(Library\DatabaseInterface $db)
    {
        $this->db = $db;
    }
}

如果您使用路由器中的代码,Auryn将执行以下操作:

使用MySQL作为具体类(boostrap中的别名)创建Library\DatabaseInterface

创建'UserModel',并将先前创建的数据库注入其中

使用先前创建的UserModel注入其中来创建UserController

这就是那里的递归,这就是我之前谈到的'自动布线'.这解决了有机磷农药的问题,因为只有当类层次结构包含数据库对象的构造函数的要求是insantiated对象,不是在每次请求.

此外,每个类都具有在构造函数中运行所需的完全要求,因此没有像服务定位器模式那样的隐藏依赖项.

RE:如何使它在需要时调用connect方法.这很简单.

    确保在Database类的构造函数中,不实例化对象,只需传入它的设置(主机,dbname,用户,密码).

    new PDO()使用类的设置有一个实际执行对象的connect方法.

    class MySQL implements DatabaseInterface
    {
        private $host;
        // ...
    
        public function __construct($host, $db, $user, $pass)
        {
            $this->host = $host;
            // etc
        }
    
        public function connect()
        {
            // Return new PDO object with $this->host, $this->db etc
        }
    }
    

    所以现在,您传递数据库的每个类都将具有此对象,但由于尚未调用connect(),因此将不具有连接.

    在可以访问Database类的相关模型中,您可以调用$this->db->connect();然后继续执行您想要执行的操作.

本质上,您仍然使用我之前描述的方法将数据库对象传递给需要它的类,但是为了决定何时在逐个方法的基础上执行连接,您只需在所需的方法中运行connect方法一.不,你不需要单身人士.你只需告诉它什么时候连接,当你不想告诉它连接时它就不会.


老答案

我将更深入地解释依赖注入容器,以及它们如何可以帮助您的情况.注意:理解"MVC"的原理将在这里有所帮助.

问题

您想要创建一些对象,但只有某些对象需要访问数据库.你目前正在做的是在每个请求上创建数据库对象,这是完全没必要的,并且在使用DiC容器之类的东西之前也是完全常见的.

两个示例对象

这是您可能想要创建的两个对象的示例.一个需要数据库访问,另一个不需要数据库访问.

/**
 * @note: This class requires database access
 */
class User
{
    private $database;

    // Note you require the *interface* here, so that the database type
    // can be switched in the container and this will still work :)
    public function __construct(DatabaseInterface $database)
    {
        $this->database = $database;
    }
}

/**
 * @note This class doesn't require database access
 */
class Logger
{
    // It doesn't matter what this one does, it just doesn't need DB access
    public function __construct() { }
}

那么,创建这些对象并处理相关依赖项的最佳方法是什么,并且只将数据库对象传递给相关的类?好吧,幸运的是,这两个在使用依赖注入容器时协调一致.

输入Pimple

Pimple是一个非常酷的依赖注入容器(由Symfony2框架的制造商提供),它使用PHP 5.3 +的闭包.

痘痘做的方式真的很酷 - 你想要的对象直到你直接要求它才会被实例化.因此,您可以设置一个新对象的负载,但在您要求它们之前,它们不会被创建!

这是一个非常简单的疙瘩示例,您可以在boostrap中创建:

// Create the container
$container = new Pimple();

// Create the database - note this isn't *actually* created until you call for it
$container['datastore'] = function() {
    return new Database('host','db','user','pass');
};

然后,在此处添加User对象和Logger对象.

// Create user object with database requirement
// See how we're passing on the container, so we can use $container['datastore']?
$container['User'] = function($container) {
    return new User($container['datastore']);
};

// And your logger that doesn't need anything
$container['Logger'] = function() {
    return new Logger();
};

真棒!那么..我如何实际使用$ container对象?

好问题!因此,您已经在引导程序中创建了$container对象,并设置了对象及其所需的依赖项.在路由机制中,将容器传递给控制器​​.

注意:示例基本代码

router->route('controller', 'method', $container);

在您的控制器,您访问$container过的参数,当你问它从用户对象,你得到一个新的用户对象(工厂式),与数据库对象已经注入!

class HomeController extends Controller
{
    /**
     * I'm guessing 'index' is your default action called
     *
     * @route /home/index
     * @note  Dependant on .htaccess / routing mechanism
     */
    public function index($container)
    {
        // So, I want a new User object with database access
        $user = $container['User'];

       // Say whaaat?! That's it? .. Yep. That's it.
    }
}

你解决了什么

所以,你现在已经用一块石头杀死了多只鸟(不仅仅是两只).

在每个请求上创建一个DB对象 - 不再是!它只是在你要求它时创建的,因为Pimple使用了闭包

从控制器中删除"新"关键字 - 是的,这是对的.您已将此责任交给容器.

注意:在继续之前,我想指出第二点是多么重要.如果没有这个容器,假设您在整个应用程序中创建了50个用户对象.然后有一天,您想要添加一个新参数.OMG - 您现在需要浏览整个应用程序并将此参数添加到每个应用程序中new User().然而,使用DiC - 如果你在$container['user']任何地方使用,你只需将第三个参数添加到容器一次,就是这样.是的,这完全是真棒.

切换数据库的能力 - 你听说过我,这一点的重点是如果你想从MySQL改为PostgreSQL - 你改变容器中的代码以返回你编码的新的不同类型的数据库,并且只要这一切都归还同样的东西,那就是它!该换出具体的实现能力,每个人都始终竖琴上的.

重要的部分

这是使用容器的一种方式,它只是一个开始.有许多方法可以使这更好的-例如,而不是物品转交容器的每一个方法,你可以使用反射/某种映射来决定需要什么样的容器的部分.自动化,你是金色的.

我希望你发现这很有用.我在这里完成它的方式至少为我减少了大量的开发时间,启动它很有趣!


很好地介绍了依赖注入容器,但你真的错过了"我的目标是避免不必要的数据库连接". - 因为如果网页只有两个或更多脚本,只有其中一个处理用户,情况就是这样.DI是另一个重要的步骤,但真正重要的是在构造函数中不要做任何事情,至少没有繁重的工作 - 比如连接到数据库.其他答案覆盖得更好.
**注意**:这是*之前*了解服务定位器反模式的帖子.即将更新......
推荐阅读
mobiledu2402851173
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有