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 MethodInline MethodInline TempReplace Temp with QueryReplace Temp with ChainIntroduce Explaining VariableSplit Temporary VariableRemove Assignments to ParametersReplace Method with Method ObjectSubstitute AlgorithmReplace Loop with Collection Closure MethodExtract Surrounding MethodIntroduce Class AnnotationIntroduce Named ParameterRemove Named ParameterRemove Unused Default ParameterDynamic Method DefinitionReplace Dynamic Receptor with Dynamic Method DefinitionIsolate Dynamic ReceptorMove Eval from Runtime to Parse Time

Moving Features Between Objects

Move MethodMove FieldExtract ClassInline ClassHide DelegateRemove Middle Man

Organizing Data

Self Encapsulate FieldReplace Data Value with ObjectChange Value to ReferenceChange Reference to ValueReplace Array with ObjectReplace Hash with ObjectChange Unidirectional Association to BidirectionalChange Bidirectional Association to UnidirectionalReplace Magic Number with Symbolic ContentEncapsulate CollectionReplace Record with Data ClassReplace Type Code with PolymorphismReplace Type Code with Module ExtensionReplace Type Code with State-StrategyReplace Subclass with FieldsLazily Initialized AtributeEagerly Initialized Attribute

Simplifying Conditional Expressions

Decompose ConditionalRecompose ConditionalConsolidate Conditional ExpressionConsolidate Duplicate Conditional FragmentsRemove Control FlagReplace Nested Conditional with Guard ClausesReplace Conditional with PolymorphismIntroduce Null ObjectIntroduce Assertion

Making Method Calls Simpler

Rename MethodAdd ParameterRemove ParameterSeparate Query from ModifierParameterize MethodReplace Parameter with Explicit MethodsPreserve Whole ObjectReplace Parameter with MethodIntroduce Parameter ObjectRemove Setting MethodHide MethodReplace Constructor with Factory MethodReplace Error Code with ExceptionReplace Exception with TestIntroduce GatewayIntroduce Expression Builder

Dealing with Generalization

Pull Up MethodPush Down MethodExtract ModuleInline ModuleExtract SubclassIntroduce InheritanceCollapse HierarchyForm Template MethodReplace Inheritance with DelegationReplace Delegation with HierarchyReplace Abstract Superclass with Module

Big Refactorings

Tease Apart InheritanceConvert Procedural Design to ObjectsSeparate Domain from PresentationExtract Hierarchy