You're Already Using Dependency Injection

| 3 Comments

Ben Hengst gave a talk at Portland Perl Mongers last night about dependency injection and inversion of control in Perl. He said something completely true. "You've probably already done this, even though you didn't know it was called this."

At its core, dependency injection is a formalization of the design principle "Don't hard-code your dependencies." Consider the code:

sub fetch
{
    my ($self, $uri) = @_;
    my $ua           = LWP::UserAgent->new;
    my $resp         = $ua->get( $uri );

    ...
}

That's not bad code by any means, but it's a little too specific and a little less generic due to the presence of the literal string LWP::UserAgent. That might be fine for your application, but that hard-coding introduces a coupling that can work against other uses of this code. Imagine testing this code, for example. While you could use Test::MockObject to intercept calls to LWP::UserAgent's constructor, that approach manipulates Perl symbol tables to work around a hard coded dependency.

An alternate approach uses a touch of indirection to allow for greater genericity:

use Moose;
has 'ua', is => 'ro', default => sub { LWP::UserAgent->new };

sub fetch
{
    my ($self, $uri) = @_;
    my $ua           = $self->ua;
    my $resp         = $ua->get( $uri );

    ...
}

Now the choice of user agent is up to the code which constructs this object. If it provides a ua to the constructor, fetch() will use that. Otherwise, the default behavior is to use LWP::UserAgent as before.

Adding one line of code and changing one line of code has provided much more flexibility.

An alternate approach is to allow setting ua through an accessor instead of a constructor, but as far as I can tell the only reason to do this is if you're stuck in The Land of the Java Bean Eaters.

While the existing literature on inversion of control and dependency injection tends to throw around big words which have the effect of obfuscating rather than enlightening, the basic concepts is simple. You've probably already done it. Now you know what it's called and why it's useful, and probably also can find ways to use it where it helps.

(See also: how you select a DBD with DBI.)

3 Comments

This is all nice and true, but you have to remember that you have to pass an object with the same interface. In case of LWP::UserAgent you have to implement ~46 methods. If something looks like a DI, it doesn't mean that it is used in the correct way, maybe it's just because author likes lazy initialisation.

The existing literature just defined a terminology and uses it. It's not exactly the same for the dynamic languages like Perl, but there are many common things and design decisions that are very useful.

Cheers.

Caveat well taken, but in the case of constructor injection of an LWP::UA workalike, the only necessary method is get(). I have a half-finished rant about the brokenness of type systems in this sense.

Luckily in this case you can check for that with Moose. Either using a Role (which is kind of overkill in this case) or a duck_type.

has 'ua', isa => duck_type ['get'], is => 'ro', default => sub { LWP::UserAgent->new };

Modern Perl: The Book

cover image for Modern Perl: the book

The best Perl Programmers read Modern Perl: The Book.

sponsored by the How to Make a Smoothie guide

Categories

Pages

About this Entry

This page contains a single entry by chromatic published on August 11, 2011 10:07 AM.

Everything is a Compiler was the previous entry in this blog.

10 Years Later, Only 250 SLOC is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.


Powered by the Perl programming language

what is programming?