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?

asked Feb 19, 2016 at 13:27

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.

answered Feb 19, 2016 at 14:17

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.