DEV Community

Thanh Tam
Thanh Tam

Posted on

What is Dependency Injection

Dependency injection is a software design pattern that allows a system to be more flexible and modular by decoupling the implementation of objects from the objects that use them. It does this by allowing the objects that use other objects (called clients) to specify which objects they will use (called dependencies) at runtime, rather than having the clients create the dependencies themselves.

The key benefit of dependency injection is that it enables clients to specify their dependencies at runtime rather than hardcoding them. This makes it easier to change the implementation of a dependency without affecting the client, as well as making it easier to test the client in isolation.

There are several ways to implement dependency injection, including constructor injection, setter injection, and interface injection. In constructor injection, the dependencies are provided to the client via the constructor when the client is created. In setter injection, the dependencies are set on the client using setter methods. In interface injection, the dependencies are passed to the client through an interface implemented by the client.

Here's an example of constructor injection in Kotlin:

interface FileSystem {
    fun readFile(path: String): String
}

class RealFileSystem: FileSystem {
    override fun readFile(path: String): String {
        // implementation to read a file from the real file system goes here
    }
}

class MyClass(private val fileSystem: FileSystem) {
    fun doSomething() {
        val fileContents = fileSystem.readFile("/path/to/file.txt")
        // use the file contents to do something
    }
}

val fileSystem = RealFileSystem()
val myClass = MyClass(fileSystem)
myClass.doSomething()

Enter fullscreen mode Exit fullscreen mode

In this example, MyClass has a dependency on FileSystem, which it needs to read a file. The FileSystem dependency is injected into MyClass via the constructor when MyClass is created. This allows MyClass to use any implementation of FileSystem without knowing how it is implemented.

It's also possible to use setter injection in Kotlin like this:

interface FileSystem {
    fun readFile(path: String): String
}

class RealFileSystem: FileSystem {
    override fun readFile(path: String): String {
        // implementation to read a file from the real file system goes here
    }
}

class MyClass {
    private lateinit var fileSystem: FileSystem

    fun setFileSystem(fileSystem: FileSystem) {
        this.fileSystem = fileSystem
    }

    fun doSomething() {
        val fileContents = fileSystem.readFile("/path/to/file.txt")
        // use the file contents to do something
    }
}

val fileSystem = RealFileSystem()
val myClass = MyClass()
myClass.setFileSystem(fileSystem)
myClass.doSomething()

Enter fullscreen mode Exit fullscreen mode

Here, the fileSystem dependency is set on MyClass using the setFileSystem setter method.

Overall, dependency injection is a useful pattern for achieving loose coupling between objects, which can make a system more flexible and easier to maintain.

Top comments (0)