Everything You Love about Java is Everything I Love About Good Design

I just read Why I love everything you hate about Java. You should too. There are some very good points about modularity in there.

Unfortunately they are all mixed up with some unnecessarily combative us-vs-them rhetoric. Apparently in Nick Kallen’s view, as a Rubyist I slap my hands over my ears and start rocking back and forth when I hear the words “Dependency Injection” or “Factory”.

Let us be clear. If you are a Rubyist and your hackles do rise at the phrase “Dependency Injection”, you need to check yourself. Using a dynamic language shouldn’t mean throwing the Gang of Four out the window. And anyway, you probably use software daily – such as Rails – which is just lousy with enterprise design patterns.

The patterns Nick cites – Dependency Injection, Factory, and Decorator – are patterns I use daily. They are some of my favorites, to the point that they are practically second nature. For instance, here’s a paraphrased and simplified example from some production code I wrote:

def execute_shell_command(command_line, options={})
  shell_command_maker = options.fetch(:shell_command_maker) {
    ShellCommand.method(:new)
  }

  command = shell_command_maker.call(command_line, options)
  command.execute!
end

This example combines Factory and Dependency Injection and tops them with some Convention Over Configuration sauce. The dependency on a command line object is injectable, and we inject it by passing in an optional factory named :shell_command_maker. This factory, rather than having to be a Factory class, can be any callable object – such as a lambda. If no option is specified, the code uses ShellCommand.new() to instantiate the object. We can inject a different command line class:

execute_shell_command("ls", :shell_command_maker => RemoteCommand.method(:new))

But we can just as easily inject something fancier. Lets inject a ShellCommand wrapped in a logging Decorator:

require 'delegator'
class CommandLogger < SimpleDelegator
  def initialize(shell_command, logger=Logger.new($stderr))
    @command = command_line
    @logger = logger
    super(shell_command)
  end

  def execute!
    @logger.info "Executing '#{@command}'"
    super
  end
end

logger = Logger.new($stdout)
make_logged_shell_command = lambda do |command_line, options|
  CommandLogger.new(ShellCommand.new(command_line, options), logger)
end
options = {
  :shell_command_maker => make_logged_shell_command
}
execute_shell_command("ls", options)
execute_shell_command("ps aux", options)
# ...

What I find repugnant about Java development is not the use of industrial strength patterns, but the fact that using those patterns in Java is so awkward, high-ceremony and obtrusive that people wind up writing entire books on simple concepts such as dependency injection. A fact which Nick seems to have grasped as well, since he’s writing his examples in Scala, a language with a level of expressiveness similar to that of Ruby.

The Java ecosystem warps your brain into a mode of thinking where modularity patterns like DI and decoration are like ancient gods which can only be invoked with a great deal of pomp and ceremony. And I think that perspective tends to make it hard to see that other programmers use the same patterns, just with less fanfare. For an even better take on this topic than mine, read Jamis Buck’s classic account of writing and discarding two Ruby DI frameworks.

This entry was posted in Uncategorized and tagged , , , , . Bookmark the permalink.
  • http://haven.loki.ws Josh Szmajda

    Great post, I was reading the article you linked and thinking “hey you can totally do this with convention in ruby”. Thanks for the examples :)

  • http://twitter.com/herberthamaral Herberth Amaral

    I think the problem isn't with Java but with non-dynamic languages… C# (even the 4th version) has the same problem, for example.

    Good post :)

  • http://twitter.com/herberthamaral Herberth Amaral

    I think the problem isn't with Java but with non-dynamic languages… C# (even the 4th version) has the same problem, for example.

    Good post :)

  • http://twitter.com/t_crayford Tom Crayford

    Minor thing, but options={} is worthless in that wrapper method. Because you call fetch, the user of this api (who doesn’t pass in options, because it has a default), can quite happily call this method without any argument errors. Instead, they will hit an error on the fetch, which is far more confusing than an argument error.