设计模式:如何只在需要时创建数据库对象/连接? [英] Design Patterns: How to create database object/connection only when needed?

查看:225
本文介绍了设计模式:如何只在需要时创建数据库对象/连接?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

  $ db = new Database(); 
$ foo = new Foo($ db); //传递db

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



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

解决方案


对于ops问题,何时只能在需要时可以创建/连接到数据库而不是在每个请求上在需要时注入它有帮助。我在这里解释你是如何正确地,正确地,因为在非特定框架上下文中没有很多有用的信息有助于在这方面。






更新:此问题的旧答案可以参见下面。这鼓励了服务定位器模式,这是非常有争议的,并且许多是反模式。新的答案添加了我从研究中学到的。




新答案



使用pimple一段时间后,我学到了很多关于它是如何工作,以及它怎么不是实际上是惊人的。它仍然很酷,但它只有80行代码的原因是因为它基本上允许创建一个闭包数组。 Pimple被用来作为一个服务定位器(因为它实际上可以做的有限),这是一个反模式。



首先,是服务定位器?




服务定位器模式是在软件开发中使用的设计模式,用于封装在获得服务时涉及的进程强抽象层。此模式使用称为服务定位器的中心注册表,根据请求返回执行某个任务所需的信息。




为什么服务定位器不好?

为什么是服务定位器错误?

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



为什么我原来这样做? 因为我认为 之后是开始进行依赖注入的地方。这是错误



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


前控制器 - > 引导 - > 路由器 - > 控制器/方法 - > 模型[服务|域对象|映射器] - > 控制器 - > 查看


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



真的,如果我仍然使用pimple,我会定义什么控制器将被创建,以及他们需要什么。因此,您可以将模型图层中的视图和任何内容注入到控制器中,以便可以使用它。这是反转控制,使测试更容易。从Aurn wiki(我很快会谈到):


在现实生活中,你不会通过运输整个五金店(希望)到施工现场,以便您可以访问任何您需要的部件。相反,工头(__construct())询问将需要的特定部分(门和窗口),并采取他们。你的对象应该以同样的方式工作;他们应该仅仅要求做他们的工作所需的具体依赖。给房子访问整个五金店是最好的糟糕的OOP风格,最糟糕的可维护性噩梦。 - 来自Auryn维基




输入Auryn



在这张笔记中,我想向您介绍一些名为 Auryn ,由 Rdlowrey 撰写,我在周末被介绍。



基于类构造函数签名的Auryn'auto-wires'类依赖。这意味着,对于每个请求的类,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和I 'd写一个类,我会简单更改 Library \Database\MySQL Library\Database\MongoDB $ injector 进入我的路由器,并且在创建控制器/方法时,这是自动解决依赖关系

  public function dispatch($ injector)
{
//确保文件/控制器存在
//确保方法调用exists
// etc ...

//创建具有必需依赖关系的控制器
$ class = $ injector-> make($ controller);
//在控制器中调用方法(action)
$ class-> $ action();
}




问题



好吧,所以使用这种技术,假设您有需要用户服务(假设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



这是递归,这是我之前讨论的'auto-wiring'。这解决了OPs问题,因为只有当类层次结构包含数据库对象作为构造函数需求时,对象被篡改,不在每个请求



此外,每个类都有在构造函数中需要运行的要求,因此没有隐藏的依赖,就像使用服务定位器模式一样。



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


  1. 确保在Database类的构造函数中,不实例化对象,

    有一个实际执行新PDO()的连接方法对象,使用类的设置。

     类MySQL实现DatabaseInterface 
    {
    private $ host;
    // ...

    public function __construct($ host,$ db,$ user,$ pass)
    {
    $ this-> host = $主办;
    // etc
    }

    public function connect()
    {
    //返回新的PDO对象$ this-> host,$ this - > db etc
    }
    }



  2. 在相关模型中,有一个对象访问Database类,请调用 $ this-> db-> connect(); ,然后继续执行您想要做的操作。

实质上,你仍然使用我之前描述的方法将你的数据库对象传递给需要它的类,但是决定何时执行连接方法一个方法基础,您只需运行所需的连接方法。没有你不需要单身。












b

旧答案




我将更深入地解释依赖注入容器,以及它们如何帮助你的情况。注意:了解MVC的原则在这里有很大的帮助。




问题


$ b b

您想要创建一些对象,但只有某些对象需要访问数据库。您目前正在做的是在每个请求上创建数据库对象,这是完全不必要的,在使用DiC容器之前也是完全通用的。



两个示例对象



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

  / ** 
* @note:需要数据库访问
* /
类用户
{
private $ database;

//注意,你需要* interface *这里,所以数据库类型
//可以在容器中切换,这仍然可以工作:)
public function __construct (DatabaseInterface $ database)
{
$ this-> database = $ database;
}
}

/ **
* @note此类不需要数据库访问
* /
类Logger
{
//这不重要,它不需要DB访问
public function __construct(){}
}

因此,创建这些对象和处理它们的相关依赖关系的最好方法是将数据库对象传递给相关类?那么,幸运的是,这两个在使用依赖注入容器时协同工作。



输入Pimple



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



那个疙瘩是真的很酷 - 你想要的对象不会实例化,直到你直接请求它。所以你可以设置一个新的对象,但直到你问他们,他们不是创建!



这是一个很简单的疙瘩示例,你创建您的 boostrap



<$ c $> ;

//创建数据库 - 注意这不是*实际上创建,直到你调用
$ container ['datastore'] = function(){
return new数据库('host','db','user','pass');
};

然后,在此处添加您的User对象和您的Logger对象。

  //创建数据库需求的用户对象
//看看我们如何传递容器,所以我们可以使用$ container ['datastore' ]?
$ container ['User'] = function($ container){
return new User($ container ['dataastore']);
};

//和你不需要任何东西的记录器
$ container ['Logger'] = function(){
return new Logger();
};



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



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



注意:示例基本代码

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

在您的控制器中,您可以访问 $ container 参数传入,当您从中请求用户对象时,您将返回一个新的User对象(工厂样式),并且已注入数据库对象!

  class HomeController extends Controller 
{
/ **
*我猜'index'是你的默认动作
*
* @route / home / index
* @note依赖于.htaccess /路由机制
* /
public function index($ container)
{
/ /所以,我想要一个新的数据库访问的用户对象
$ user = $ container ['User'];

//说whaaat?而已? ..是的。而已。
}
}



您解决了什么



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




  • strong>在每个请求上创建一个DB对象 - 不再有! 从控制器中删除新关键字 - 是的,没错。



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




  • 切换数据库的能力 - 你听说过了,这是如果你想改变从MySQL到PostgreSQL - 你改变你的容器中的代码,以返回一个新的不同类型的数据库,你编码,只要它都返回相同的东西,就是这样!



重要部分

>

这是一种使用容器的方式,它只是一个开始。有很多方法使这更好 - 例如,而不是把容器交给每个方法,你可以使用反射/某种映射来决定容器的哪些部分是必需的。自动化,你是金色的。



我希望你觉得这很有用。我在这里做的方式至少为我减少了大量的开发时间,这是很好的开始!


I've a simple application, say it has some classes and an "extra" one that handles database requests. Currently i'm creating the database object everytime the app is used, but in some cases there's no need for a database connection. I'm doing it like this (PHP btw):

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

But sometimes the $foo object does not need db access, as only methods without database actions are called. So my question is: What's the professional way to handle situations like this / how to create the db connection/object only when needed ?

My goal is to avoid unnecessary database connections.

解决方案

Note: Although the direct answer to ops question, "when can I only create / connect to the database when required and not on every request" is inject it when you need it, simply saying that is not helpful. I'm explaining here how you actually go about that correctly, as there really isn't a lot of useful information out there in a non-specific-framework context to help in this regard.


Updated: The 'old' answer to this question can be see below. This encouraged the service locator pattern which is very controversial and to many an 'anti-pattern'. New answer added with what I've learned from researching. Please read the old answer first to see how this progressed.

New Answer

After using pimple for a while, I learned much about how it works, and how it's not actually that amazing after all. It's still pretty cool, but the reason it's only 80 lines of code is because it basically allows the creation of an array of closures. Pimple is used a lot as a service locator (because it's so limited in what it can actually do), and this is an "anti-pattern".

Firstly, what is a service locator?

The service locator pattern is a design pattern used in software development to encapsulate the processes involved in obtaining a service with a strong abstraction layer. This pattern uses a central registry known as the "service locator" which on request returns the information necessary to perform a certain task.

I was creating pimple in the bootstrap, defining dependencies, and then passing this container to each and every single class I instantiated.

Why is a service locator bad?

What's the problem with this you say? The main problem is that this approach hides dependencies from the class. So if a developer is coming to update this class and they haven't seen it before, they're going to see a container object containing an unknown amount of objects. Also, testing this class is going to be a bit of a nightmare.

Why did I do this originally? Because I thought that after the controller is where you start doing your dependency injection. This is wrong. You start it straight away at the controller level.

If this is how things work in my application:

Front Controller --> Bootstrap --> Router --> Controller/Method --> Model [Services|Domain Objects|Mappers] --> Controller --> View --> Template

...then the dependency injection container should start working right away at the first controller level.

So really, if I were to still use pimple, I would be defining what controllers are going to be created, and what they need. So you would inject the view and anything from the model layer into the controller so it can use it. This is Inversion Of Control and makes testing much easier. From the Aurn wiki, (which I'll talk about soon):

In real life you wouldn't build a house by transporting the entire hardware store (hopefully) to the construction site so you can access any parts you need. Instead, the foreman (__construct()) asks for the specific parts that will be needed (Door and Window) and goes about procuring them. Your objects should function in the same way; they should ask only for the specific dependencies required to do their jobs. Giving the House access to the entire hardware store is at best poor OOP style and at worst a maintainability nightmare. - From the Auryn Wiki

Enter Auryn

On that note, I'd like to introduce you to something brilliant called Auryn, written by Rdlowrey that I was introduced to over the weekend.

Auryn 'auto-wires' class dependencies based on the class constructor signature. What this means that, for each class requested, Auryn finds it, figures out what it needs in the constructor, creates what it needs first and then creates an instance of the class you asked for originally. Here's how it works:

The Provider recursively instantiates class dependencies based on the parameter type-hints specified in their constructor method signatures.

...and if you know anything about PHP's reflection, you'll know some people call it 'slow'. So here's what Auryn does about that:

You may have heard that "reflection is slow". Let's clear something up: anything can be "too slow" if you're doing it wrong. Reflection is an order of magnitude faster than disk access and several orders of magnitude faster than retrieving information (for example) from a remote database. Additionally, each reflection offers the opportunity to cache the results if you're worried about speed. Auryn caches any reflections it generates to minimize the potential performance impact.

So now we've skipped the "reflection is slow" argument, here's how I've been using it.

How I use Auryn

  • I make Auryn part of my autoloader. This is so that when a class is asked for, Auryn can go away and read the class and it's dependencies, and it's dependencies' dependencies (etc), and return them all into the class for instantiation. I create the Auyrn object.

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

  • I use a Database Interface as a requirement in the constructor of my database class. So I tell Auryn which concrete implementation to use (this is the part you change if you want to instantiate a different type of database, at a single point in your code, and it'll all still work).

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

If I wanted to change to MongoDB and I'd written a class for it, I'd simple change Library\Database\MySQL to Library\Database\MongoDB.

  • Then, I pass the $injector into my router, and when creating the controller / method, this is where the dependencies are automatically resolved.

    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();
    }
    

Finally, answer OP's question

Okay, so using this technique, let's say you have the User controller which requires the User Service (let's say UserModel) which requires Database access.

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

If you use the code in the router, Auryn will do the following:

  • Create the Library\DatabaseInterface, using MySQL as the concrete class (alias'd in the boostrap)
  • Create the 'UserModel' with the previously created Database injected into it
  • Create the UserController with the previously created UserModel injected into it

That's the recursion right there, and this is the 'auto-wiring' I was talking about earlier. And this solves OPs problem, because only when the class hierarchy contains the database object as a constructor requirement is the object insantiated, not upon every request.

Also, each class has exactly the requirements they need to function in the constructor, so there are no hidden dependencies like there were with the service locator pattern.

RE: How to make it so that the connect method is called when required. This is really simple.

  1. Make sure that in the constructor of your Database class, you don't instantiate the object, you just pass in it's settings (host, dbname, user, password).
  2. Have a connect method which actually performs the new PDO() object, using the classes' settings.

    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. So now, every class you pass the database to will have this object, but will not have the connection yet because connect() hasn't been called.

  4. In the relevant model which has access to the Database class, you call $this->db->connect(); and then continue with what you want to do.

In essence, you still pass your database object to the classes that require it, using the methods I have described previously, but to decide when to perform the connection on a method-by-method basis, you just run the connect method in the required one. No you don't need a singleton. You just tell it when to connect when you want it to, and it doesn't when you don't tell it to connect.


Old Answer

I'm going to explain a little more in-depth about Dependency Injection Containers, and how they can may help your situation. Note: Understanding the principles of 'MVC' will help significantly here.

The Problem

You want to create some objects, but only certain ones need access to the database. What you're currently doing is creating the database object on each request, which is totally unnecessary, and also totally common before using things like DiC containers.

Two Example Objects

Here's an example of two objects that you may want to create. One needs database access, another doesn't need database access.

/**
 * @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() { }
}

So, what's the best way to create these objects and handle their relevant dependencies, and also pass in a database object only to the relevant class? Well, lucky for us, these two work together in harmony when using a Dependency Injection Container.

Enter Pimple

Pimple is a really cool dependency injection container (by the makers of the Symfony2 framework) that utilises PHP 5.3+'s closures.

The way that pimple does it is really cool - the object you want isn't instantiated until you ask for it directly. So you can set up a load of new objects, but until you ask for them, they aren't created!

Here's a really simple pimple example, that you create in your 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');
};

Then, you add your User object and your Logger object here.

// 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();
};

Awesome! So.. how do I actually use the $container object?

Good question! So you've already created the $container object in your bootstrap and set up the objects and their required dependencies. In your routing mechanism, you pass the container to your controller.

Note: example rudimentary code

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

In your controller, you access the $container parameter passed in, and when you ask for the user object from it, you get back a new User object (factory-style), with the database object already injected!

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

What you've solved

So, you've now killed multiple birds (not just two) with one stone.

  • Creating a DB object on each request - Not any more! It's only created when you ask for it because of the closures Pimple uses
  • Removing 'new' keywords from your controller - Yep, that's right. You've handed this responsibility over to the container.

Note: Before I continue, I want to point out how significant bullet point two is. Without this container, let's say you created 50 user objects throughout your application. Then one day, you want to add a new parameter. OMG - you now need to go through your whole application and add this parameter to every new User(). However, with the DiC - if you're using $container['user'] everywhere, you just add this third param to the container once, and that's it. Yes, that totally is awesome.

  • The ability to switch out databases - You heard me, the whole point of this is that if you wanted to change from MySQL to PostgreSQL - you change the code in your container to return a new different type of database you've coded, and as long as it all returns the same sort of stuff, that's it! The ability to swap out concrete implementations that everyone always harps on about.

The Important Part

This is one way of using the container, and it's just a start. There are many ways to make this better - for example, instead of handing the container over to every method, you could use reflection / some sort of mapping to decide what parts of the container are required. Automate this and you're golden.

I hope you found this useful. The way I've done it here has at least cut significant amounts of development time for me, and it's good fun to boot!

这篇关于设计模式:如何只在需要时创建数据库对象/连接?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆