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 Book
s and Magazine
s 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.