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.
Comments