I would love to see some composition examples

I saw this tweet:

https://twitter.com/solnic/status/623224611212251136

I lack the experience to understand “prefer composition over inheritance” in Ruby (or Rails).
I would love to hear reasons & examples for that.
(Also googling at the same time)

2 Likes

This refers to creating reuse by extracting functionality into objects that can be instantiated by your class, rather then pushing the functionality up the inheritance hierarchy (into a super class or mixin).

This example may be too simplistic, but consider the following classes that both create_foo:

class A
  def create_foo
    # does stuff
  end
end

class B
  def create_foo
    # does same stuff
  end
end

You might push the #create_foo method up into a super class or module (inheritance). In composition, we would create a new object to handle Foo creation:

class Foo
  def create
     # still does the same stuff
  end
end

Then we could instantiate this class and call it from our original classes:

class A
  def create_foo
    foo = Foo.new
    foo.create
  end
end

class B
  def create_foo
    foo = Foo.new
    foo.create
  end
end

The benefit of composition over inheritance is that it is far more malleable. Creating variable behavior in the inherited create_foo method would likely cause it to gradually grow into a nest of if and case statements. In the composition example, I only need to replace the Foo class with something that quacks like Foo.

2 Likes

This is a paradigm that’s not so much relevant in Ruby as in static languages. Here’s a crude example:

class Car
  attr_reader :engine
  def initialize
    @engine = 1200
  end
end

class Ford < Car
end

car = Ford.new
puts car.engine

My Ford needs an engine and one way of getting one is to inherit from something that has one, i.e. a Car. The trouble with this is that if you need to select which engine your Ford will have based on some conditional dynamic logic then you’re stuck. You can’t un-inherit Car and re-inherit from something else that has the engine you need (although in Ruby we can use modules and meta-programming to get round that). The alternative approach would be:.

class Engine
  attr_reader :capacity
  def initialize
    @capacity = 1200
  end
end


class Ford
  def engine
  	Engine.new.capacity
  end

end

car = Ford.new
puts car.engine

We’re now composing an engine when we need it, as opposed to expecting one from a parent class. The advantage here is that can dynamically plug-in whichever class we see fit at runtime, e.g. DieselEngine or PetrolEngine. This is a choice we don’t really have with inheritance, as we need to decide whether we need to inherit from a DieselCar or a PetrolCar right back when we define our class.

When it comes to choosing which method to use, i apply the old OO principle: If Class B is a type of class A then use inheritance. If Class B has an instance of A then use composition. That would mean that, in our example, my Ford is_a car and has_an engine. So I would use both methods here, inheriting from Car and composing an engine.

HTH

2 Likes

Thanks both of you :slight_smile:
I can at least remember the OO principle now (“is a” vs “has a”)
But with my understanding now I would expect the example to be:

Inheritance

class Car
  def engine_capacity
    1000
  end
end

class Ford < Car
  def engine_capacity
    1200
  end
end

Composition

class Engine
  attr_reader :capacity

  def initialize(capacity)
    @capacity = capacity
  end
end

class Car
  attr_reader :engine

  def initialize(engine)
    @engine = engine
  end

  def engine_capacity
    engine.capacity
  end
end

def Ford < Car
  # nothing to override now
end

A default engine can be applied too

class Car
  def initialize(engine)
    @engine = engine
  end

  def engine_capacity
    engine.capacity
  end

  def engine
    @engine ||= default_engine
  end

  private

  def default_engine
    Engine.new(1000)
  end
end

def Ford < Car
  private

  def default_engine
    Engine.new(1200)
  end
end
1 Like

@PikachuEXE you’re absolutely right, a Car has_an Engine and a Ford is_a Car, that’s the way it should be. I told you my example was crude :smile: The default engine is a nice touch, though I’d probably write it a bit less verbosely:

class Engine
  attr_reader :capacity

  def initialize(capacity)
    @capacity = capacity
  end
end

class Car
  DEFAULT_CAPACITY = 1000
  attr_reader :engine
  def initialize(engine_capacity)
    @engine = ( engine_capacity ? Engine.new(engine_capacity) : Engine.new(DEFAULT_CAPACITY) )
  end
end

class Ford < Car
end

car = Ford.new(2000)
puts car.engine.capacity #capacity = 20000

car2 = Ford.new(nil)
puts car2.engine.capacity # Default capacity

I know a lot of people frown upon inheritance, but I think in a dynamic, flexible language like Ruby, the disadvantages are not that bad. Besides, Ruby relies heavily on inheritance to enable some of the cool things we can do with it. The ROM is but an inheritance tree (one one axis anyway) after all. In general, I tend to stick to the has_a, is_a rule and not worry about false dilemmas too much. :thumbsup:

1 Like

I think anti-inheritance is a backlash from people working in poorly modeled systems.

A fictitious (though representative example): modeling “a person is_a manager” instead of "a person has_a role of manager.

This doesn’t mean inheritance is broken and never use it. Just that we now have a better understanding of our model and of OO. That’s a good thing.

1 Like

I know where you’re coming from. That’s why I’ll often talk of is_a_type_of and has_an_instance_of instead of simply has_a and is_a. Helps avoiding confusion.