The Audacious Code Experiment

Code mentoring, fun experiments, DDD and XP by Stephen Best

Factories for free

TL;DR

The [dependency inversion principle](http://en.wikipedia.org/wiki/Dependencyinversionprinciple) tells us that an object shouldn't directly reference or instantiate its collaborators. because doing so makes your code more complex, more rigid and less re-usable. This presents us with a need for factory objects that we can depend on instead of those concrete class references. Good news everyone! Ruby gives us this for free.

One of my favourite Ruby tricks is use the #method method to get a reference to a class's .new method and passing that to another object as a factory. This means there's little excuse not to INJECT ALL THE DEPENDENCIES.

Pass in the .new method

In this example we have a versioned document object which holds references to and is a facade for a collection of documents, which here play the role of individual versions.

When updated the versioned document creates a new document, stores the new data in it and adds it to its collection of versions.

class VersionedDocument
  def initialize(id, versions)
    @id = id
    @versions = versions
  end

  def attributes
    versions.last.attributes
  end

  def update(updated_attributes)
    new_version(updated_attributes).tap { |nv|
      @versions.push(nv)
    }
  end

  private

  def new_version(attributes)
    # This line is the one that don't belong
    # We have to replace this with an injected dependency
    Document.new(attributes)
  end
end

We can now replace the reference to Document.new with a factory. As the title promises we get that factory for free by way of a method reference.

class VersionedDocument
  def initialize(id, versions, document_factory)
    @id = id
    @versions = versions
    @document_factory = document_factory
  end

  # ...

  private

  attr_reader :document_factory

  def new_version(attributes)
    document_factory.call(attributes)
  end
end

# Now instantiation looks something like this
# Assuming `Document` is some sort of active record

VersionedDocument.new(
  "doc-id",
  Document.find_by_document_id("doc-id"),
  Document.method(:new),
)

This is great for testing and code flexibility, but puts the extra burden of having access to a document_factory on any object that wishes to instantiate a VersionedDocument.

Factory-factories (almost for free)

In the same way it would be great to create an object that would create a VersionedDocument for us without us needing to know about the actual class name or document factories.

The kind of object I'm talking about is a factory factory, a factory that creates other (specialised) factories. In more verbose languages this can be a nightmare, where you end up creating a crazy number of classes.

Luckily Ruby makes this very simple. Rather than add another class to the system, we can define it as the following.

versioned_document_factory = lambda { |id, versions|
  VersionedDocument.new(id, versions, Document.method(:new))
}

This lambda we've defined requires only the document data that belongs together and isolates its callers from knowledge of other dependencies. When or if things get hairier we can replace it with a proper class whose instances respond to #call.

Published: