Interface Segregation Principle in Ruby
ISP in Ruby
I think most of the people writing code in Ruby know the SOLID principles to a certain degree.
Today I want to share my thoughts about the interface segregation principle in the context of Ruby. Originally the SOLID principles were formalized with more statically typed languages in mind. In languages like Java and Go explicit interfaces are required for a certain type of polymorphism.
Thus to pass a different thing to a method, the client would have to implement all methods defined by the interface and
it totally makes sense to minimize the interface’s methods to whats necessary in order to make sure:
no client should be forced to depend on methods it does not use.
Duck typing solves the problem automatically
In Ruby and other languages where duck typing is possible a client only has to implement the smallest possible interface, which could be the one method, that is expected to be called.
Thus one might think this principle hardly applies to Ruby, which seems kind of true. However I think there is a certain danger, most Ruby developers are not aware of, and that is the public API, of the object that is used.
Duck typing creates other problems
Thinking about ISP in the context of Ruby, I noticed, that there is a negative side to it. Duck typing lets us accept everything that at least responds to a required methods. However this usually means Ruby programmers, including myself, pass objects that have a way bigger public API, like:
- active record models
- decorated models ( in case of a full delegation )
- or any other object that has additional methods
Where in the Java case, we make sure the required and available interface is minimal, in Ruby we only make sure the required interface is minimal, and we tend to ignore the available interface. This can easily lead other developers, our self, or library users to hook into those additional methods our object provides, which will cause tight coupling and unwanted dependencies.
This of course is a violation of the ISP.
I think one can think of passing object with unwanted APIs is like publishing an API, and thus there is no longer control of whether a client uses the additional methods or not. Thus changing the type of the object or any public method it has, should be handled as an incompatible change.
So it may appear as if duck typing solves the problems addressed by ISP, but it makes it easy to believe the interface is small, when it is huge in reality, causing unnecessary dependencies, up to the complete public API of a given object.
To solve this issue, an obvious solution would be to pass object with minimal interfaces. This can be done via the façades , decorators(that don’t automatically delete all methods) or by simple composition. Of course a blank class’ interface in Ruby is still huge, but we can at least make sure that we dont expose additional methods that the client should not be coupled to.
So please dont pass objects that expose an unnecessary large APIs to others.
A note on inheritance
This observation includes the usage of inheritance, mixins and decorators that
delegate_all, since all this approaches
are effectively inheritance and expose all methods when only some of them are needed.
I think it is interesting how Ruby can make sure that the client automatically depends on the smallest interface possible, at first glance, when he/she really may depend on the largest interface possible in the worse case.