Skip to content

Instantly share code, notes, and snippets.

@jszmajda
Last active April 20, 2016 21:29
Show Gist options
  • Save jszmajda/1ccb901e6a632ea6d86c to your computer and use it in GitHub Desktop.
Save jszmajda/1ccb901e6a632ea6d86c to your computer and use it in GitHub Desktop.
# Multiple inheritance with Modules as an alternative to injected composition
# from Sandi Metz's talk [Nothing is Something](http://confreaks.tv/videos/bathruby2015-nothing-is-something)
# Like Sandi's 'direct' DI method this has behavior outside of the base class
# that gets composed together. However in this gist I compose modules in class
# definitions instead of injecting collaborators.
# Tradeoffs between this and Sandi's version are that in this case the API consumer doesn't
# have to know how to make a RandomEchoHouse (no `house = House.new(formatter: Whatever.new)`),
# but also the API consumer can't make anything not already accounted for either.
module DefaultOrdering
def order(data)
data
end
end
module RandomOrdering
def order(data)
data.shuffle
end
end
module DefaultFormatting
def format(parts)
parts
end
end
module EchoedFormatting
def format(parts)
parts.zip(parts).flatten
end
end
class House
include DefaultFormatting
include DefaultOrdering
DATA = [ 'the horse and the hound and the horn that belonged to',
'the farmer sowing his corn that kept',
'the rooster that crowed in the morn that woke',
'the priest all shaven and shorn that married',
'the man all tattered and torn that kissed',
'the maiden all forlorn that milked',
'the cow with the crumpled horn that tossed',
'the dog that worried',
'the cat that killed',
'the rat that ate',
'the malt that lay in',
'the house that Jack built' ]
def initialize
@data = order(DATA)
end
def recite
(1..data.length).map {|i| line(i)}.join("\n")
end
def line(number)
"This is #{phrase(number)}.\n"
end
def phrase(number)
parts(number).join(" ")
end
def parts(number)
format(data.last(number))
end
attr_reader :data # personal preference I think
end
# Inheritance is valid here IMO because we are specializing behavior
class RandomHouse < House
include RandomOrdering
end
class EchoHouse < House
include EchoedFormatting
end
class RandomEchoHouse < House
include RandomOrdering
include EchoedFormatting
end
@jszmajda
Copy link
Author

Thanks very much for taking the time to go through this, it's awesome to be able to have discussions like this with you, I feel like I learn a lot every time we talk :)

I think your analysis is spot-on. Including the flog score is very interesting. You're naturally going to leave out the materialized concepts of RandomEchoHouse and friends, which will give you a lower score of course, but I don't imagine including those would add much (random_echo_house = House.new(orderer: RandomOrder.new, formatter: EchoFormatter.new), etc I suppose).

I think interestingly you can retain the value of presenting materialized concepts to the API consumer by adding a Factory class to your implementation (random_echo_house = HouseFactory.random_echo_house maybe?). This adds the pro of the module-based solution while retaining all of the pros of the (more open/closed!) composition based solution.

I also love your point about DI from even further outside the local context. I did that a ton when I used to write j2ee. I think people often conflate it with big nasty systems but it's a really great way to create clean code.

Thanks again for taking the time to review this, you rock! :)

@yitznewton
Copy link

I've been thinking about the inheritance (which includes mixins) vs. composition question a lot lately because I've had to modify a lot of heavily mixed-in code.

I visualize it like looking for an artifact on the ocean floor. Inheritance is like looking over a square mile. You know all the code that's doing whatever it's doing is in this class, you just don't know where the code is physically located; so it may take a long time to navigate through all the modules and figure out what overrides what.

With object composition, it's like looking in a mile-deep trench: you may need to navigate through a deep object graph, but to me, the way is clearer than trying to figure out which contributed bit of functionality is causing a certain effect.

When using a deep graph of small objects, you can often get a single class in its entirety on one editor screen, or a few folds deep. Comparing that with n open files worth of modules, I find it easier to understand one "single-class" class at a time, rather than a class composed of many modules.

Class responsibility, and therefore tests, is also more focused, in my experience.

It still depends on the design of the modules/classes in question, but these techniques seem to lean in these directions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment