-
-
Save holsee/5336517 to your computer and use it in GitHub Desktop.
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 | |
Thx for this. From an OO point of view I have two issues with this:
- 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).
- 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.
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.
Andy I'm relatively new to ruby, so these are not words of a seasoned rubyist.
- Monkey patching does feel dirty and yea can be seen as ocp violation in this example.
- 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.
- 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.
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.
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)