Ruby Inheritance VS Mixins

ruby inheritance vs mixins

I just read about this topic in The Well-Grounded Rubyist (great book, by the way). The author does a better job of explaining than I would so I'll quote him:


No single rule or formula always results in the right design. But it’s useful to keep a
couple of considerations in mind when you’re making class-versus-module decisions:

  • Modules don’t have instances. It follows that entities or things are generally best
    modeled in classes, and characteristics or properties of entities or things are
    best encapsulated in modules. Correspondingly, as noted in section 4.1.1, class
    names tend to be nouns, whereas module names are often adjectives (Stack
    versus Stacklike).

  • A class can have only one superclass, but it can mix in as many modules as it wants. If
    you’re using inheritance, give priority to creating a sensible superclass/subclass
    relationship. Don’t use up a class’s one and only superclass relationship to
    endow the class with what might turn out to be just one of several sets of characteristics.

Summing up these rules in one example, here is what you should not do:

module Vehicle 
...
class SelfPropelling
...
class Truck < SelfPropelling
include Vehicle
...

Rather, you should do this:

module SelfPropelling 
...
class Vehicle
include SelfPropelling
...
class Truck < Vehicle
...

The second version models the entities and properties much more neatly. Truck
descends from Vehicle (which makes sense), whereas SelfPropelling is a characteristic of vehicles (at least, all those we care about in this model of the world)—a characteristic that is passed on to trucks by virtue of Truck being a descendant, or specialized
form, of Vehicle.

Why does DataMapper use mixins vs inheritance?

I think the idea is that ActiveRecord considers the database backed aspect to be the key feature of a model class so it inherits that behavior. DataMapper looks like it considers being database backed to just be an aspect of a class that can be added to a class.

That's my guess. Yehuda Katz could tell you definitively.

Using multi-level inheritance instead of ruby mixins

Two main reasons:

You can only inherit from one class, but you can mix in as many mixins as you want. This means that inheritance is extremely "expensive" in the sense that if you are forced to use inheritance, you are forced to "use up" 100% of your "inheritance resources".

You can compose mixins any way you want. In your example, I can only get the methods of A, the methods of AB, and the methods of ABC. I can, for example, not get only the methods of B. With mixins, I can compose them in any combination: only A, only B, only C, AB, AC, BC, and ABC. I can also compose them in any order I want: I can have the methods of C override the methods of B or I can have the methods of B override the methods of C.

I can easily imagine an object that is both Enumerable and Comparable. (For example, Strings are Comparable and before Ruby 1.9, they also used to be Enumerable.) In your proposed world, this would only be possible if either Enumerable inherits from Comparable or Comparable inherits from Enumerable. However, this will not work: Numerics are Comparable but not Enumerable, Arrays are Enumerable but not Comparable.

There is also a more philosophical / semantic reason: enumerability and comparability are completely orthogonal concepts, why would you tie them together so closely? Inheritance is one of the closest forms of coupling we have, tying two concepts to closely together that don't actually have anything to do with each other, is just wrong.

Ruby: mixins, inheritance and constants name resolution

Due to the way constants are resolved based on definition scope and not in inherited scopes you'll need to bridge that with a method call:

class ABase
def action
# Within this scope only constants defined in `ABase` are resolved.
# As ABase::C and ::C (root-level) don't exist, C can't be resolved.
# However, a method `c` defined in a subclass will be.
c.new.foo
end
end

module M1
class A < ABase
def c
C
end
end

class C
def foo
puts "M1::C foo"
end
end
end

module M2
class A < ABase
# This resolves C as either M2::A::C, M2::ABase::C, M2::C or ::C,
# whichever it finds first.
def c
C
end
end

class C
def foo
puts "M2::C foo"
end
end
end

Then you get the expected results:

M1::A.new.action
# => M1::C foo
M2::A.new.action
# => M2::C foo

When do we use ruby module vs using class composition?

When the relationship between Helper and the MyStuff class is one of ownership, use composition. This is known as a "has-a" relationship. For example, let's say you have Person class and a Car class. You would use composition because a person has a car:

class Person
def initialize
@car = Car.new
end
end

class Car
def accelerate
# implementation
end
end

When Helper "acts like" MyStuff, use a module mixin. Helper, in this case, takes on the role of MyStuff. This is a bit different than a "is-a" relationship, which would imply that you should use traditional inheritance. For example, let's say we have a Person class and a Sleeper module. A person takes on the role of a sleeper sometimes, but so do other objects--instances of Dog, Frog, or maybe even Computer. Each of those other classes represent something that can go to sleep.

module Sleeper
def go_to_sleep
# implementation
end
end

class Person
include Sleeper
end

class Computer
include Sleeper
end

Sandi Metz's Practical Object-Oriented Design in Ruby is an excellent resource for these topics.

Are modules == mixins in ruby?

Mixins are a language concept that allows to inject some code into a class.

This is implemented in Ruby by the keyword include that takes a Module as a parameter.

So yes, in Ruby, mixins are implemented with modules. But modules have other uses than mixins.

For example, modules can also be used for namespacing your classes or encapsulating utility functions to keep from polluting the global namespace.

Use mixin method over inherited one

If you can modify the source code of Node, then let it prepend Enumerable instead of include Enumerable.

If you can't, then you can grab the instance method select from Enumerable and bind it to the instance of Node, then call it.

node = Node.new(...)
Enumerable.instance_method(:select).bind(node).call


Related Topics



Leave a reply



Submit