java imple_带imple的依赖注入

java imple

In application development, we try to create independent modules so that we can reuse code in future projects. But, it’s difficult to create completely independent modules which provide useful functionality; their dependencies can cause maintenance nightmares unless they are managed properly. This is where Dependency Injection comes in handy, as it gives us the ability to inject the dependencies our code needs to function properly without hard coding them into the modules.

在应用程序开发中,我们尝试创建独立的模块,以便我们可以在将来的项目中重用代码。 但是,很难创建完全独立的模块来提供有用的功能。 它们的依赖关系可能导致维护方面的噩梦,除非对其进行适当的管理。 这就是依赖关系注入派上用场的地方,因为它使我们能够注入代码正常运行所需的依赖关系,而无需将其硬编码到模块中。

Pimple is a simple dependency injection container which takes advantage PHP’s closures to define dependencies in manageable way. In this article we’ll look at the problems with hard-coding dependencies, how dependency injection solves them, and how to use Pimple to keep code that takes advantage of dependency injection more maintainable.

Pimple是一个简单的依赖项注入容器 ,它利用PHP的闭包以可管理的方式定义依赖项。 在本文中,我们将研究硬编码依赖关系的问题,依赖关系注入如何解决它们,以及如何使用Pimple保持利用依赖关系注入的代码的可维护性。

具体依赖问题 (The Issues with Concrete Dependencies)

We use a number of PHP classes when writing applications. One class may need to call methods of one or more other classes to provide the intended functionality, and so we say the first class depends on the other classes. For example:

编写应用程序时,我们使用许多PHP类。 一个类可能需要调用一个或多个其他类的方法来提供预期的功能,因此我们说第一类依赖于其他类。 例如:

<?php
class A
{
    public function a1() {
        $b = new B();
        $b->b1();
    }
}

Class A depends on class B. If class B is not available then the above code will not work.

A取决于类上B 。 如果B类不可用,则以上代码将不起作用。

Moreover, every time we hard code the creation of an object inside a class, we are making a concrete dependency to that class. Concrete dependencies are a barrier to writing testable code. A better way is to provide an object of class B to class A. These objects can be provided through A’s constructor or a setter method.

而且,每次我们在一个类内对对象的创建进行硬编码时,我们都会对该类进行具体的依赖。 具体的依赖关系是编写可测试代码的障碍。 更好的方法是将类B的对象提供给类A 这些对象可以通过A的构造函数或setter方法提供。

Let’s take a look at a more realistic scenario before we go any further.

在继续之前,让我们看一个更现实的场景。

Sharing content on social networking websites is very common these days, and most sites display their social profile feeds right on their site itself. Suppose we have a class called SocialFeeds which generates feeds from social sites like Twitter, Facebook, Google+, etc. Separate classes are created to work with each of these services. Here we’ll look at the class that interfaces with Twitter, TwitterService.

如今,在社交网站上共享内容非常普遍,并且大多数网站都在其网站上直接显示其社交资料供稿。 假设我们有一个名为SocialFeeds的类,该类从诸如Twitter,Facebook,Google +等社交网站生成提要。将创建单独的类来使用这些服务。 在这里,我们将看一下与Twitter交互的类TwitterService

The SocialFeeds class requests a Twitter feed using TwitterService. TwitterService interacts with the database to retrieve the specific user token to access the API. The token is passed to the OAuth class which retrieves the feeds using the provided token and returns it to the SocialFeeds class.

SocialFeeds类使用TwitterService请求Twitter提要。 TwitterService与数据库进行交互以检索特定的用户令牌以访问API。 令牌将传递到OAuth类,该OAuth类使用提供的令牌检索供稿,并将其返回给SocialFeeds类。

<?php
class SocialFeeds
{
    public function getSocialFeeds() {
        $twService = new TwitterService();
        echo $twService->getTweets();
    }
}
<?php
class TwitterService
{
    public function getTweets() {
        $db = new DB();
        $query = "Query to get user token from database";
        $token = $db->getQueryResults($query);

        $oauth = new OAuth();
        return $oauth->requestTwitterFeed($token);
    }
}
<?php
class OAuth
{
    public function requestTwitterFeed($token) {
        // Retrieve and return twitter feed using the token    		
    }
}
<?php
class DB
{
    public function getQueryResults($query) {
        // Get results from database and return token
    }
}

It’s clear that SocialFeeds depends on TwitterService. But TwitterService depends on DB and OAuth, and so SocialFeeds depends on both DB and OAuth indirectly.

显然, SocialFeeds依赖于TwitterService 。 但是, TwitterService依赖于DBOAuth ,因此SocialFeeds间接依赖于DBOAuth

So what are the issues? SocialFeeds depends on concrete implementations of three classes, so it is impossible to test SocialFeeds as a separate unit without having real implementations of the other classes. Or, let’s say we want to use a different database or a different OAuth provider. In this case we would have to replace the existing classes with new class in each occurrence throughout our code.

那是什么问题呢? SocialFeeds取决于三个类的具体实现,因此如果没有其他类的实际实现,则不可能将SocialFeeds作为一个单独的单元进行测试。 或者,假设我们要使用其他数据库或其他OAuth提供程序。 在这种情况下,我们在每次代码中每次出现时都必须用新类替换现有的类。

修复具体依赖性 (Fixing Concrete Dependencies)

The solution to these dependency issues is as simple as providing the objects dynamically when necessary without using concrete implementations. There are two types of techniques to inject dependencies: constructor-based dependency injection and setter-based injection.

解决这些依赖性问题的方法很简单,就是在需要时不使用具体实现就动态地提供对象。 注入依赖关系有两种技术:基于构造函数的依赖关系注入和基于setter的注入。

基于构造函数的注入 (Constructor-Based Injection)

With constructor-based dependency injection, dependant objects are created externally and passed to the class’s constructor as parameters. We can assign these objects to class variables and use anywhere inside the class. Constructor-based injection for the SocialFeeds class looks like this:

通过基于构造函数的依赖注入,可以在外部创建依赖对象,并将其作为参数传递给类的构造函数。 我们可以将这些对象分配给类变量,并在类中的任何地方使用。 SocialFeeds类的基于构造函数的注入如下所示:

<?php
class SocialFeeds
{
    public $twService;

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

    public function getSocialFeeds() {
        echo $this->twService->getTweets();
    }
}

An instance of TwitterService is passed as an object to the constructor. SocialFeeds still depends on TwitterService, but now we can have the freedom to provide different versions of the Twitter service provider or even a mock object for testing purposes. The DB and OAuth classes are defined similarly with regard to TwitterService.

TwitterService的实例作为对象传递给构造函数。 SocialFeeds仍然依赖于TwitterService ,但是现在我们可以自由地提供不同版本的Twitter服务提供商,甚至可以提供用于测试目的的模拟对象。 DBOAuth类的定义与TwitterService相似。

<?php
$db = new DB();
$oauth = new OAuth();
$twService = new TwitterService($db, $oauth);
$socialFeeds = new SocialFeeds($twService);
$socialFeeds->getSocialFeeds();

基于Setter的注入 (Setter-Based Injection)

With setter-based injection, objects are provided through setter methods instead of the constructor. Here’s a setter-based implementation of dependency injection for the SocialFeeds class:

对于基于setter的注入,对象是通过setter方法而不是构造函数提供的。 这是SocialFeeds类的基于setter的依赖项注入的SocialFeeds

<?php
class SocialFeeds
{
    public $twService;

    public function getSocialFeeds() {
        echo $this->twService->getTweets();
    }

    public function setTwitterService($twService) {
        $this->twService = $twService;
    }
}

The initialization code including DB and OAuth for now looks like this:

现在,包括DBOAuth的初始化代码如下所示:

<?php
$db = new DB();
$oauth = new OAuth();
$twService = new TwitterService();
$twService->setDB($db);
$twService->setOAuth($oauth);

$socialFeeds = new SocialFeed();
$socialFeeds->setTwitterService($twService);
$socialFeeds->getSocialFeeds();

构造函数与二传手注入 (Constructor vs Setter Injection)

It’s up to you to choose between constructor or setter-based injection. Constructor-based injections are suitable when all the dependencies are required in order to instantiate the class. Setter-based injections are suitable when dependencies are not required in each occasion.

由您决定是在构造函数还是基于setter的注入之间进行选择。 当需要所有依赖关系以实例化类时,基于构造函数的注入是合适的。 当不需要在每种情况下都依赖时,基于Setter的注入是合适的。

Advantages

优点

  • Constructor – all the dependencies of a class are identifiable simply by looking at the class’s constructor

    构造函数–只需查看类的构造函数,即可识别类的所有依赖关系
  • Setter – adding a new dependency is as easy as adding a new setter method, which does not break existing code

    Setter –添加新的依赖项就像添加新的setter方法一样简单,该方法不会破坏现有代码

Disadvantages

缺点

  • Constructor – adding a new dependency increases the constructor’s parameters; existing code needs to be updated throughout our application to provide the new dependency

    构造函数–添加新的依赖关系会增加构造函数的参数; 现有代码需要在我们的整个应用程序中进行更新,以提供新的依赖关系
  • Setter – we have to manually search for the necessary dependencies as they are not specified anywhere

    设置器–我们必须手动搜索必要的依赖项,因为它们在任何地方都没有指定

With knowledge of dependency injection and various injection techniques, it’s time to look at Pimple and see how it fits in.

有了依赖注入和各种注入技术的知识,现在该看看Pimple并了解它的适用范围。

丘疹在DI中的作用 (The Role of Pimple in DI)

You might be wondering why Pimple is necessary when we can inject the dependencies already using the techniques previously mentioned. To answer this question, we need to look to the DRY principle.

您可能想知道为什么当我们可以使用前面提到的技术注入依赖项时,为什么需要Pimple。 要回答这个问题,我们需要参考DRY原理。

Don’t Repeat Yourself (DRY) is a principle of software development aimed at reducing repetition of information of all kinds, especially useful in multi-tier architectures. The DRY principle is stated as “Every piece of knowledge must have a single, unambiguous, authoritative representation within a system – Wikipedia

不要自己重复(DRY)是旨在减少各种信息重复的软件开发原则,在多层体系结构中尤其有用。 DRY原则表示为“系统中的每条知识都必须具有单一,明确,权威的表示形式- 维基百科

Consider the constructor-based injection example. Each time we want an object of the SocialFeed class, we have to repeat the whole setup process of instantiating and passing its dependencies. According to DRY, such code should be avoided to prevent maintenance headaches. Pimple acts as a container for defining such dependencies to avoid repetition.

考虑基于构造函数的注入示例。 每次我们想要SocialFeed类的对象时,都必须重复实例化和传递其依赖关系的整个设置过程。 根据DRY,应避免使用此类代码以防止维护麻烦。 Pimple充当定义此类依赖项以避免重复的容器。

Let’s look at a simple example to see how Pimple works.

让我们看一个简单的例子,看看Pimple是如何工作的。

<?php
$container = new Pimple();
$container['class_name'] = 'Test';
$container['object_name'] = function ($c) {
    return new $c['class_name']();
};

$testClass = $container['object_name'];

An instance of Pimple is created to act as the container for storing dependencies. It implements the SPL ArrayAccess interface so working with it is very similar to working with an array. First we’ve defined a key which holds the name of some arbitrary class we want. Then we’ve defined a closure to return the instance of the specified class which acts as a service. Note that $c will be passed an instance of the container, so we can reference other defined keys as we please; each defined parameter or object is available in the closure through the $c variable. Now whenever we want an instance of the class, we can reference the key to retrieve the object.

创建一个Pimple实例以充当用于存储依赖项的容器。 它实现了SPL ArrayAccess接口,因此使用它与使用数组非常相似。 首先,我们定义了一个键,该键保存我们想要的任意类的名称。 然后,我们定义了一个闭包以返回指定类的实例,该实例充当服务。 注意$c将被传递给容器的一个实例,因此我们可以随意引用其他已定义的键。 每个定义的参数或对象都可以通过$c变量在闭包中使用。 现在,每当我们需要类的实例时,我们都可以引用键来检索对象。

Let’s convert the SocialFeeds example to Pimple. The examples on the official Pimple site show constructor-based injection, so here we’ll illustrate setter-based injection. Keep in mind that any of the setter methods or code we defined in earlier do not need to be modified for us to use Pimple – we simply encapsulate the logic.

让我们将SocialFeeds示例转换为Pimple。 Pimple官方网站上的示例显示了基于构造函数的注入,因此这里我们将说明基于setter的注入。 请记住,使用Pimple不需要修改我们先前定义的任何setter方法或代码-我们只需封装逻辑即可。

<?php
$container = new Pimple();
$container['oauth'] = function($c) {
	return new OAuth();
};
$container['db'] = function($c) {
	return new DB();
};
$container['tweet_service'] = function($c) {
	$twService = new TwitterService();
	$twService->setDB($c['db']);
	$twService->setOauth($c['oauth']);
	return $twService;
};
$container['social_feeds'] = function($c) {
	$socialFeeds = new SocialFeeds();
	$socialFeeds->setTwitterService($c['tweet_service']);
	return $socialFeeds;
};

$socialFeeds = $container['social_feeds'];
$socialFeeds->getSocialFeeds();

Both DB and OAuth classes are independent modules, so we directly return a new instance of them inside closures. Then we add dependencies to the TwitterService class using setter-based injections. We added DB and OAuth classes into the container already, so we can directly access them inside the function using $c['db'] and $c['oauth'].

DBOAuth类都是独立的模块,因此我们直接在闭包内返回它们的新实例。 然后,我们使用基于setter的注入将依赖项添加到TwitterService类。 我们已经将DBOAuth类添加到了容器中,因此我们可以使用$c['db']$c['oauth']在函数内部直接访问它们。

Now the dependencies are encapsulated inside the container as services. Whenever we want to use different DB class or different OAuth service, we can just replace the class inside the container statement and everything will work perfectly. With Pimple you need to add new dependencies in just one place.

现在,依赖项作为服务封装在容器内。 每当我们想要使用不同的DB类或不同的OAuth服务时,我们只需替换容器语句中的类即可,一切将正常运行。 使用Pimple,您只需要在一个地方添加新的依赖项。

高级丘疹用法 (Advanced Pimple Usage)

In the above scenario, Pimple will return new instances of each class from the closure whenever one is requested. There are certain scenarios where we need to use the same object without initializing new instances each time, for example connecting to database is a perfect example.

在上述情况下,只要有一个请求,Pimple都会从闭包中返回每个类的新实例。 在某些情况下,我们需要使用同一对象而无需每次都初始化新实例,例如,连接数据库是一个很好的例子。

Pimple provides the ability to return the same instance using sharing objects, doing so requires us to specify the closure through the share() method as shown below:

Pimple提供了使用共享对象返回相同实例的功能,这需要我们通过share()方法指定闭包,如下所示:

<?php
$container['db'] = $container->share(function ($c) {
    return new DB();
});

Also, so far we’ve defined all our dependencies in a single location inside the Pimple container. But think of a situation where we need the services with its dependencies but configured in a slightly different way than the original. For example, let’s say we need to access an ORM for certain functionality of the TwitterService class. We can’t change the existing closure since it will force all of the existing functionality to use the ORM.

同样,到目前为止,我们已经在Pimple容器内的单个位置定义了所有依赖项。 但是考虑一下这样一种情况,我们需要具有相关性但配置方式与原始方式略有不同的服务。 例如,假设需要为TwitterService类的某些功能访问ORM。 我们无法更改现有的闭包,因为它将强制所有现有的功能使用ORM。

Pimple provides the method extend() to modify the existing closure dynamically without affecting the original implementation. Consider the following code:

Pimple提供了方法extend()来动态修改现有闭包,而不会影响原始实现。 考虑以下代码:

<?php
$container['tweet_service'] = $container->extend('tweet_service', function($twSservice, $c) {
	$twService->setDB(new ORM());
	return $twService;
});

Now we’re able to use different extended versions of tweet_service in special scenarios. The first parameter is the name of the service, the second is a function that gets access to the object instance and the container.

现在,我们可以在特殊情况下使用tweet_service不同扩展版本。 第一个参数是服务的名称,第二个参数是用于访问对象实例和容器的函数。

Indeed, extend() is a powerful way of adding dependencies dynamically to suit different situations, but make sure to limit the extended versions of services to a minimum as it increases the amount of duplicate code.

实际上, extend()是一种动态添加依赖项以适应不同情况的有效方法,但是请确保将服务的扩展版本限制在最小,因为这会增加重复代码的数量。

摘要 (Summary)

Managing dependencies is one of the most essential and difficult tasks in web application development. We can use Dependency Injection using constructors and setter methods to manage them effectively. Dependency injection comes with its own hassles though, which Pimple solves by providing a lightweight container for creating and storing object dependencies in a DRY manner.

管理依赖关系是Web应用程序开发中最重要和最困难的任务之一。 我们可以通过构造函数和设置方法使用依赖注入来有效地管理它们。 但是,依赖注入有其自身的麻烦,Pimple通过提供轻量级容器以DRY方式创建和存储对象依赖关系来解决。

Feel free to share your experiences of managing dependencies in your projects and what you think about Pimple as a dependency injection container in the comments below.

欢迎在下面的评论中分享您在项目中管理依赖项的经验以及您对Pimple作为依赖项注入容器的看法。

Image via Fotolia

图片来自Fotolia

翻译自: https://www.sitepoint.com/dependency-injection-with-pimple/

java imple

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值