更新时间:2023-02-05 21:36:47
简单地说(因为它不仅仅限于OOP世界的问题),一个依赖性是组件A需要(依赖于)组件B来执行应该做的事情的情况。在这种情况下,该词还用于描述依赖组件。要将其放在OOP / PHP术语中,请考虑以下示例:强制性汽车类比:
class Car {
public function start(){
$ engine = new Engine();
$ engine-> vroom();
}
}
汽车
取决于引擎
。 引擎
是 Car
的依赖。这段代码很糟糕,因为:
Car
的代码 Engine
替换为 MockEngine
进行测试或 TurboEngine
扩展了原来的一个,而不修改 Car
。 依赖注入是一种解决所有这些问题的方法通过将 Car
需要引擎
明确地明确提供一个:
class Car {
protected $ engine;
public function __construct(Engine $ engine){
$ this-> engine = $ engine;
}
public function start(){
$ this-> engine-> vroom();
}
}
$ engine = new SuperDuperTurboEnginePlus(); // Engine的一个子类
$ car = new Car($ engine);
以上是构造函数注入的示例,其中依赖关系依赖对象)通过类构造函数提供给依赖(消费者)。另一种方法是在 Car
类中暴露一个 setEngine
方法,并使用它来注入一个引擎
。这被称为 setter注入,主要用于在运行时被替换的依赖关系。
任何非平凡的项目由一堆相互依赖的组件组成,并且很容易失去跟踪哪里被注入的地方很快。一个依赖注入容器是一个知道如何实例化和配置其他对象的对象,知道它们与项目中其他对象的关系,并为您执行依赖注入。这使您可以集中管理所有项目(内部)依赖关系,更重要的是,可以更改/模拟一个或多个项目,而无需在代码中编辑一堆位置。
让我们来看一下这个汽车的比喻,看看什么OP试图实现一个例子。假设根据 mysqli
对象,我们有一个数据库
对象。假设我们想使用一个真正的原始依赖关系indection容器类 DIC
,它暴露了两种方法:注册($ name,$ callback)
注册一个在给定名称下创建一个对象的方式,并且 resolve($ name)
可以从该名称获取对象。我们的容器设置将如下所示:
$ dic = new DIC();
$ dic-> register('mysqli',function(){
return new mysqli('somehost','username','password');
});
$ dic-> register('database',function()use($ dic){
return new Database($ dic-> resolve('mysqli'));
} );请注意,我们正在告诉我们的容器来抓取一个 mysqli的实例
code> 自己组合一个数据库
的实例。然后,为了得到一个数据库
实例,其依赖自动注入,我们将简单地: $ database = $ dic-> resolve('database');
这是它的要点。一个更复杂但还比较简单易懂的PHP DI / IoC容器是 Pimple 。
关于OP的代码和问题:
- 不要为您的容器使用静态类或单例(或任何其他事情); 他们都是邪恶的。查看Pimple代替
- 决定是否要您的
mysqliWrapper
class extend c code code code code code code code code code code code $ c mysqliWrapper
你正在交换另一个依赖关系。您的物品不应该知道或使用容器;否则它不再是DIC,它是服务定位器(反)模式。
- 注册之前,您不需要
require
一个类文件在容器中,因为您不知道是否要使用该类的对象。将所有容器设置在一个地方。如果您不使用自动装载机,您可以在向容器注册的匿名功能中要求
。
其他资源
- $ b> 反转控制容器和依赖注入模式 $ b
- 不要找东西 - 关于IoC的清洁代码/ DI
Quick Forward:
I'm writing this with the intention of getting a better understanding of dependency injection and IoC containers, but also so that afterwards I can correct the mistakes in it and use it to help teach a few friends of mine about them as well.
As of now I've tried reading through the documentation for various frameworks(laravel, fuel, codeigniter, symfony) and I found that there were too many different aspects of the frameworks that I needed to feel comfortable using it that I decided to try to just learn each of the major pieces individually on my own before trying to use them in the frameworks themselves.
I've spent hours googling various meanings, looking over *** responses, and reading various articles trying to understand what an IoC and how to use it to correctly manage dependencies, and I believe I understand what it is in concept, but I am still grey on how to implement it correctly. I think the best way for anybody reading this to help me is to give what my current understanding of IoC containers and dependency injection is, and then let people who have a better understanding than myself point out where my understanding falls short.
My understanding:
- A dependency is when an instance of ClassA requires an instance of ClassB to instantiate a new instance of ClassA.
- A dependency injection is when ClassA is passed an instance of ClassB, either through a parameter in ClassA's constructor or through a set~DependencyNameHere~(~DependencyNameHere~ $param) function. (This is one of the areas I'm not completely certain on).
- An IoC container is a singleton Class(can only have 1 instance instantiated at any given time) where the specific way of instantiating objects of those class for this project can be registered. Here's a link to an example of what I'm trying to describe along with the class definition for the IoC container I've been using
So at this point is where I start trying use the IoC container for more complicated scenarios. As of now it seems in order to use the IoC container, I am limited to a has-a relationship for pretty much any class I want to create that has dependencies it wants to define in the IoC container. What if I want to create a class that inherits a class, but only if the parent class has been created in a specific way it was registered in the IoC container.
So for example: I want to create a child class of mysqli, but I want to register this class in the IoC container to only instantiate with the parent class constructed in a way I've previously registered in the IoC container. I cannot think of a way to do this without duplicating code (and since this is a learning project I'm trying to keep it as 'pure' as possible). Here are some more examples of what I am trying to describe.
So here are some of my questions:
- Is what I'm trying to do above possible without breaking some principle of OOP? I know in c++ I could use dynamic memory and a copy constructor to accomplish it, but I haven't been able to find that sort of functionality in php. (I will admit that I have very little experience using any of the other magic methods besides __construct, but from reading and __clone if I understood correctly, I couldn't use in the constructor it to make the child class being instantiated a clone of an instance of the parent class).
- Where should all my dependency class definitions go in relation to the IoC? (Should my IoC.php just have a bunch of require_once('dependencyClassDefinition.php') at the top? My gut reaction is that there is a better way, but I haven't come up with one yet)
- What file should I be registering my objects in? Currently doing all the calls to IoC::register() in the IoC.php file after the class definition.
- Do I need to register a dependency in the IoC before I register a class that needs that dependency? Since I'm not invoking the anonymous function until I actually instantiate an object registered in the IoC, I'm guessing not, but its still a concern.
- Is there anything else I'm overlooking that I should be doing or using? I'm trying to take it one step at a time, but I also don't want to know that my code will be reusable and, most importantly, that somebody who knows nothing about my project can read it and understand it.
I know this is extremely long, and just wanted to give a thanks in advance to anybody who took the time to read it, and even more so to anybody shares their knowledge.
Put simply (because it's not a problem limited to OOP world only), a dependency is a situation where component A needs (depends on) component B to do the stuff it's supposed to do. The word is also used to describe the depended-on component in this scenario. To put this in OOP/PHP terms, consider the following example with the obligatory car analogy:
class Car {
public function start() {
$engine = new Engine();
$engine->vroom();
}
}
Car
depends on Engine
. Engine
is Car
's dependency. This piece of code is pretty bad though, because:
- the dependency is implicit; you don't know it's there until you inspect the
Car
's code
- the classes are tightly coupled; you can't substitute the
Engine
with MockEngine
for testing purposes or TurboEngine
that extends the original one without modifying the Car
.
- It looks kind of silly for a car to be able to build an engine for itself, doesn't it?
Dependency injection is a way of solving all these problems by making the fact that Car
needs Engine
explicit and explicitly providing it with one:
class Car {
protected $engine;
public function __construct(Engine $engine) {
$this->engine = $engine;
}
public function start() {
$this->engine->vroom();
}
}
$engine = new SuperDuperTurboEnginePlus(); // a subclass of Engine
$car = new Car($engine);
The above is an example of constructor injection, in which the dependency (the depended-on object) is provided to the dependent (consumer) through the class constructor. Another way would be exposing a setEngine
method in the Car
class and using it to inject an instance of Engine
. This is known as setter injection and is useful mostly for dependencies that are supposed to be swapped at run-time.
Any non-trivial project consists of a bunch of interdependent components and it gets easy to lose track on what gets injected where pretty quickly. A dependency injection container is an object that knows how to instantiate and configure other objects, knows what their relationship with other objects in the project are and does the dependency injection for you. This lets you centralize the management of all your project's (inter)dependencies and, more importantly, makes it possible to change/mock one or more of them without having to edit a bunch of places in your code.
Let's ditch the car analogy and look at what OP's trying to achieve as an example. Let's say we have a Database
object depending on mysqli
object. Let's say we want to use a really primitive dependency indection container class DIC
that exposes two methods: register($name, $callback)
to register a way of creating an object under the given name and resolve($name)
to get the object from that name. Our container setup would look something like this:
$dic = new DIC();
$dic->register('mysqli', function() {
return new mysqli('somehost','username','password');
});
$dic->register('database', function() use($dic) {
return new Database($dic->resolve('mysqli'));
});
Notice we're telling our container to grab an instance of mysqli
from itself to assemble an instance of Database
. Then to get a Database
instance with its dependency automatically injected, we would simply:
$database = $dic->resolve('database');
That's the gist of it. A somewhat more sophisticated but still relatively simple and easy to grasp PHP DI/IoC container is Pimple. Check its documentation for more examples.
Regarding OP's code and questions:
- Don't use static class or a singleton for your container (or for anything else for that matter); they're both evil. Check out Pimple instead.
- Decide whether you want your
mysqliWrapper
class extend mysql
or depend on it.
- By calling
IoC
from within mysqliWrapper
you're swapping one dependency for another. Your objects shouldn't be aware of or use the container; otherwise it's not DIC anymore it's Service Locator (anti)pattern.
- You don't need to
require
a class file before registering it in the container since you don't know if you're going to use an object of that class at all. Do all your container setup in one place. If you don't use an autoloader, you can require
inside the anonymous function you register with the container.
Additional resources:
-
Inversion of Control Containers and the Dependency Injection pattern by Martin Fowler
-
Don't look for things -- a Clean Code Talk about IoC/DI