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?
Top comments (3)
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.
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.