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

Discussion in 'Ruby' started by Woody Peterson, Sep 17, 2010.

  1. 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?
    --
    Posted via http://www.ruby-forum.com/.
     
    Woody Peterson, Sep 17, 2010
    #1
    1. Advertising

  2. Woody Peterson wrote:
    > 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.
    --
    Posted via http://www.ruby-forum.com/.
     
    Brian Candler, Sep 18, 2010
    #2
    1. Advertising

  3. Woody Peterson

    Robert Dober Guest

    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.
    --
    The best way to predict the future is to invent it.
    -- Alan Kay
     
    Robert Dober, Sep 18, 2010
    #3
  4. 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.



    --
    Posted via http://www.ruby-forum.com/.
     
    Woody Peterson, Sep 24, 2010
    #4
  5. On Fri, Sep 17, 2010 at 10:39 PM, Woody Peterson
    <> wrote:
    > 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 = { |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/
     
    Robert Klemme, Sep 24, 2010
    #5
  6. On Fri, Sep 24, 2010 at 3:42 AM, Woody Peterson
    <> wrote:

    > 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


    --
    remember.guy do |as, often| as.you_can - without end
    http://blog.rubybestpractices.com/
     
    Robert Klemme, Sep 24, 2010
    #6
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. -
    Replies:
    1
    Views:
    484
    John C. Bollinger
    Apr 11, 2005
  2. Code4u
    Replies:
    9
    Views:
    2,775
  3. Gary Wessle

    inheritance and composition

    Gary Wessle, Nov 16, 2006, in forum: C++
    Replies:
    3
    Views:
    297
  4. Replies:
    8
    Views:
    542
    James Kanze
    Jul 29, 2007
  5. Bill Atkins
    Replies:
    5
    Views:
    474
    Georgy
    May 31, 2004
Loading...

Share This Page