Skip to content

Instantly share code, notes, and snippets.

@holsee
Last active December 15, 2015 22:59
Show Gist options
  • Save holsee/5336517 to your computer and use it in GitHub Desktop.
Save holsee/5336517 to your computer and use it in GitHub Desktop.
I'm no expert but this is how I would use modules instead of IoC container.
class Nails
def to_s
"nails"
end
end
class Glue
def to_s
"glue"
end
end
class PowerSaw
def initialize(power_source)
@power_source = power_source
end
def to_s
"#{@power_source} power saw"
end
end
class HandSaw
def to_s
"hand saw"
end
end
class Electricity
def to_s
"electrical"
end
end
class Carpenter
def build_something
puts "Building something in wood with #{fixings} and #{tool}"
end
end
module ManualToolsModule
def tool
HandSaw.new
end
def fixings
Glue.new
end
end
module PowerToolsModule
def power_source
Electricity.new
end
def tool
PowerSaw.new power_source
end
def fixings
Nails.new
end
end
# reopening the class to mix the module in
class Carpenter
include ManualToolsModule
end
# or (alt registration which replaces first registration)
class Carpenter
include PowerToolsModule
end
Carpenter.new.build_something
@holsee
Copy link
Author

holsee commented Apr 8, 2013

I guess you can use some meta-prog secret sauce to do the module inclusion dynamically on the instance (or even at a class level)

@andypike
Copy link

andypike commented Apr 8, 2013

Thx for this. From an OO point of view I have two issues with this:

  1. To change strategy, we need to modify all classes that include the module. To my mind this breaks OCP. A class should only have one reason to change and that is what it is responsible for. If this is a bigger system and multiple classes use the same mixin, we need to edit every class when we change strategy (from manual tools to power tools).
  2. When we use mixins, we are adding functionality to the receiving class. So Carpenter will have a method called fixings which creates it's collaborator (Nails). So this isn't IoC.

This method does help separate logic etc into different files but the objects that are created are not loosely coupled.

What do you think? Really interested in this subject in the Ruby world. I think I must be missing something, either there is a way to do this or the Ruby community is less pedantic about this sort of thing. I might be over thinking it but from a pure OO perspective I think there is something missing.

@andypike
Copy link

andypike commented Apr 8, 2013

Just re-looked at this and I think that my first point is less of an issue. As you reopen the class and include the module. Interesting thinking about this stuff in a dynamic world.

@holsee
Copy link
Author

holsee commented Apr 8, 2013

Andy I'm relatively new to ruby, so these are not words of a seasoned rubyist.

  1. Monkey patching does feel dirty and yea can be seen as ocp violation in this example.
  2. Dynamic languages are simple, in that if the dependency is created elsewhere I.e. no .new in the consumer then you are decoupled and are bound only by the methods and attrs consumed on the dependency as an interface.
  3. So as per point 2, you are decoupled from creation and you can swap in alternate dependencies as long as they quack like a duck. This includes mocks and stubs for testing.

I think everything is loosely coupled by the dynamic nature and the new instance creation is nowhere near the consumer. The module itself in this example could be refined, each method defined within simple acting like a factory method that you might see in the IoC container. The part that feels dirty is the monkey patching at a class level, where it would be nice to do this upon the instantiation on a per instance basis.

I still feel there is some value to be had from an IOC container and am not convinced otherwise myself. I'd be eager to work with you on this experiment as I too find it interesting, to see what the most idiomatic way would be to achieve your goals. Instance Lifetime management outside of the class is something I miss in ruby.

IoC + param injection is a pattern which really helps keep classes clean, tiny and single purposes, but 'ruby is like play dough' so by creating new instances outside of a container, favouring poor mans DI is no big deal as testing in isolation is totally possible by replacing a Class with a mock or stub version. This always bites you in the ass for testability in staticly typed languages. I think this is the major argument agains the need for a container.

@andypike
Copy link

andypike commented Apr 8, 2013

I totally get what you are saying here. I guess it's a mindset thing to some degree. Are classes loosely coupled in a dynamic language by default because it's easier to change the behaviour compared to a static language.

I wonder if IoC is more popular in status languages because it is the only way to get nice loosely coupled classes whereas in dynamic languages there are other options that are easy and so people take that approach even if these methods are not strictly IoC/loosely coupled as seen by a purest.

Just a word on testability. For me, being able to mock/test easily is not the goal of IoC but a nice side affect of doing it. Obviously in ruby we can mock anything using the language features but my feeling is that we do this because it's the easy way rather than the "right" way.

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