Refactoring: Refactorings/Isolate Dynamic Receptor

From Notes
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

  1. Create a new class whose sole responsibility is to handle the dynamic method calls
  2. Copy the logic from method_missing on the original class to the method_missing of the focused class
  3. Create a method on the original class to return an instance of the focused class
  4. Change all client code that previously called the dynamic methods on the original object to call the new method first
  5. Remove the method_missing from the original object
  6. 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)
Refactorings
Composing Methods

Extract Method • Inline Method • Inline Temp • Replace Temp with Query • Replace Temp with Chain • Introduce Explaining Variable • Split Temporary Variable • Remove Assignments to Parameters • Replace Method with Method Object • Substitute Algorithm • Replace Loop with Collection Closure Method • Extract Surrounding Method • Introduce Class Annotation • Introduce Named Parameter • Remove Named Parameter • Remove Unused Default Parameter • Dynamic Method Definition • Replace Dynamic Receptor with Dynamic Method Definition • Isolate Dynamic Receptor • Move Eval from Runtime to Parse Time

Moving Features Between Objects

Move Method • Move Field • Extract Class • Inline Class • Hide Delegate • Remove Middle Man

Organizing Data

Self Encapsulate Field • Replace Data Value with Object • Change Value to Reference • Change Reference to Value • Replace Array with Object • Replace Hash with Object • Change Unidirectional Association to Bidirectional • Change Bidirectional Association to Unidirectional • Replace Magic Number with Symbolic Content • Encapsulate Collection • Replace Record with Data Class • Replace Type Code with Polymorphism • Replace Type Code with Module Extension • Replace Type Code with State-Strategy • Replace Subclass with Fields • Lazily Initialized Atribute • Eagerly Initialized Attribute

Simplifying Conditional Expressions

Decompose Conditional • Recompose Conditional • Consolidate Conditional Expression • Consolidate Duplicate Conditional Fragments • Remove Control Flag • Replace Nested Conditional with Guard Clauses • Replace Conditional with Polymorphism • Introduce Null Object • Introduce Assertion

Making Method Calls Simpler

Rename Method • Add Parameter • Remove Parameter • Separate Query from Modifier • Parameterize Method • Replace Parameter with Explicit Methods • Preserve Whole Object • Replace Parameter with Method • Introduce Parameter Object • Remove Setting Method • Hide Method • Replace Constructor with Factory Method • Replace Error Code with Exception • Replace Exception with Test • Introduce Gateway • Introduce Expression Builder

Dealing with Generalization

Pull Up Method • Push Down Method • Extract Module • Inline Module • Extract Subclass • Introduce Inheritance • Collapse Hierarchy • Form Template Method • Replace Inheritance with Delegation • Replace Delegation with Hierarchy • Replace Abstract Superclass with Module

Big Refactorings

Tease Apart Inheritance • Convert Procedural Design to Objects • Separate Domain from Presentation • Extract Hierarchy