3

The general sentiment seems to be that composition is preferable, in most cases, over inheritance, as it leads to less coupling. But for a case like this,

module Publishable
  def new?
    @content.created_at > freshness_threshold
  end
end

class Book
  include Publishable

  def initialize(content)
    @content = content
  end

  def freshness_threshold
    t = Time.now
    Time.new(t.year)
  end
end

class Magazine
  include Publishable

  def initialize(content)
    @content = content
  end

  def freshness_threshold
    t = Time.now
    Time.new(t.year, t.month)
  end
end

where the methods implemented by a mixin depend on the presence of certain methods (freshness_threshold) and instance variables (@content) in the including class, would it be better to use inheritance? This is a contrived example, but imagine there are additional methods in Publishable that also depend on methods/variables in the including class.

Would it be preferable to define an abstract parent class that Book and Magazine descend from?

1 Answer 1

4

Mixins or traits are a third alternative to inheritance or composition. Whether they are better often depends on the particular implementation. The important feature of mixins or traits are that they are like abstract classes, but can be applied to multiple classes (and one class can consume multiple mixins). There are – in some implementations – some details regarding the method resolution order that are different between abstract classes and mixins, but otherwise from that they really are equivalent, up to the point that this difference is irrelevant if the language allows multiple inheritance (MI).

The thing where traits are really great is that they supply zero or more methods, and require zero or more methods. This makes using traits fairly straightforward. However, most languages don't have namespaces for methods – all definitions land in the same namespaces, which can lead to clashes between traits.

Your example doesn't even go that far, because your mixin only implicitly declares the requirements on using classes (that freshness_threshold must be a date, and @content must have a created_at field which must be a date). This is even worse than an abstract class in, say, Java – except that Java doesn't have MI, so you are forced to use composition when “inheriting” multiple behaviours.

In contrast, composition forces you to be more explicit about data flow. This is good, because your code becomes more obvious! In your particular example, the mixin only has a single method, so for all purposes it's equivalent to a free function (or Proc or whatever it's called in Ruby). This function requires two inputs: the freshness_threshold date, and the created_at date. Given such a free function is_new(creation_date, threshold_date), it is now very simple to make this behaviour avaiable to your classes without having to pollute the public interface of these objects with a public freshness_threshold date. E.g. they could define

def new?
  t = Time.now
  return is_new(@content.created_at, Time.new(t.year, t.month))
end

If your objects depend on is_new as an explicit dependency, e.g.:

def initialize(content, is_new = is_new_default)
  @content = content
  @is_new = is_new
end

, then an important advantage of composition appears: we can dynamically supply a different implementation (without having to resort to reflection or similar), which allows us to easily and separately test both the is_new operation and the code that depends on this operation. This advantage is less obvious for a simple comparison as here or other “functionally pure” operations, but will be truly important if the dependency supplies external side effects, e.g. network requests, database queries, …. Unless the language has dependency management baked-in to the inheritance system or mixin system, this is hard to beat.

If Books and Magazines were both used in a polymorphic context where you'd need to find out whether they are new? without knowing if they are a Book or a Magazine, then defining a common interface that declares this method would be sensible (assuming a more statically typed language). However, interface inheritance is completely orthogonal to the inheritance vs. composition discussion.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.