Refactoring: Refactorings/Isolate Dynamic Receptor
Jump to navigation
Jump to search
A class that uses method_missing can become painful to alter. method_missing is a powerful tool that provides a lot of options, but if incorrectly placed, it can be difficult to determine which class is responsible for what methods.
Solution: introduce a new class and move the method_missing logic to that class.
Mechanics
- Create a new class whose sole responsibility is to handle the dynamic method calls
- Copy the logic from method_missing on the original class to the method_missing of the focused class
- Create a method on the original class to return an instance of the focused class
- Change all client code that previously called the dynamic methods on the original object to call the new method first
- Remove the method_missing from the original object
- Test
Example
class Recorder
instance_methods.each do |meth|
undef_method meth unless meth =~ /^(__|inspect)/
end
def messages
@messages ||= []
end
def method_missing(sym, *args)
messages << [sym, args]
self
end
def play_for(obj)
messages.inject(obj) do |result, message|
result.send message.first, *message.last
end
end
def to_s
messages.inject([]) do |result, message|
result << "#{message.first}(args: #{message.last.inspect})"
end.join(".")
end
end
class CommandCenter
def start(command_string)
# ...
self
end
def stop(command_string)
# ...
self
end
end
Recorder = Recorder.new
recorder.start("LRMMMMRL")
recorder.stop("LRMMMMRL")
recorder.play_for(CommandCenter.new)
After
class MessageCollector
instance_methods.each do |meth|
undef_method meth unless meth =~ /^(__|inspect)/
end
def messages
@messages ||= []
end
def method_missing(sym, *args)
messages << [sym, args]
self
end
end
class Recorder
def play_for(obj)
@message_collector.messages.inject(obj) do |result, message|
result.send message.first, *message.last
end
end
def record
@message_collector ||= MessageCollector.new
end
def to_s
@message_collector.messages.inject([]) do |result, message|
result << "#{message.first}(args: #{message.last.inspect})"
end.join(".")
end
end
class CommandCenter
def start(command_string)
# ...
self
end
def stop(command_string)
# ...
self
end
end
recorder = Recorder.new
recorder.record.start("LRMMMMRL")
recorder.record.stop("LRMMMMRL")
recorder.play_for(CommandCenter.new)