毕竟依赖注入是什么?

它是什么,以及依赖性注射在软件开发中有多有帮助。
毕竟依赖注入是什么?

与朋友和家人交谈时,首先启动他们的软件职业,我经常听到依赖性注射的词汇 匕首gu 或任何其他旨在帮助注入应用程序的依赖项的工具。不幸的是,他们中的任何一个都实际上理解或掌握为什么需要它,为什么我们需要这样的工具来实现它。当我告诉他们时,有些人甚至很惊讶,你真的不需要这些工具来拥有依赖注入,并且它们只是帮助者。真的很好的帮助,但仍然只是帮助者。

就个人而言,我相信该工具很棒,并帮助您更快地发展并提供更好的质量代码。但是,了解背后的概念是很重要的。这通常使工具更容易理解。

我想至少提供一个解释,并通过步骤方法来实现我所学到的,以及如何在软件开发中看到依赖注射。

从函数开始

通常,使用面向对象的范例解释依赖性注入。我想先尝试用功能编程解释它,因为我认为这更简单。依赖关系在软件中无处不在。由于我们主要了解它们,因此难以依赖或紧密耦合使得延长,测试和缩放代码,而依赖性松散(或松散耦合)往往更容易处理。所以要从我们开始了解真正是依赖性的。

在功能规划中,功能是一流的公民。这意味着它们可以作为参数传递,被其他函数返回,分配给变量,以及其他事物。是的,您猜到了它,在功能编程依赖关系中是函数。

Let’s look at the map example. I’ll be using 酏剂 因为它是我对我感到满意的最纯粹的功能编程语言。我会尽力解释语法。 酏剂 已经在标准库中拥有此功能,但对于这篇文章的目的,我将自己实施。我们有一个整数列表,在我们的应用程序中,我们需要突出这些整数:

def map_to_squares([]) do
  []
end

def map_to_squares([head | tail]) do
  [head * head | map_to_squares(tail)]
end

使用  模式匹配酏剂 decides which function to call. When the list is not empty it will call the second function – map_to_squares([head | tail]) – and recursively build another list until eventually it’s empty. At this point it will call the first function – map_to_squares([]).

Calling map_to_squares([1, 2, 3, 4]) yields the list [1, 4, 9, 16].

我们可以用另一个列表调用此功能并获取其平方元素吗?绝对地!

Can we call this function with a list and apply another operation to the list’s elements? Say we want to return a list of 1s and 0s depending if the number is odd or even respectively? No!

Why can’t we do this? Because the function can only square items. It has the squaring function coupled with its implementation. you can see it in head * head. In other words, map_to_squares is tightly coupled with the squaring function.

我们如何为任何功能做出这项工作?我们需要颠覆控制。简单地说 - 我们不再说我们要申请的功能是什么功能,但我们让呼叫代码指定此功能:

def map([], _) do
  []
end

def map([head | tail], fun) do
  [fun.(head) | map(tail, fun)]
end

So now we have a function map that receives another function. This function can be anything as long as it receives 1 parameter and this parameter is an element of the list.

Calling map([1, 2, 3, 4], fn x -> x * x end) will still yield [1, 4, 9, 16], but now we can also call it like so:

map([1, 2, 3, 4], fn x ->
    if Integer.is_even(x) do
      0
    else
      1
    end
end)

和 get the list [1, 0, 1, 0]. We no longer care what function we need to apply to the list members as long as it respects the rules already defined.

我们成功地授予了调用此功能的控件。对映射函数的依赖仍然存在,但现在它不再紧密耦合。这使得它更加灵活和可重复使用。我们不必编写多个函数,该函数将操作应用于列表中的每个元素。我们可以使用此一个并将操作注入函数。

此外,我们还通过迭代列表的逻辑解耦了映射函数的逻辑。这对测试真的很好。我们现在可以确保我们的函数迭代列表中的所有元素,并为每个元素调用给定的映射函数。

值得注意的是,在功能规划中,还有其他方法可以解决这些依赖性,这可能比此处解释的那些更好 - 即 部分应用

在OO世界

在面向对象的世界(OO)中的事情并不不同。如果您对此范例没有陌生人,您可以知道您的对象通常有很多依赖项。这些依赖项是其他对象。我会使用 kotlin. 对于下一个例子,主要是因为我是一个Android开发人员。我知道人们可以使用更多的功能构造 kotlin. 而不是在其他oo语言中,但为了争论,我会尽力远离这些。

让我们想象下面的场景。我们正在为一些运输公司构建一个有几个运输媒体和司机的运输公司。每个司机都可以驱动一个或多个运输 - 总线,飞机,汽车等。可以开始考虑以下内容:

class Bus {
  fun drive() = println("Driving on the road")
}

class Driver {
  lateinit val bus: Bus

  fun drive() = bus.drive()
}

As you can see we have a class Bus that can be driven and a class Driver that for now can only drive a bus. There are quite some issues with this approach, but let’s start with the bus dependency from the driver class.

With this approach the driver can only drive buses. However we were told that drivers could drive more than one transportation medium. So first thing we need to do is generalize the Bus class. Let’s build an interface for a generic transport.

interface Transport {
  fun drive()
}

class Bus : Transport {
  override fun drive() = println("Driving on the road")
}

This step is very important because it will enable us to make sure the Driver class depends on a generic transport and not only on a bus enabling the driver to effectively drive more than one transport. If you recall the example with functional programming this is similar in that the mapping function specifies a signature, but not how it should behave. Likewise we should specify interfaces and not the behavior. As a rule of thumb always depend on abstractions rather than concrete implementations.

We can now write our Driver class as follows:

class Driver {
  lateinit var transport: Transport

  fun drive() = transport.drive()
}

Now the dependency to Bus is no longer present. The Driver class depends on an interface that describes the contract for transports. We can now have a single driver driving multiple transports:

interface Transport {
  fun drive()
}

class Bus : Transport {
  override fun drive() = println("Driving on the road")
}

class Airplane : Transport {
  override fun drive() = println("Flying over the road")
}

class Driver {
  lateinit var transport: Transport

  fun drive() = transport.drive()
}

fun main(args: Array<String>) {
  val driver = Driver()
  val plane = Airplane()
  val bus = Bus()

  driver.apply {
    transport = plane
    drive()
  }
  driver.apply {
    transport = bus
    drive()
  }
}

As you can see the created driver now can drive both a bus and a plane as well as any other class complying to the Transport interface. Notice also how we’ve created another class – Airplane – that can be used with the class Driver and yet we didn’t have to touch the Driver class.

We’ve given control to the code using Driver. This is why we say dependency injection is a form of inversion of control.

我们在这里看到的是依赖注入的形式,其中使用Setter注入依赖关系。这是为了保持例子简单。其他形式的注射剂包括现场注射(其中在 kotlin. 看起来几乎是相同的)和构造函数注射。

我个人更喜欢使用构造函数注入的方法,因为它不可能在不初始化所有依赖项的情况下调用这些方法。举个例子:

class Driver(private val transport: Transport) {
  fun drive() = transport.drive()
}

Here the dependency must be passed when the instances of Driver are created and therefore it’s impossible to forget about it before invoking the method Driver.drive(). Also I take advantage of kotlin.’s type system to avoid nulls here. However, this would not work for our application because it requires one driver to drive multiple transport mediums.

One last thing that is very important to notice. We can now test the Driver class without worrying about which transport medium it’s using under the hood. In fact, because we inverted the control to the calling code, during the tests we can call this class with a mock object and avoid creating a real transport making it way easier to test.

那么为什么需要像匕首和刺痛一样的工具?

正如我们所看到的,依赖注入是一个基本上允许呼叫代码控制正在使用的内容的概念。在给定的示例中,依赖项非常简单且微不足道。我们实际上可以手动创建所有对象并注入每个依赖项。

然而,在一个真实的世界场景中,事情并不是这么简单。通常,您的依赖关系有依赖关系,它将具有自己的依赖性等等。通常所有这些依赖关系都有需要管理的一定时间。事情变得非常复杂,我们最终得到了依赖性的图表,这太难创造和维护。

这是工具喜欢的地方 匕首 and gu 帮助我们出去。他们使管理这些依赖性更容易。它们自行创建依赖关系图,并确保在请求给定对象时,满足其所有依赖项。这消除了大量的样板代码并提高了生产率。

包起来

在这篇文章中,我试图至少给出依赖注入在软件开发中很重要的原因。我们开始使用功能方法,并转移到面向对象的世界。

在这里,我们知道有很多工具可以帮助我们建设和维护我们的依赖图。依赖关系到处都存在,应考虑它们。硬编码依赖项将使在不触摸它的情况下延长类的行为几乎是不可能的。利用这些工具在短期和长期帮助我们。

摄影者 偷偷摸摸的肘部uns

想加入我们的工程团队吗?
作者爆头
Frederico促廊
我们是来自50多个国家的750多人团队,具有共同激情的语言。从我们在柏林和纽约的办事处,我们帮助人们发现自我导向语言学习的乐趣。我们目前提供14种不同的语言 - 从西班牙语到印度尼西亚 - 数百万积极的订阅者选择学习。
我们是来自50多个国家的750多人团队,具有共同激情的语言。从我们在柏林和纽约的办事处,我们帮助人们发现自我导向语言学习的乐趣。我们目前提供14种不同的语言 - 从西班牙语到印度尼西亚 - 数百万积极的订阅者选择学习。

推荐的文章

为什么专家说这个应用程序是一种新语言的完美工具

为什么专家说这个应用程序是一种新语言的完美工具

这是Babbel的专业语言课程如何帮助您立即拥有基本对话。
7世界各地的令人敬畏的独立庆祝活动

7世界各地的令人敬畏的独立庆祝活动

从树上攀登比赛到竞争风筝飞行,这些古怪的独立庆祝活动使我们的最爱列表。
Android模块化项目 - 组织您的图书馆依赖项

Android模块化项目 - 组织您的图书馆依赖项

随着您的项目增长,您可能发现需要将其拆分为多个模块。当您在一家团队开发相同应用程序的公司工作时,这变得更加突出,但不同的功能。