I recently came across this reddit, demo'ing "dependency injection and inversion of control" in what I think is C#/.Net?
It resulted in a predictable spate of comments on reddit saying basicaly "oh my gosh, dependency injection is so evil, just look at that."
But it got me thinking about how I'd handle the same issues in ruby, and I think the issues to be handled are real issues. So let's look at that.
I don't understand the (.Net?) framework being used in the original example, and I don't feel like reproducing an app environment that would do similar in ruby, so let's just stick to the heart of the matter. They've got some class (not entirely sure which one it is in their tree) that has a method that processes payments, translating that method into ruby it looks something like this:
class SomeClass
def process_payment(string orderData)
paymentProvider = PayPalPaymentProvider.new
result = paymentProvider.Execute(orderData)
MessageBox.Show(result.to_string) # yeah, just assume this exists somewhere
end
end
Now, the problem is that we've hard-coded process_payment
to use PayPalPaymentProvider. Why is this a problem? Well, it may not be, but it legitimately might be, because it might make testing of SomeClass difficult (when you don't actually want to connect to PayPal in testing), or it might make things difficult if your app later expands to handle more real payment providers. I think the original author is not crazy to think some refactoring is called for here.
How can we handle this? Well, here's one way that "Dependency Injection" might look in ruby:
class SomeClass
class << self
attr_accessor :default_payment_provider
end
self.default_payment_provider = PaypalPaymentProvider
def process_payment(string orderData,
payment_provider = self.class.default_payment_provider.new)
result = payment_provider.Execute(orderData)
MessageBox.Show(result.to_string) # yeah, just assume this exists somewhere
end
end
Note:
-
It's actually completely backwards compatible with the previous code, anything can still call
process_payment(orderData)
, and get the same behavior they got before. -
Alternately, something can call `process_payment(orderData, payment_provider_instance) to call that particular invocation with a different payment provider (any class that implements #execute to do what you want)
-
Alternately, you can globally set
SomeClass.default_payment_provider = SomeOtherClass
to change the default payment provider. This might be useful in testing. -
Unlike strongly typed languages, we have no interfaces to document or enforce that the class/instance we are using impelements an #execute method to do what we want. This is just a difference between ruby and Java-like languages, and I don't neccesarily find it better in ruby, I point it out precisely because it is potentially a pitfall to be aware of. Strongly typed languages and community practices like in C# or Java compared to weakly typed languages like ruby have trade-offs;
-
but my point here is that apparently ruby's approach leads to much more sane 'dependency injection' -- note well that there's absolutely no need of a huge "dependency injector library", with confusing XML configuration or anything else.
"Dependency Injection" in ruby can be so simple that it almost doesn't even need a name, it's just "abstracting out something that varies, via parameterization", same as we do all the time when we write and refactor code. If anything, I think the name for what's going on here is actually the 'strategy pattern' -- the thing that led to PaypalPaymentProvider being it's own class in the first place, instead of just some methods in the original class. If you had started out with that logic just in methods of the same class, the recognition that you actually needed it parameterized, combined with knowing the 'strategy' pattern (by name or intuitively) is what would lead you to put it in it's own class and parameterize it in a refactoring.
But I suggest that what I've done here is essentially the same pattern as in the original C# (?) example. If that was 'dependency injection', so is this.
So while it almost seems so simple it doesn't need a name -- knowing of the strategy pattern, and even knowing something of what the heck people mean by "dependency injection" in other languages helps me arrive at this solution quicker when I need it, or build it in from the start when it seems likely to be needed. I am not a pattern hater -- I don't actually believe that 'patterns are just ways of working around language limitations'; rather, if the language (or framework/library) gives you adequate support, then applying patterns are so easy and simple that they aren't monstrous, and in some cases are so intuitive you don't even need to know the name of it. But being familiar with some commonly useful patterns can still be helpful in arriving at a nice solution quickly.
Because it can be done so backwards-compatibly, there's not neccesarily a need to abstract everything possible out in this way when you start. It's easy enough to add later where you discover you actually need it. There is such a thing as "premature abstraction" (aka over-engineering).
On the other still positive hand, if done sensibly it can be so simple, without adding much complexity or making the code any harder to read or follow, why not add it when writing initially if it intuively seems to make sense? The YAGNI absolutists won't like it, but I think there's nothing special about 'dependency injection' here, it's just a usual case of writing at the proper level of abstraction, factoring out things that may vary as parameters, which we make intuitive decisions about all the time when practicing the craft (craft!) of writing software.
Okay, so I store the default 'strategy' (paypal) in a class var.
People don't like class variables, considering them a 'code smell' -- but what's the reason for this? It's essentially because it's global state. But Java/C# "dependency injection" is STILL global state, is it not?
Sometimes it actually makes sense to have global state, and a large subset of such times is the one we call 'configuration', which is essentially what we're doing with the settable default strategy.
Now, configuration is actually somewhat of a 'not clear best practice' problem in ruby, as evidenced by the too-many gems to do configuration for you available, each with their own not-quite-right balance of simplicity/flexibility/power.
But if are going to have global state, why not do it simply, instead of a monstrous 'dependency injection' framework?
If it's a rails app, instead of sticking it in a class var, you could put it in ./config/application.rb in your own custom config.my_key.
Although if you do it with class-level state carefully, you can have better behavior under inheritance and such -- a sub-class of SomeClass can inherit SomeClass's default or set it's own (just for it and it's subclasses). But the straighforward Ruby class variable approach I demo'd above wont' do that. Class state in ruby ends up being kind of 'too many ways to do it'. ActiveSupport's class_attribute is one way I like when I have ActiveSupport.
If someone did end creating the 'perfect' flexible-enough-to-fit-all-cases 'configuration' library for ruby, would it end up being just as monstrous as 'dependency injection' in other languages? Maybe. But then I guess it wouldn't be 'perfect'. Maybe better to just do it case by case as above, I dunno. Software is hard.
Or is what I've suggested a terrible way to do things in ruby too? I'm no architecture expert, I'm just muddling through trying to find the simplest ways to concisely support the behavior I need, same as everyone else. Let me know if you think there's a better way.
Some people might do this in ruby by implementing the methods in a mix-in module, rather than a seperate class, include'ing that module in SomeClass, and then somehow at runtime swapping that module for a different module or over-riding it by including a different module, either at the class level or even the instance level with extend
.
But I find those types of solutions more complex, not less, than putting a strategy like this in it's own class/object. Let's face it, mix-in modules like this are really nothing but multiple inheritance for ruby (but only of 'abstract' classes), and are decidedly not "composition over inheritance". If giant XML-based dependency injection frameworks are the tar pit of Java/C# developers, then crazy complicated mix-in interactions are the tar-pit of ruby developers. Which lead to the same problems that avoiding multiple inheritance and 'composition over inheritance' are meant to avoid -- weird hard to predict interactions, hard to follow/read code, undocumented interface dependencies between classes/modules, etc.
Ruby style duck-typing makes it so easy to use actual composition instead, and it leads to much more readable and testable and clearly concerns-seperated code, why not just do it?
I've looked into similar questions for Python. Here's an outdated presentation I gave in April 2010:
http://code.google.com/p/dippy/
and here's a somewhat more recent comment on the subject:
http://www.reddit.com/r/programming/comments/mpusp/9_things_i_learned_from_reading_the_clean_coder/c32yick