当前位置: 首页 > 面试题库 >

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

秦鹏飞
2023-03-14
问题内容

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

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

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

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


问题答案:

注意: 尽管可以 直接回答 ops问题:“何时只能在需要时创建/连接到数据库,而不是在每个请求时都可以连接到数据库”,
但这是在需要时注入的 ,只是说这 没有
帮助。我在这里解释的是您实际上是如何正确处理的,因为在非特定框架的上下文中确实没有很多有用的信息可以帮助您解决此问题。

更新: 以下是该问题的“旧”答案。这鼓励了非常有争议的服务定位器模式,并且对许多“反模式”而言。新的答案加上了我从研究中学到的知识。
请先阅读旧答案 ,看看进展如何。

使用疙瘩一段时间后,我学到了很多关于它是如何工作的,以及它如何并不是 真正
是惊人的毕竟。它仍然很酷,但是之所以只有80行代码,是因为它基本上允许创建闭包数组。Pimple经常用作服务定位器(因为它实际上只能做些限制),这是一种“反模式”。

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

服务定位器模式是一种设计模式,用于软件开发,以封装包含具有强大抽象层的服务的过程。此模式使用一个称为“服务定位器”的中央注册表,该注册表根据请求返回执行特定任务所需的信息。

我在引导程序中创建pimple,定义依赖项,然后将此容器传递给实例化的每个类。

为什么服务定位器不好?

你说这是什么问题?主要问题是这种方法在类中 隐藏了依赖 项。因此,如果开发人员即将更新此类,而他们之前从未见过,那么他们将看到一个包含
未知数量对象 的容器 对象 。此外,测试此类课程将是一场噩梦。

为什么我本来要这样做? 因为我认为 控制器之后是您开始进行依赖项注入的地方。这是 错的 。您可以在控制器级别立即启动它。

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

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

…然后,依赖项注入容器应立即在第一个 控制器级别 开始工作。

所以说真的,如果我仍然使用pimple,我将定义要创建的控制器以及它们需要的控制器。因此,您可以 将视图以及模型层中的所有内容注入到控制器中,
以便可以使用它。这是控制反转,使测试更加容易。从Aurn
Wiki,(我将在稍后讨论):

在现实生活中,您不会通过将整个五金店(希望地)运送到施工现场来盖房子,以便您可以访问所需的任何零件。取而代之的是,领班(__construct())询问需要的特定部件(门和窗户),然后进行采购。您的对象应该以相同的方式起作用。他们应该只要求执行工作所需的特定依赖项。让众议院有权进入整个五金店,这最好是糟糕的OOP风格,最糟糕的是可维护性的噩梦。-来自Auryn
Wiki

输入Auryn

关于这一点,我想向您介绍由Rdlowrey撰写的一个很棒的东西,叫做Auryn,我是在周末被介绍给我的。

Auryn基于类构造函数签名的“自动装配”类依赖关系。这意味着,对于每个请求的类,Auryn都会找到它,找出构造函数中需要的内容,先创建所需的内容,然后再创建最初要求的类的实例。运作方式如下:

Provider根据构造函数方法签名中指定的参数类型提示,递归实例化类依赖关系。

…如果您对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\MySQLLibrary\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的问题

好的,所以使用这种技术,假设您有一个需要用户服务的User控制器(比如说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

那就是递归,这就是我之前所说的“自动装配”。这解决了OP的问题,因为 只有当类层次结构包含数据库对象作为构造函数需求时 ,该对象才会被实例化,
而不是在每次请求时都 被实例化。

同样,每个类都具有在构造函数中正常运行所需的要求,因此 没有 像服务定位器模式那样存在 隐藏的依赖关系

RE:如何制作它,以便在需要时调用connect方法。这真的很简单。

  1. 确保在Database类的构造函数中,不要实例化该对象,而只需传入它的设置(主机,dbname,用户,密码)。
  2. 具有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
    }
    

    }

  3. 因此,现在,传递给数据库的每个类都将具有该对象,但尚未建立连接,因为尚未调用connect()。

  4. 在具有访问Database类权限的相关模型中,您调用$this->db->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是一个非常酷的依赖项注入容器(由Symfony2框架的开发者使用),它使用PHP5.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传入的参数,然后从中请求用户对象时,您将获得一个新的User对象(工厂风格),并且已经注入了数据库对象!

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.
    }
}

你解决了什么

因此,您现在已经用一块石头杀死了多只鸟(而不仅仅是两只)。

  • 在每个请求上创建一个数据库对象 -不再!由于Pimple使用闭包,因此仅在您要求时创建它
  • 从控制器中删除“新”关键字 -是的,没错。您已将此责任移交给了容器。

注意: 在继续之前,我想指出第二点的重要性。如果没有此容器,可以说您在整个应用程序中创建了50个用户对象。然后有一天,您想添加一个新参数。OMG-
您现在需要遍历整个应用程序并将此参数添加到每个应用程序中new User()。但是,使用DiC-
如果您在$container['user']各处使用,只需将第三个参数添加到容器 一次即可 。是的,那太棒了。

  • 切换数据库的能力 -您听说过,这的全部意思是,如果您想从MySQL更改为PostgreSQL-您可以更改容器中的代码以返回已编码的另一种新类型的数据库,并且只要它们返回相同的东西,就是这样! 交换 每个人都经常渴望的 具体实现能力

重要部分

这是使用容器的 一种 方式,这只是一个开始。有很多方法可以使此方法更好-
例如,您可以使用反射/某种映射来确定需要容器的哪些部分,而不是将容器交给每种方法。自动化这个过程,您真是太棒了。

希望您觉得这有用。我在这里完成该操作的方式至少为我节省了大量开发时间,并且启动非常有趣!



 类似资料:
  • 问题内容: 我有2个馆藏:医院和患者。本月患者在医院A进行检查,但是下个月,该患者在医院B进行检查。那么在更新患者信息时,如何保存患者已经在医院A进行检查的患者病史?请给我个建议吗?谢谢 问题答案: 您需要为此准备单独的检查集。(就像关系数据库中的中间(关联)表一样。) 解决此问题的一种方法是使用虚拟填充。使用虚拟填充,我们无需保留对考试的引用,这将简化添加,更新或删除考试时的操作。因为只需要更新

  • 有一个独立的Java应用程序。其中有一个工厂方法,只调用一次,只创建一个对象(如果可能的话)。我有两个问题--哪种模式对此更好?其次,在这种情况下,将创建工厂的对象存储在工厂本身中是正确的吗?

  • 我需要强制< code>[Setup]部分中的指令< code > CreateUninstallRegKey 仅在需要时为卸载创建注册表项。 例如,如果我设置了一个条件来创建卸载注册表项,则只有当条件为True时才能创建该项。否则,不得创建卸载注册表项。 如何在Inno设置中执行此操作? 更新问题 我写的代码是: 使用此代码,卸载注册表项始终在创建。(我写的代码应该是有问题。 如果 ISDone

  • 空对象(Null) Intent 使用什么都不做 的空对象来代替 NULL。 一个方法返回 NULL,意味着方法的调用端需要去检查返回值是否是 NULL,这么做会导致非常多的冗余的检查代码。并且如果某一个调用端忘记了做这个检查返回值,而直接使用返回的对象,那么就有可能抛出空指针异常。 Class Diagram Implementation // java public abstract clas

  • 我看到很多视频和教程在GUI中移动对象时使用计时器,但我尝试在没有计时器的情况下做一些事情,它似乎工作正常。我不太明白什么时候需要计时器。非常感谢任何帮助 没有计时器的两个移动对象的代码示例 带有计时器的代码示例

  • 本文向大家介绍浅析php设计模式之数据对象映射模式,包括了浅析php设计模式之数据对象映射模式的使用技巧和注意事项,需要的朋友参考一下 php中的设计模式中有很多的各种模式了,在这里我们来为各位介绍一个不常用的数据映射模式吧,希望文章能够帮助到各位。 数据映射模式使您能更好的组织你的应用程序与数据库进行交互。 数据映射模式将对象的属性与存储它们的表字段间的结合密度降低。数据映射模式的本质就是一个类