Inheritance and Composition
A little more notes after reading “Object-Oriented Design in Ruby”, regarding inheritance and composition.
Composition is the good selection in the most cases, where you don’t have a clear view that the inheritance fits in. Inheritance requires all the subclasses to be the specialization of parent classes (is-a relationship). Otherwise, the structure can deteriorate during the future enhancements.
Composition is the more flexibile option to take. Though it requires additional codes for delegating methods, ruby’s Mix-in or Forwardable module can help implementing the delegation with a few lines of code.
require 'forwardable' class A def a; "a is called"; end def b; "b is called"; end def c; "c is called"; end def d; "d is called"; end end class B extend Forwardable def initialize @a = A.new end def_delegators :@a, :a, :b # delegate methods through same name def_delegator :@a, :c, :e # delegate methods with different name (c -> e) def_delegator :@a, :d, :f # delegate methods with different name (d -> f) end b = B.new puts [b.a, b.b, b.e, b.f].join(", ") # => a is called, b is called, c is called, d is called
From a practical point of view, an design metric that matters most is the change cost in the future. If the change cost hit a threshold, changes cause unexpected defects, development speed slows down, and then the death spiral begins. Though we cannot always know what changes can happen in the future, we should guess and make design decision at some point. The composition is a safer way to go in the unclear eye-sight, and also Ruby provides a neat implementation sharing through Mix-in and Forwardable.