loading...

Template design pattern in Ruby. Any reason not to use a module instead of a superclass?

briankephart profile image Brian Kephart ・1 min read

The classic template design pattern uses inheritance to declare which methods its subclasses must define, like so:

class Template
  ABSTRACT_METHOD_ERROR = 'You forgot to define that method.'

  def method_defined_in_class
    raise ABSTRACT_METHOD_ERROR
  end

  def method_not_defined_in_class
    raise ABSTRACT_METHOD_ERROR
  end
end

class ObjectFromTemplate < Template
  def method_defined_in_class
    'You remembered! No errors here.'
  end
end

obj = ObjectFromTemplate.new

obj.method_defined_in_class # => 'You remembered! No errors here.'

obj.method_not_defined_in_class # => RuntimeError (You forgot to define that method.)

I have been wondering if there is a reason not to use a module for the template instead.

Module Template
  ABSTRACT_METHOD_ERROR = 'You forgot to define that method.'

  def method_defined_in_class
    raise ABSTRACT_METHOD_ERROR
  end

  def method_not_defined_in_class
    raise ABSTRACT_METHOD_ERROR
  end
end

class ObjectWithTemplate
  include Template

  def method_defined_in_class
    'You remembered! No errors here.'
  end
end

obj = ObjectWithTemplate.new

obj.method_defined_in_class # => 'You remembered! No errors here.'

obj.method_not_defined_in_class # => RuntimeError (You forgot to define that method.)

The second way seems obviously better to me (for all the reasons that composition is generally to be favored over inheritance), but I've never seen it done this way. Any thoughts or experiences to share?

Posted on by:

briankephart profile

Brian Kephart

@briankephart

I play guitar and bass. Sometimes I code.

Discussion

markdown guide
 

We have both cases in our codebase. The trickiest problems I've seen come when you inadvertently want to end up using instance methods from the subclass (or inheritor) in the abstract class. In either case if you do that you end up creating a coupled dependency and ruining your clean architecture. The difference between these styles is essentially how you want to handle polymorphism.

With inheritance it's more explicit and easier to identify an object's behavior in the code, whereas with composition it's more implicit and duck-typing makes your code more flexible. The second style you showed are called concerns in Rails.

Another issue that I've seen with composition is when you start nesting these modules, and including modules in modules in classes. The deeper you go the harder it is to follow what's happening.

 

Thanks for the reply! I thought this seemed like a good approach to the pattern, so it's nice to hear someone else has used it effectively.

... end up using instance methods from the subclass (or inheritor) in the abstract class.

I thought abstract classes weren't meant to be instantiated, just to provide behavior to their subclasses. Have I misunderstood?

 

You're definitely right, abstract classes are not meant to be instantiated. That does not mean that the methods they implement are clean from pulling in dependencies, and when mixins/modules end up depending on their child classes (same with abstract classes), then you have problems.

module Commentable
  def comment(text)
    # You now depend on methods of the child class
    self.comments << text 
  end
end

class Post
  include Commentable
end