Saturday, May 13, 2006

Ruby Design Pattern: Module Injection

Signal that objects of a class is designed to be extended via mixins by passing the mixin module via the constructor.


class ExtensibleClass
def initialize(mixin)
extend(mixin)
end
...
end

Ruby allows objects to be extended with new functionality at runtime. Any object can be extended in this manner by a call to the extend method. Some classes are designed to have their objects extended with new functionality.

Example


Consider a Strategy object that encapsulates a set of transformation rules for XML documents. The document is used by a treewalker. The treewalker walks the node tree representing the XML document. The Strategy class uses Module Injection:

strategy = Strategy.new(io, TransformationRules)
treewalker = TreeWalker.new(strategy)
treewalker.walk(xml_document)

The strategy object contains dispatch code that chooses which transformation rules to call depending on such things as node type (element, processing instruction, text, etc.) and node name.

The Strategy object also needs transformation rules. Keeping the transformation rules in a mixin module makes it possible to reuse the Strategy object with different rule sets.

Because the rules are kept in a module, it is possible to make the module up of several smaller mixins. In this example, TransformationRules can itself mix in several smaller rules modules. This is what the Strategy class might look like:

class Strategy

def initialize(io, rules)
@io = io
extend(rules)
end

def execute_before(node)
# Dispatch code
end

def execute_after(node)
# Dispatch code
end
end

And here is a sample treewalker. This one is for REXML:

module XmlUtil
class TreeWalker

def initialize(strategy)
@strategy = strategy
end

def walk(node)
@strategy.execute_before(node) if @strategy.respond_to? :execute_before
if node.instance_of?(REXML::Document)
walk(node.root)
elsif node.instance_of?(REXML::Element) then
node.children.each { |child|
walk(child)
}
end
@strategy.execute_after(node) if @strategy.respond_to? :execute_after
end
end
end

When to Use Module Injection


Use Module Injection when you want to signal clearly that an object must be extended with a mixin module.

You can also use it when you want to encapsulate a family of algorithms, similar to Strategy, but with the added flexibility of using modules.

Related Patterns


Dependency Injection, Inversion of Control, Strategy, Pollution Control.

No comments: