Dependency-Inversion-Principle
What happens when you rip off a lizards tail? Does it’s DNA change? Does it become a different lizard? Does it die?
In case you actually aren’t sure what happens, the tail pretty much just comes right off and the lizard scurries away. If you see that same lizard a few days later, a tiny stub will be forming where the tail once protruded. A few more days and you’ll notice that stub has grown a bit more. Some months later, a new fully functional tail is right back where you had removed one. In all the lizard catching experiences I had during the times I lived in California as a child I never saw a lizard die because it’s tail came off. That’s a pretty great defense mechanism for change wouldn’t you say?
High level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.
I wonder about the code that constructs the tail and what kind of interfaces exists within the lizard. Modeling our programs, even object oriented programs after the real world can cause problems. However, I find it useful to consider how we might design software systems around problems solved in the natural world. Lizards require much more to operate than we’ll need for an example, so let’s think about the lizard chase in code but keep it in scope of the tail/scurry issue.
class Lizard
attr_accessor :tail
def initialize
@tail = LizardTail.new
end
def scurry_away
@tail.counter_balance
p "Scurries fast!"
end
def drop_tail
@tail.detach
end
def regenerate_tail(time)
@tail.grow(time)
end
end
class LizardTail
attr_accessor :length
def initialize
@length = 2
end
def grow(time)
if @length == nil
@length = time
else
@length += time
end
end
def detach
@length = nil
end
def counter_balance
if @length > 0
p "Tail Swings #{@length/2} to the right."
p "Tail Swings #{@length/2} to the left."
else
p "Tail can't help with balance, right now."
end
end
end
Let’s make a lizard.
lizzy = Lizard.new
=> #<Lizard:0x007fcdc2874cf8 @tail=#<LizardTail:0x007fcdc2874a28 @length=2>>
Great! That works.
Let’s see if lizzy
will scurry.
lizzy.scurry_away
=> "Tail Swings 1 to the right."
"Tail Swings 1 to the left."
"Scurries fast!"
Awesome, she’s fast! …but is she fast enough to get away from a predator who grabs her tail?
lizzy.drop_tail
=> nil
lizzy.scurry_away
=> NoMethodError: undefined method `>' for nil:NilClass
from (irb):91:in `counter_balance'
from (irb):59:in `scurry_away'
from (irb):103
Hmm…
If our lizard wants to scurry away it can’t if the tail were to be dropped. In other words, in the case of a single change, our lizard breaks. This is because we created a dependence for the lizard (High Level) on the lizard tail (Low Level).
In real life a lizard doesn’t depend on it’s tail to scurry. In fact it doesn’t really depend on it’s tail at all, it’s more of a defense mechanism. Recently, it was discovered that the tail that grows back, is not even a true lizard tail!
In real life what happens is the lizard details: all it’s parts, how they function and interact, depend on the abstraction that is the lizard DNA. The DNA is sort of like a policy layer. Then a level down we have the lizard mechanisms, like regeneration. One more level down we have the actual implementation, a tail. This way the lizard can operate and be lizardly without having it’s little life depend on such a lossy tail. It’s unfortunate we don’t have the same regeneration mechanism, but we do have the same drive to survive in the face of physical losses i.e. amputation, blindness, etc. This is thanks to a SOLID design that we share with all known living creatures. Perhaps these principles of OO design are not an invention so much as a discovery. I wonder what other discoveries await our kind as we grow technologically… I don’t know, but I know the only way to find out is to keep coding.