The following article covers the Law of Demeter which implies that clients can only communicate with their direct dependencies (let’s say friends) and never with transitive dependencies (dependency of my dependency).
https://proandroiddev.com/law-of-demeter-with-examples-in-kotlin-6e1cf75e3f94
First of all, what is Law of Demeter?
It’s also called Demeter’s law or LoD or Principle of Least Knowledge or “don’t talk to strangers”.
It was discovered in 1987 in Northeastern University, Boston, by Ian Holland
Here is the general formulation of the law:
- Only talk to your immediate friends.
- Each unit should have only limited knowledge about other units: only units “closely” related to the current unit.
- Or: Each unit should only talk to its friends; Don’t talk to strangers.
Violating the law, example in Kotlin
Law of Demeter
So, we’re easily violating this law and we don’t even notice most of the times.
Here is an example:
data class Car(val name: String, val engine: Engine)
data class Engine(val cc: CylinderCapacity, val power: Power)
data class CylinderCapacity(val value: Int)
data class Power(val value: Int)
fun main() {
val car = Car(name = "Polo", engine = Engine(cc = CylinderCapacity(value = 2000), power = Power(200)))
println(car.engine.power.value)
}
The fact that Car knows about Power and we can access the value in it directly through Car, is a violation of Law of Demeter. Why?
Let’s review the law:
Only talk to your immediate friends. Car’s immediate friends is Engine, not what’s contained in Engine (CylinderCapacity and Power)
We’re calling Power through Car, from the main function
How to fix it
Here is a possible way to fix it:
data class Car(private val name: String, private val engine: Engine) {
fun power(): String {
return "Car $name has ${engine.power()}"
}
}
data class Engine(private val cc: CylinderCapacity, private val power: Power) {
fun power(): String {
return "${power.value}hp"
}
}
data class CylinderCapacity(val value: Int)
data class Power(val value: Int)
fun main() {
val car = Car(name = "Polo", engine = Engine(cc = CylinderCapacity(value = 2000), power = Power(200)))
println(car.power())
}
So first of all let’s make some property private, it will help to avoid making them accessible from “strangers”:
data class Car(private val name: String, private val engine: Engine) {
data class Engine(private val cc: CylinderCapacity, private val power: Power) {
and expose them this way:
data class Car(private val name: String, private val engine: Engine) {
fun power(): String {
return "Car $name has ${engine.power()}"
}
}
data class Engine(private val cc: CylinderCapacity, private val power: Power) {
fun power(): String {
return "${power.value}hp"
}
}
fun main() {
val car = Car(name = "Polo", engine = Engine(cc = CylinderCapacity(value = 2000), power = Power(200)))
println(car.power())
}
Car calls Engine to know about Power, not Power directly.
Benefits of Law of Demeter
There are a lot of benefits but I’d like to highlight couple of them here.
Loose Coupling
Car is not directly coupled with Power. It’s true that Car knows about the concept of power, but it’s semantically correct since any Car has “power”, but the definition, rules, etc. are within the Engine.
For instance in the example the power is expressed in hp (Horse Power), so if we want to change that we just need to change Engine (maybe is better to move it to Power class? Maybe.), not Car.
data class Engine(private val cc: CylinderCapacity, private val power: Power) {
fun power(): String {
return "${power.value}hp"
}
}
// From hp to kW.
data class Engine(private val cc: CylinderCapacity, private val power: Power) {
fun power(): String {
return "${power.value}kW"
}
}
Here we’ve changed the power from hp to kW.
Encapsulation
Very similar to the previous point, if you change Power, should not affect Car. In this case we want to change Engine to take into account different types of powers (fuel vs hybrid for instance).
data class Car(private val name: String, private val engine: Engine) {
fun power(): String = "Car $name has ${engine.power()}"
}
data class Engine(private val cc: CylinderCapacity, private val powerSources: List<PowerSource>) {
fun power(): String = "${powerSources.sumOf { it.power() }}hp"
}
data class CylinderCapacity(private val value: Int)
data class ElectricPowerSource(private val value: Int) : PowerSource {
override fun power() = value
}
data class FuelPowerSource(private val value: Int) : PowerSource {
override fun power() = value
}
interface PowerSource {
fun power(): Int
}
fun main() {
val powerSources = listOf(
ElectricPowerSource(300),
FuelPowerSource(200),
)
val car = Car(name = "Polo", engine = Engine(cc = CylinderCapacity(value = 2000), powerSources = powerSources))
println(car.power())
}
Even if in this case the logic is really dirty, no change is required in Car, since it’s just calling the .power() method that is calling the engine’s one that is encapsulating the complexity of calculating the power of the engine, that could come from multiple power sources.
println(car.power())
#reads #fabrizio napoli #software engineering #lod #law of demeter #dependency