composition vs 'leaf-class mixins' (vs class inheritance)

W

Woody Peterson

I have a design question inspired by Rails 3 internals. In Design
Patterns in Ruby, the Strategy pattern is cited as a way to vary an
algorithm following the oft-spoken "prefer composition over inheritance"
advice. For a concrete example, let's say we wanted to fight in the
matrix:

class Person
attr_accessor :skills

def initialize(skills)
@skills = skills
end

def fight
@skills.each { |skill| skill.invoke }
end
end

class KungFu
def invoke
puts "5-fisted punch"
end
end

class Dodge
def invoke
puts "bullet-time"
end
end

class SeeTheMatrix
def invoke
puts "woah"
end
end

me = Person.new([])
me.fight # i've got nothing

neo = Person.new([KungFu.new, Dodge.new, SeeTheMatrix.new])
neo.fight # "5-fisted punch"
# "bullet-time"
# "woah"

Rails 3 might fight in the matrix like this:

class Person
def fight
end
end

module KungFu
def fight
puts "5-fisted punch"
super
end
end

module Dodge
def fight
puts "bullet-time"
super
end
end

class Neo < Person
include KungFu
include Dodge

def fight
puts "woah"
super
end
end

Neo.new.fight # "woah"
# "bullet-time"
# "5-fisted punch"

Maybe a class-based inheritance implementation would be:

class Person
def fight
end
end

class PersonKnowsKungFu < Person
def fight
"5-fisted punch"
super
end
end

class PersonMovesLikeThem < PersonKnowsKungFu
def fight
"bullet-time"
super
end
end

class Neo < PersonMovesLikeThem
def fight
"woah"
super
end
end

The goal of all of these would be to easily add new skills and
characters. Obviously the class-based inheritance example is brittle
because it'd take a refactoring to give someone bullet-time without
kung-fu. I'm mostly interested in the differences between the
"leaf-class-mixins" strategy and the strategy pattern. "Prefer
composition over inheritance" would suggest you should use the strategy
pattern, but mixing in modules sure seems like a simple and ruby-ish way
of achieving the same thing. The only difference I can see is that the
strategy pattern allows for run-time skill manipulation, which I don't
find myself needing often. What other advantages would I gain from
preferring composition here?
 
B

Brian Candler

Woody said:
Rails 3 might fight in the matrix like this:

class Person
def fight
end
end

module KungFu
def fight
puts "5-fisted punch"
super
end
end

module Dodge
def fight
puts "bullet-time"
super
end
end

class Neo < Person
include KungFu
include Dodge

def fight
puts "woah"
super
end
end

Neo.new.fight # "woah"
# "bullet-time"
# "5-fisted punch"

That to me is awful. The logic is obfuscated; the order in which the
methods are called is hard-coded to the order in which you mix in the
modules, and also depends on where 'super' is placed in each method.

The Rails3 approach you show above probably works well where you just
want to decorate existing methods with 'before' and 'after' actions,
sort of AOP-style.

But in general, I find the composition approach much clearer and more
widely applicable, because it's totally explicit what actions you're
invoking and in what order. You can add, remove, replace or re-order
actions at run-time (although you say that's not important to you), and
for different object instances.

For debugging, a simple #inspect will show you the stack of actions
belonging to this object. You can probably extract the relevant info
using #ancestors in the inheritance version, but what happens if you
want a second, orthogonal set of behaviours in your object? #ancestors
is essentially a linear list, so you'll have to know by inspection which
modules implement behaviour A and which implement behaviour B.

Perhaps most importantly, it also ensures separation of concerns. Mixins
can call each other willy-nilly, and share instance variables in the
underlying object; but if you have separate instances of @object1 and
@object2, then they won't even know of the existence of the other object
unless you intentionally pass them a reference. So this makes logic
clear: call @object1 to do some work, and if you want @object2 to do
something which depends on that, pass it the result.

I also find composition is far superior when you're unit testing and
mocking. (I think that unit testing your module KungFu above would be
painful).

When you learn "object oriented programming" from books or school, they
push you down the class inheritance route. I took me a long time to work
this out, but it is often a poor way to build real systems. It's a bit
like being brought up a Catholic and then finally realising you don't
have to believe in it after all.
 
R

Robert Dober

I have given this post quite some thoughts (for a change ;) and I
somehow believe that the complexity of your code comes from a
misconception (sorry for being blunt, but please be aware that I
express only an idea not a judgment). As Brian said correctly your
inherited approach is somehow confusing and concerns are quite
distributed over your classes. So far so good. But does this
necessarily mean that we have a case of composition here? Well I do
not believe this is the case. You are not composing your behavior you
are accumulating it.(1) In other words why not just expressing what
you really want to do first, like e.g.

class Person
def fight
@abilities.each do | ability |
invoke ability
end

def add_ability an_ability
@abilities << an_ability
end

def invoke an_ability
# I'll come to this later
end
end

Now we have complete control over how we combine fighting according to
our abilities, it seems an unrealistic simplification to apply all
your abilities in a fixed order for each fight anyway, thus I invite
you to imagine different implementations for @abilities.
As you can see we have decided about our "behavior" without any impact
on coupling which is hidden in #invoke.

E.g.

def invoke an_ability
instance_eval(&an_ability)
or
an_ability.new(self).use
or
....
end

and should be adapted to your needs without any thoughts on how we
"accumulated" the behavior.

I have of course only presented the upsides of my approach and am more
than happy to read about the downsides.

HTH
Robert
(1) A paradigm supporting my view would be to use traits to compose
your person, union of traits does not support a method fight in each
trait, but I am digressing.
 
W

Woody Peterson

Robert, if I understand you correctly, you're saying my 'composition'
example is too coupled to the skill implementation, specifically that
you might want to change both the way skills are invoked and the skill
itself? What I saw about your example is that skill invocation and
iteration are separated, it doesn't expose it's internals, and you could
inherit from Person to fight differently or invoke a skill differently
(in addition to swapping out skills). I think this is what you were
advocating, in which case I definitely agree, my example was pretty
naive.

Brian, thanks, that's perfect. I agree. I looked at rails 3 again
briefly, and they mitigate introspection concerns with some extra
methods for reading what's been included in a class. Not sure what they
do about the other points, ex. testing, but I'm still keeping it in
mind... perhaps for layering on behavior where access to state could be
considered desirable (ex. pulling something out of the request headers)
and where ordering isn't a primary concern.

It's not just that schools focus on inheritance as the key part of OO,
it's also that OO language designers tend to build in inheritance as a
very dominant concept (in opposition to ex. Alan Kay's statements that
message passing should be the dominant concept of OO). Solving problems
in terms of the languages' built-in dispatching sure seems like it
shouldn't be of such limited use. That said, I find a lot of value in
separating concerns and clean tests, so for now my real-world problems
will most likely avoid using this 'leaf-class mixin' technique as an end
goal.
 
R

Robert Klemme

I have a design question inspired by Rails 3 internals. In Design
Patterns in Ruby, the Strategy pattern is cited as a way to vary an
algorithm following the oft-spoken "prefer composition over inheritance"
advice. For a concrete example, let's say we wanted to fight in the
matrix:

=A0 =A0class Person
=A0 =A0 =A0attr_accessor :skills

=A0 =A0 =A0def initialize(skills)
=A0 =A0 =A0 =A0@skills =3D skills
=A0 =A0 =A0end

=A0 =A0 =A0def fight
=A0 =A0 =A0 [email protected] { |skill| skill.invoke }
=A0 =A0 =A0end
=A0 =A0end

=A0 =A0class KungFu
=A0 =A0 =A0def invoke
=A0 =A0 =A0 =A0puts "5-fisted punch"
=A0 =A0 =A0end
=A0 =A0end

=A0 =A0class Dodge
=A0 =A0 =A0def invoke
=A0 =A0 =A0 =A0puts "bullet-time"
=A0 =A0 =A0end
=A0 =A0end

=A0 =A0class SeeTheMatrix
=A0 =A0 =A0def invoke
=A0 =A0 =A0 =A0puts "woah"
=A0 =A0 =A0end
=A0 =A0end

=A0 =A0me =3D Person.new([])
=A0 =A0me.fight # i've got nothing

=A0 =A0neo =3D Person.new([KungFu.new, Dodge.new, SeeTheMatrix.new])
=A0 =A0neo.fight # "5-fisted punch"
=A0 =A0 =A0 =A0 =A0 =A0 =A0# "bullet-time"
=A0 =A0 =A0 =A0 =A0 =A0 =A0# "woah"

Rails 3 might fight in the matrix like this:

=A0 =A0class Person
=A0 =A0 =A0def fight
=A0 =A0 =A0end
=A0 =A0end

=A0 =A0module KungFu
=A0 =A0 =A0def fight
=A0 =A0 =A0 =A0puts "5-fisted punch"
=A0 =A0 =A0 =A0super
=A0 =A0 =A0end
=A0 =A0end

=A0 =A0module Dodge
=A0 =A0 =A0def fight
=A0 =A0 =A0 =A0puts "bullet-time"
=A0 =A0 =A0 =A0super
=A0 =A0 =A0end
=A0 =A0end

=A0 =A0class Neo < Person
=A0 =A0 =A0include KungFu
=A0 =A0 =A0include Dodge

=A0 =A0 =A0def fight
=A0 =A0 =A0 =A0puts "woah"
=A0 =A0 =A0 =A0super
=A0 =A0 =A0end
=A0 =A0end

=A0 =A0Neo.new.fight # "woah"
=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0# "bullet-time"
=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0# "5-fisted punch"

That would be a poor Neo who's skill set never changes.
Maybe a class-based inheritance implementation would be:

=A0 =A0class Person
=A0 =A0 =A0def fight
=A0 =A0 =A0end
=A0 =A0end

=A0 =A0class PersonKnowsKungFu < Person
=A0 =A0 =A0def fight
=A0 =A0 =A0 =A0"5-fisted punch"
=A0 =A0 =A0 =A0super
=A0 =A0 =A0end
=A0 =A0end

=A0 =A0class PersonMovesLikeThem < PersonKnowsKungFu
=A0 =A0 =A0def fight
=A0 =A0 =A0 =A0"bullet-time"
=A0 =A0 =A0 =A0super
=A0 =A0 =A0end
=A0 =A0end

=A0 =A0class Neo < PersonMovesLikeThem
=A0 =A0 =A0def fight
=A0 =A0 =A0 =A0"woah"
=A0 =A0 =A0 =A0super
=A0 =A0 =A0end
=A0 =A0end

Your last attempt via inheritance is unrealistic because the number of
combinations is not manageable. You can do the math yourself.
The goal of all of these would be to easily add new skills and
characters. Obviously the class-based inheritance example is brittle
because it'd take a refactoring to give someone bullet-time without
kung-fu. I'm mostly interested in the differences between the
"leaf-class-mixins" strategy and the strategy pattern. "Prefer
composition over inheritance" would suggest you should use the strategy
pattern, but mixing in modules sure seems like a simple and ruby-ish way
of achieving the same thing. The only difference I can see is that the
strategy pattern allows for run-time skill manipulation, which I don't
find myself needing often. What other advantages would I gain from
preferring composition here?

Your example reminds me more of a situation where an instance needs to
be able to fill out different _roles_ that does it sound like
strategy. Key point of strategy is that you have one strategy
implementation at any point in time. The roles may change over time
(you learn a new skill, you loose another). Any type related solution
(inheritance, mixin) to your problem breaks at this point because it
is static: you cannot change it at runtime (attempts have to be done
to demixin a module but AFAIK there is no good solution to this).

The point to understand is that a person _has_ some skills _at a
particular point in time_. So your first approach augmented with some
logic that let's the skill know who is performing at the moment is
actually superior to the other two approaches. That way you can
create an arbitrary number of Persons and give each one individual
skills and change their skill set.

Kind regards

robert


--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
R

Robert Klemme

It's not just that schools focus on inheritance as the key part of OO,
it's also that OO language designers tend to build in inheritance as a
very dominant concept (in opposition to ex. Alan Kay's statements that
message passing should be the dominant concept of OO). Solving problems
in terms of the languages' built-in dispatching sure seems like it
shouldn't be of such limited use. That said, I find a lot of value in
separating concerns and clean tests, so for now my real-world problems
will most likely avoid using this 'leaf-class mixin' technique as an end
goal.

I believe inheritance is overrated and often overused. In teaching
inheritance is often introduced as "is a" relationship which is
correct IMHO. There are two sources of overuse: people design "is a"
relationships between types which do not model reality in a way which
yields good software (i.e. reusable, modular etc.; as in this example
with skills).

The other source of overuse is using inheritance as "implementation
inheritance". This is a problem in languages which do not allow
private inheritance, i.e. in which you cannot prevent type
compatibility. In C++ you can inherit privately and in Eiffel there
are even more sophisticated mechanisms to control visibility of
inherited features. In those languages inheritance which does not
model "is a" is possible and OK. But in Java for example you neither
have MI nor control over visibility. Here, inheritance (and
implementing interfaces) always means "is a". In Ruby the situation
is similar to Java: mixin and inheritance are always visible (you can
do x.kind_of? ModuleY an get true if x's class mixes in ModuleY) so it
always means "is a". Although you can apply some tricks and use
metaprogramming to change that in a way, that will give you quirky
code.

Btw, thank you for bringing up an interesting topic for discussion!

Kind regards

robert
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,776
Messages
2,569,603
Members
45,189
Latest member
CryptoTaxSoftware

Latest Threads

Top