Inheritance is Unlikely
Justin Marney, Former Viget
A common solution is to move the shared code into a base class and inherit.
# Filled with api interaction behavior class Base def api_specific_behavior puts "connect to api" end end class User < Base def user_specific_behavior puts "I am a user" end endThe problem with inheritance in this situation lies primarily in a subtle violation of the Liskov Substitution Principal which essentially states that inheritance should implement an "is-a" relationship. If we have a class called User that inherits from Base, are we trying to model the part of our domain where a User is a Base? This doesn't make sense given that we never actually have any Bases (i.e. instances of Base).
We created the class to share behavior not to model an aspect of the domain. Unfortunately, sharing behavior in this way creates a brittle, highly-coupled, and most importantly unnecessary relationship between the parent and child. The real pain of using this technique comes when you want to model a true inheritance relationship on the child. For example, we needed ActiveRecord objects that could also interact with an external API. Since both ActiveRecord and our design required classes to inherit from a base class we were forced to revise our design.
A better solution is to move shared behavior into a module.
module ApiInteractions def api_specific_behavior puts "connect to api" end end class User include ApiInteractions def user_specific_behavior puts "I am a user" end endThe pattern of using inheritance to share behavior most likely stems from the absence of a non-coupling behavior sharing mechanism in older OO languages. Modules specifically allow you to share behavior between classes without introducing an inheritance relationship. The behavior of a User in your domain doesn't include connecting to an HTTP server so why should that functionality be hard-wired into your User model via inheritance? Modules allow you to move these types of concerns out of your domain and into reusable packages.
What about the initialization method in my base class?
Any behavior in the initializer of the base class is specific to the concerns the base class is handling. That behavior should be available as part of the modules api and called from the host class.
Move initializer behavior into a method and call from the host class.
module ApiInteractions def api_initialization_behavior api_specific_behavior end def api_specific_behavior puts "connect to api" end end class User include ApiInteractions def initialize api_initialization_behavior end endThe above code highlights one last issue you should be aware of when refactoring. It is possible to couple your modules to your host classes. When you apply this technique avoid having a large (or even medium sized) interface between the module and its host. In the above case one method, api_initialization_behavior, is enough to get the job done. You should be shooting for one or two methods. Avoid using instance variables as well, use accessor methods instead. This way your module becomes more flexible and your model classes are not filled with code that is tightly coupled to the implementation of the module.
Wait a minute, ActiveRecord uses a Base class — it must be ok?
You might be wondering why ActiveRecord requires you to inherit from a base class when object persistence isn't really part of your domain model. Trust me, you aren't alone. Take a look at DataMapper's API for an alternative, module-based implementation.
The ideas in this post were inspired by the Pragmatic Studio Advanced Ruby Training. I have been hacking on Ruby for two years now and considered myself to be fairly adept in its mystical ways. Within the first ten minutes of training I had already learned about things I never knew existed. Want a good run down of Ruby C code conventions? Interested in coding your own custom control flow structures? Ever wanted to know how to use Drb and Rinda? This class will teach any Ruby programmer something. I highly recommend you attend if you have the opportunity.