Dependency Inversion states that high-level modules shouldn’t depend on low-level modules but on abstractions. In this demo, you’ll adhere to this principle to reduce coupling in your e-commerce app.
Start by opening the Kotlin playground in your browser. Download the course material from the GitHub link at the side of the video. Copy and paste the code from DependencyInversion.kts in the Starter folder for Lesson 4.
Your app uses a DatabaseService to persist data to the database. Run the app to see it at work:
Processing order...
Persist order amounting to 2400.0 to an SQLite Database.
You’re currently using an SQLite database. But then you need to swap that for an in-memory database during tests. With the current design, there’s no clean way to achieve this. You’ll either have to rewrite DatabaseService for testing or hack some logic that uses an in-memory database if the current execution environment is a test.
The best way to deal with this is to follow the dependency inversion principle. Making DatabaseService, a high-level class, depend on another high-level class, OrderService, is the problem. Instead, make it depend on an abstraction.
Refactor OrderService to depend on an abstraction via its constructor. First, create the abstraction for persisting orders:
interface OrderRepository {
fun saveOrder(shoppingCart: ShoppingCart): Boolean
}
Then, create an implementation for SQLite:
class SqliteOrderRepository : OrderRepository {
override fun saveOrder(shoppingCart: ShoppingCart): Boolean {
println("Persist order amounting to ${shoppingCart.getTotalOrderPrice()} to an SQLite Database.")
return true
}
}
Then, instead of tight coupling SqliteOrderRepository with OrderService, provide it via its constructor. Remember to use the abstraction instead of the concrete implementation:
class OrderService(private val orderRepository: OrderRepository) {
fun processOrder(shoppingCart: ShoppingCart) {
println("Processing order...")
orderRepository.saveOrder(shoppingCart)
}
}
In main, update orderProcessor instantiation:
val orderProcessor = OrderService(SqliteOrderRepository())
Rerun the app:
Processing order...
Persist order amounting to 2400.0 to an SQLite Database.
It’s just as before. The main benefit of this design is that you can easily swap in an in-memory database implementation for your tests. Create the new OrderRepository implementation:
class InMemoryOrderRepository : OrderRepository {
override fun saveOrder(shoppingCart: ShoppingCart): Boolean {
println("Persist order amounting to ${shoppingCart.getTotalOrderPrice()} to an In-Memory Database.")
return true // success
}
}
Use it in main to see it in action:
val orderProcessor = OrderService(InMemoryOrderRepository())
orderProcessor.processOrder(shoppingCart)
And the result is:
Processing order...
Persist order amounting to 2400.0 to an In-Memory Database.
That’s all for this demo. Continue to the concluding segment of this lesson.