ruby inheritance vs mixins

36,792

Solution 1

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.

Solution 2

I think mixins are a great idea, but there's another problem here that nobody has mentioned: namespace collisions. Consider:

module A
  HELLO = "hi"
  def sayhi
    puts HELLO
  end
end

module B
  HELLO = "you stink"
  def sayhi
    puts HELLO
  end
end

class C
  include A
  include B
end

c = C.new
c.sayhi

Which one wins? In Ruby, it turns out the be the latter, module B, because you included it after module A. Now, it's easy to avoid this problem: make sure all of module A and module B's constants and methods are in unlikely namespaces. The problem is that the compiler doesn't warn you at all when collisions happen.

I argue that this behavior does not scale to large teams of programmers-- you shouldn't assume that the person implementing class C knows about every name in scope. Ruby will even let you override a constant or method of a different type. I'm not sure that could ever be considered correct behavior.

Solution 3

My take: Modules are for sharing behavior, while classes are for modeling relationships between objects. You technically could just make everything an instance of Object and mix in whatever modules you want to get the desired set of behaviors, but that would be a poor, haphazard and rather unreadable design.

Solution 4

The answer to your question is largely contextual. Distilling pubb's observation, the choice is primarily driven by the domain under consideration.

And yes, ActiveRecord should have been included rather than extended by a subclass. Another ORM - datamapper - precisely achieves that!

Solution 5

I like Andy Gaskell's answer very much - just wanted to add that yes, ActiveRecord should not use inheritance, but rather include a module to add the behavior (mostly persistence) to a model/class. ActiveRecord is simply using the wrong paradigm.

For the same reason, I very much like MongoId over MongoMapper, because it leaves the developer the chance to use inheritance as a way of modelling something meaningful in the problem domain.

It's sad that pretty much nobody in the Rails community is using "Ruby inheritance" the way it's supposed to be used - to define class hierarchies, not just to add behavior.

Share:
36,792
Brad Cupit
Author by

Brad Cupit

coder (Java, Groovy), father, member of The Church of Jesus Christ of Latter-day Saints

Updated on July 12, 2022

Comments

  • Brad Cupit
    Brad Cupit almost 2 years

    In Ruby, since you can include multiple mixins but only extend one class, it seems like mixins would be preferred over inheritance.

    My question: if you're writing code which must be extended/included to be useful, why would you ever make it a class? Or put another way, why wouldn't you always make it a module?

    I can only think of one reason why you'd want a class, and that is if you need to instantiate the class. In the case of ActiveRecord::Base, however, you never instantiate it directly. So shouldn't it have been a module instead?

  • Chris Tonkinson
    Chris Tonkinson over 10 years
    This is a wise word of caution. Reminiscent of the multiple inheritance pitfalls in C++.
  • Marcin
    Marcin about 9 years
    Is there any good mitigation for this? This looks like a reason why Python multiple inheritance is a superior solution (not trying to start a language p*ssing match; just comparing this specific feature).
  • PL J
    PL J over 8 years
    The example shows it neatly - Truck IS A Vehicle - there is no Truck that wouldn't be a Vehicle.
  • PL J
    PL J over 8 years
    The example shows it neatly - Truck IS A Vehicle - there is no Truck that wouldn't be a Vehicle. However I'd call module perhaps SelfPropelable (:?) hmm SelfPropeled sounds right, but it's almost the same :D. Anyway I wouldn't include it in Vehicle but in Truck - as there ARE Vehicles that AREN'T SelfPropeled. Also good indication is to ask - are there other Things, NOT Vehicles that ARE SelfPropeled ? - Well perhaps, but I'd be harder to find. So Vehicle could inherit from class SelfPropelling (as class it wouldn't fit as SelfPropeled - as that's more of a role)
  • emery
    emery about 8 years
    This answers the question in a direct way: inheritance enforces a specific organizational structure that can make your project more readable.
  • Marcin
    Marcin about 8 years
    @bazz That's great and all, but composition in most languages is cumbersome. It's also mostly relevant in duck-typed languages. It also doesn't guarantee that you don't get weird states.
  • Laas
    Laas over 6 years
    Old post, I know, but still turns out in searches. The answer is partly incorrect - C#sayhi outputs B::HELLO not because Ruby mixes up the constants, but because ruby resolves constants from closer to distant - so HELLO referenced in B would always resolve to B::HELLO. This holds even if the class C defined it's own C::HELLO too.
  • Joel Blum
    Joel Blum over 3 years
    There are subtle difference between base class inheritance and including modules: gist.github.com/yoelblum/12b6a648d0ff7df4e3c0e95a55e8be1d
  • OuttaSpaceTime
    OuttaSpaceTime over 2 years
    can you further specify your statement and add an correct answer to the question?