依赖注入和服务定位器

What is true Dependency Injection in Android ?

什么是Android中真正的依赖注入?

Dependency Injection (DI) is a topic that has always been in discussion in the Android community. Personally speaking, DI is not a notion that is hard to understand, and its implementation should not be complex either. However, Android developers still keep talking, discussing and fighting for a clean solution.

依赖注入 (DI)是Android社区中一直讨论的主题。 就个人而言,DI并不是一个难以理解的概念,其实现也不应该太复杂。 但是,Android开发人员仍在继续讨论,讨论和争取一种干净的解决方案。

From my point of view, despite all the efforts from the community, we are still not getting a satisfying answer to this matter. One of the biggest barriers is the fact that Activity and Fragment are not supposed to have argument constructor (With AndroidX Fragment Factory this might change)and these components can be destroyed/recreated at any time in an application’s lifecycle. This makes implementing an ideal DI in Android almost impossible.

从我的角度来看,尽管社区做出了种种努力,但我们仍未获得令人满意的答复。 最大的障碍之一是,不应该将Activity和Fragment具有参数构造函数(使用AndroidX Fragment Factory可能会更改),并且可以在应用程序的生命周期中随时销毁/重新创建这些组件。 这使得在Android中实现理想的DI几乎是不可能的。

A few years ago, we did not have many choices for DI’s framework: Dagger was the only dominant one and it was also officially recommended by Google. It goes without saying that Dagger is rather complex to implement. Some people feel comfortable using it; but others — and not a small number either- are not. Why did I say so? Well, first of all, Dagger is hard and we did not have a choice, as I have mentioned earlier.

几年前,我们对于DI的框架没有太多选择: Dagger是唯一的主导者,它也是Google正式推荐的。 不用说,Dagger的实现相当复杂。 有些人使用它感到自在。 但是其他人(也不是少数)不是。 我为什么这么说? 好吧,首先,匕首很难,正如我前面提到的,我们别无选择。

Secondly, as I was observing the number of articles discussing other Kotlin frameworks like Koin (a much simpler framework that resolves the same thing Dagger does) and how people reacted to its appearance, I felt like people had been held back for so long that Koin appeared to be their “Eureka”. I admit that I was in that situation as well. At that time, I could not understand why Koin was so much more user friendly compared to Dagger while they were both solving the same problem.

其次,当我观察到讨论诸如Koin的其他Kotlin框架(解决Dagger所做的相同事情的更简单的框架)以及人们对它的外观如何React的文章数量之多时,我感到人们被抑制了这么长时间,以至于Koin似乎是他们的“ 尤里卡” 。 我承认我也处于这种情况。 当时,我不明白为什么Koin与Dagger相比都更容易使用,而他们俩都在解决相同的问题。

Until one day, I came across an article about Dagger and Koin, revealing to me a completely new notion: Service Locator. Koin was the first step in my DI’s discovering journey that made me feel less distasteful towards DI, and this notion, Service Locator, was the second step that made me understand more deeply about DI in the Android world. After all that, I came to acceptance with the idea that we could not have an ideal DI in Android, but it doesn’t matter, the world is never ideal anyway and we always have to adapt.

直到一天,我才看到一篇有关Dagger和Koin的文章,向我揭示了一个全新的概念: Service Locator 。 Koin是DI的发现之旅中的第一步,这使我对DI的感觉减少了,而Service Locator这个概念是使我对Android世界中的DI有了更深入了解的第二步。 毕竟,我接受了这样的想法,即我们无法在Android中拥有理想的DI,但这并不重要,无论如何世界都不是理想的,我们总是必须适应。

In the context of this blog, I will explain the difference between a Dependency Injector and a Service Locator. To do that, I, first of all, will show you a very basic implementation of DI. A DI pattern involves 3 types of classes:

在本博客的上下文中,我将解释Dependency InjectorService Locator之间的区别。 为此,我首先将向您展示DI的非常基本的实现。 DI模式涉及3种类型的类:

  • Client class (or Consumer class) : a class (dependant class) that depends on a service (or use a service to do things)

    客户类 (或消费者类 ):依赖服务(或使用服务来做事)的类(从属类)

  • Service class: a class (dependency) that provides a service

    服务类 :提供服务的类(依赖项)

  • Injector class: a class that is in charge of injecting Service into Client

    注入器类 :负责将服务注入到客户端的类

Consider the following example:

考虑以下示例:

We have a MessageService that helps to send a message to a given destination. For now, we do not know how the message is going to be sent, but we do know that there is a method taking care of that, so we use interface to describe the service.

我们有一个MessageService ,可以帮助将消息发送到给定的目的地。 目前,我们尚不知道如何发送消息,但我们确实知道有一种方法可以解决此问题,因此我们使用接口来描述服务。

interface MessageService {
  
    fun sendMessage(message: String, destination : String)
  
}

And an SMSService that uses SMS to send message

还有一个使用SMS发送消息的SMSService

class SMSService : MessageService {
    override fun sendMessage(message: String, destination: String) {
        println("Sending $message to $destination using SMSService")
    }
}

Next, we have a client class MessageClient who wants to use the MessageService to send a message. According to Dependency Inversion Principle, modules should depend on abstraction, so ideally, this client class should also be an interface. However, to simplify the example, I'm creating a concrete class:

接下来,我们有一个客户端类MessageClient ,它想使用MessageService发送消息。 根据依赖倒置原则 ,模块应依赖抽象,因此,理想情况下,此客户端类也应该是接口。 但是,为了简化示例,我创建了一个具体的类:

class MessageClient(private val service: MessageService) {
    fun chatToFriend(message: String, destination: String) {
        service.sendMessage(message, destination)
    }
}

And the last class which will be in charge of “injection”:

最后一班将负责“注射”:

class Component {
  
    val client : MessageClient = MessageClient(SMSService()) // SMSService is injected into MessageClient through constructor injection
  
    fun main(){
         val destination = "Moon"
         client.chatToFriend("Hello, I'm from the Earth!", destination)
    }
}

That’s all for a simple DI. Dagger follows this structure, but the thing is, in Dagger, Client part ( Activity / Fragment) still knows about the existence of Injector class because inject() method have to be called from the component to get them to enter into the dependency graph ( AppComponent). Here is an example of AppComponent and method onCreate() in a dependent Activity:

这就是简单的DI。 Dagger遵循这种结构,但事实是,在Dagger中, 客户端部分( Activity / Fragment )仍然知道Injector类的存在,因为必须从组件中调用inject()方法以使其进入依赖关系图( AppComponent )。 这是一个依赖的 Activity中的AppComponent和方法onCreate()的示例:

@Component(modules = [AppModule::class])
interface AppComponent {
    fun inject(mainActivity : MainActivity)
}

Method onCreate():

方法onCreate()

override fun onCreate(savedInstanceState: Bundle?) {
    (applicationContext as Appplication).appComponent.inject(this)
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
}

Because of this, Dagger is not a clean DI but it does provide a good enough service in a special environment like Android. And now, we will look at Koin, why it is not called a Dependency Injector but a Service Locator. Comparing with the example above, Koin does not provide an injection into Activity or Fragment. Look at how dependency is accessed from these components:

因此,Dagger不是干净的DI,但是在像Android这样的特殊环境中确实提供了足够好的服务。 现在,我们将研究Koin,为什么它不称为Dependency Injector而是Service Locator 。 与上面的示例相比,Koin不提供对ActivityFragment的注入。 查看如何从这些组件访问依赖项:

class MainActivity : AppCompatActivity() {
      private val viewModel : MainViewModel by viewModel()
}

This syntax by viewModel() is basically similar to a getter() method that gets objects from a Central Registry. And Koin is only a clean version of using object (singleton in Kotlin) to store things, and it provides a direct access to dependencies for anyone who has the demand. That is why it's called a Locator.

by viewModel()这种语法基本上类似于从Central Registry获取对象的getter()方法。 而且,Koin只是使用object (在Kotlin中为单个object )存储事物的干净版本,它为有需求的任何人提供了对依赖关系的直接访问。 这就是为什么它被称为Locator的原因

  • Note that this Central Registry does not only store dependencies but also the lazy function that initialises them later.

    请注意,该中央注册表不仅存储依赖关系,还存储稍后对其进行初始化的惰性函数。

That is the reason why Dependency Injection and Service Locator are two totally different ways to resolve the same problem of accessing dependencies from consumers. One injects and the other locates dependencies. It is an undeniable fact that the implementation of Koin is so much simpler than the one of Dagger; moreover, Koin is officially introduced as a DI’s framework:

这就是为什么依赖项注入服务定位器是两种完全不同的方式来解决从消费者访问依赖项的相同问题的原因。 一个注入,另一个找到依赖关系。 不可否认的是,Koin的实现比Dagger的实现简单得多。 此外,Koin被正式引入为DI的框架:

A pragmatic lightweight dependency injection framework for Kotlin developers.

适用于Kotlin开发人员的实用的轻量级依赖注入框架。

that’s why people love it and keep mistakenly using it, thinking that it’s a DI. However, Service Locator is often criticised as an anti-pattern, because of some disadvantages such as:

这就是为什么人们喜欢它并错误地使用它,以为它是DI的原因。 但是,由于一些缺点,通常将Service Locator批评为反模式 ,例如:

Examine the example below:

检查以下示例:

class MessageClient() {
    private val service = Locator.GetService<MessageService>()
    fun chatToFriend(message : String, destination : String) {
        service.sendMessage(message, destination)
    }
}
  • The fact that it provides a way to not use parameter constructor (dependencies are taken using statical getter()), hides the dependence of consumer classes and makes these classes harder to use and test. By not looking at the content of the class, we can not immediately extract dependencies which should be mocked to solely test the class.

    它提供了一种不使用参数构造函数的方式(使用静态getter()获取依赖关系),隐藏了消费者类的依赖关系,并使这些类更难以使用和测试。 通过不查看类的内容,我们无法立即提取依赖关系,应该对依赖关系进行模拟以仅测试类。

  • Not having a Component class to manage the initialisation, MessageClient class assumes that MessageService is already available in Locator, but what if it's not?

    由于没有Component类来管理初始化,因此MessageClient类假定MessageServiceLocator中已经可用,但是如果没有,该怎么办?

  • Client class depends now not only on its dependencies but also redundantly on the Service Locator class.

    现在,客户端类不仅依赖于其依赖项,而且还冗余地依赖于Service Locator类。

  • Reusability: what do we do if we want to reuse a module in another project that does not utilize the same Service Locator ?

    可重用性:如果我们想在另一个不使用相同服务定位器的项目中重用模块,该怎么办?

That is why there exists a group of developers who keep being loyal to Dagger still (I do not count Hilt because it is still in the early stage) and stay away from Koin. In my opinion, Dagger is actually the closest solution to DI, so it is always the best choice if we truly master it. However, there is nothing coming from nothing, Koin exists for a reason. The cost between its consequences and effectiveness — through its friendly syntax, fast and simple implementation, perfect compatibility with Kotlin — is still worthy. From my point of view, the biggest loss of using Koin is the misunderstanding of the notions: Dependency Injection and Service Locator. Figuring out these definitions, we are good to go.

这就是为什么有一群开发人员仍然忠于Dagger(我不算Hilt,因为它仍处于早期阶段)并远离Koin。 我认为,Dagger实际上是最接近DI的解决方案,因此,如果我们真正掌握它,它始终是最佳选择。 但是,没有一无所有,Koin之所以存在是有原因的。 通过其友好的语法,快速简便的实现以及与Kotlin的完美兼容性,在其后果和有效性之间付出的代价仍然值得。 从我的角度来看,使用Koin的最大损失是对以下概念的误解: 依赖注入服务定位器 。 弄清楚这些定义,我们很好。

TL;DR

TL; DR

Koin is not a Dependency Injector but a Service Locator and this pattern is usually considered as an anti-pattern. However, Koin is still worthy with its useful features. Let’s use Koin more precisely.

Koin 不是 依赖注入器,而是服务定位器 ,该模式通常被认为是反模式 但是,Koin仍然值得其有用的功能。 让我们更精确地使用Koin。

翻译自: https://medium.com/modulotech/dependency-injection-and-service-locator-6144ed55a8e

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值