[Lernnotiz] Metaprogramming Ruby 2. Ausgabe: Kapitel 3: Methode

Überblick

Ein Memo von dem, was Sie durch Lesen von Kapitel 3 Methode von Metaprogramming Ruby gelernt haben.

Erste grobe Zusammenfassung

Der Inhalt soll eine Lösung für das Problem des doppelten Codes in der Methodendefinition bieten.

――Es gibt zwei Haupttypen von Lösungen.

  1. Dynamische Methode (define_method)
  2. Ghost-Methode (method_missing)

Beispiel

Wir werden anhand des konkreten Beispiels "Erstellen eines Systems, das Computerteile über 99 US-Dollar erkennt" fortfahren.

Problem mit doppeltem Code

Wie man eine solche Methode mit viel Codeduplizierung verbessert ...

#Computerklasse mit viel Duplizierung
class Computer
  def initialize(computer_id, data_source)
    @id = computer_id
    @data_source = data_source
  end

  def mouse
    info = @data_source.get_mouse_info(@id)
    price = @data_source.get_mouse_price(@id)
    result = "Mouse: #{info} ($#{price})"
    return "* #{result}" if price >= 100
    result
  end

  def cpu
    info = @data_source.get_cpu_info(@id)
    price = @data_source.get_cpu_price(@id)
    result = "Cpu: #{info} ($#{price})"
    return "* #{result}" if price >= 100
    result
  end

  def keyboard
    info = @data_source.get_keyboard_info(@id)
    price = @data_source.get_keyboard_price(@id)
    result = "Keyboard: #{info} ($#{price})"
    return "* #{result}" if price >= 100
    result
  end

  # ....
end

Lösung mit dynamischen Methoden

Dynamischer Versand

Verwenden Sie "Object # send" ("obj.send (: my_method, arg)") anstelle der üblichen Punktnotation ("obj.my_method (arg)"), um die Methode aufzurufen. Mit "send" wird der Methodenname, den Sie aufrufen möchten, zu einem Argument, und Sie können den Methodennamen dynamisch angeben. Eine solche Technik wird als ** dynamischer Versand ** bezeichnet.

#Refactoring mit dynamischem Versand
class Computer
  def initialize(computer_id, data_source)
    @id = computer_id
    @data_source = data_source
  end

  def mouse
    component :mouse
  end

  def cpu
    component :cpu
  end

  def keyboard
    component :keyboard
  end

  def component(name)
    info = @data_source.send "get_#{name}_info", @id
    price = @data_souce.send "get_#{name}_price", @id
    result = "#{name.capitalize}: #{info} ($#{price})"
    return "* #{result}" if price >= 100
    result
  end
end

Dynamische Methode

Sie können Module # define_method verwenden, um eine Methode dynamisch zu definieren. Sie müssen den Methodennamen und den Block übergeben, und der Block wird zum Hauptteil der Methode. Hier wollen wir define_method in der Computerklassendefinition aufrufen, also müssen wir es zu einer Klassenmethode machen.

# define_Weiteres Refactoring mit Methode
class Computer
  def initialize(computer_id, data_source)
    @id = computer_id
    @data_source = data_source
  end

  def self.define_component(name)
    define_method(name) do
      info = @data_source.send "get_#{name}_info", @id
      price = @data_source.send "get_#{name}_price", @id
      result = "#{name.capitalize}: #{info} ($#{price})"
      retrun "* #{result}" if price >= 100
      result
    end
  end

  define_component :mouse
  define_component :cpu
  define_component :keyboard
end

Dann können Sie es so verwenden

obj = Computer.new(42, data_source)
obj.mouse # => "Wireless Touch"
obj.price # => 60

Weitere Instrumentierung

Um Doppelungen weiter zu vermeiden, installieren Sie data_source (*) und erweitern Sie es auf den Namen der Komponente.

# data_Instrope die Quelle!
class Computer
  def initialize(computer_id, data_source)
    @id = computer_id
    @data_source = data_source
    # get_xxx_Übergeben Sie den Block an eine Liste von Methoden namens info
    #Eine Zeichenfolge, die dem regulären Ausdruck entspricht(mouse,CPU usw.)Definieren Sie eine Methode mit dem Namen
    data_source.methods.grep(/^get_(.*)_info$/) { Computer.define_component $1 }
  end

  def self.define_component(name)
    define_method(name) do
      info = @data_source.send "get_#{name}_info", @id
      price = @data_source.send "get_#{name}_price", @id
      result = "#{name.capitalize}: #{info} ($#{price})"
      retrun "* #{result}" if price >= 100
      result
    end
  end
end

Selbst wenn eine Komponente auf der Seite "data_source" hinzugefügt wird, kann sie jetzt unterstützt werden, ohne die Computerklasse zu manipulieren.

(*) Instrumentierung ... Ein Objekt nach einem Sprachelement fragen (Variable, Klasse, Methode usw.)

"hoge".class #Fragen Sie die Klasse
=> String
"hoge".methods.grep(/to_(.*)/) # "to_"Hören Sie sich die Methode an, die mit beginnt
=> [:to_c, :to_str, :to_sym, :to_s, :to_i, :to_f, :to_r, :to_json_raw, :to_json_raw_object, :to_json, :to_enum]

Lösung mit der Ghost-Methode

method_missing Wenn eine nicht vorhandene Methode aufgerufen wird, wird "BasicObject # method_missing" aufgerufen. Dies ist ein allgemeines Gefühl.

class Lawyer; end
nick = Lawyer.new
nick.talk
=> NoMethodError: undefined method `talk' for #<Lawyer:0x00007f921c0f2958>

Durch Überschreiben dieser method_missing können Sie eine Methode aufrufen, die tatsächlich nicht existiert. Eine solche Methode, die von "method_missing" verarbeitet wird, aber auf der Empfängerseite keine entsprechende Methode hat, wird als "Ghost-Methode" bezeichnet.

class Computer
  def initialize(computer_id, data_source)
    @id = computer_id
    @data_source = data_source
  end

  def method_missing(name)
    # @data_Wenn die Quelle keine entsprechende Methode enthält, Methode der übergeordneten Klasse_Anruf fehlt
    super if !@data_source.respond_to?("get_#{name}_info")

    #Wenn es eine Methode gibt, gehen Sie wie folgt vor
    info   = @data_source.__send__("get_#{name}_info", @id)
    price  = @data_source.__send__("get_#{name}_price", @id)
    result = "#{name.capitalize}: #{info} ($#{price})"
    return "* #{result}" if price >= 100
    result
  end

  def respond_to_missing?(method, include_private = false)
    @data_source.respond_to?("get_#{method}_info") || super
  end
end

Der Fehler "method_missing" ist schwer zu beseitigen

Beispiel)

class Roulette
  def method_missing(name, *args)
    person = name.to_s.capitalize
    3.times do
      number = rand(10) + 1
      puts "#{number}..."
    end
    "#{person} got a #{number}"  #Hier tritt eine Endlosschleife auf. Ich wundere mich warum?
  end
end

Unbeschriebenes Blatt

Eine weitere Falle für method_missing

In der vorherigen Klasse "Computer" funktioniert nur die Methode "Anzeige" nicht ordnungsgemäß.

my_computer = Computer.new(42, DS.new)
my_computer.display # => nil

Warum. => Da die Methode display bereits in der geerbten KlasseObject definiert ist.

Object.instance_methods.grep /^d/
=> [:define_singleton_method, :display, :dup]

Ich glaube, ich rufe "Computer # display" auf, aber ich komme nicht zu "method_missing", weil "Object # display" gefunden wird.

Um dies zu lösen, müssen unnötige Methoden gelöscht werden. Eine Klasse mit minimalen Methoden wird als ** leere Tafel ** bezeichnet.

Als Methode zum Realisieren einer leeren Tafel werden eine Methode zum Erben der Klasse "BasicObject" und eine Methode zum Löschen unnötiger Methoden eingeführt.

Fazit

Wie Sie sehen können, birgt die Ghost-Methode das Risiko, Fehler einzuschließen, die nützlich, aber schwer zu finden sind. Also die folgende Schlussfolgerung.

** "Verwenden Sie nach Möglichkeit dynamische Methoden und Ghost-Methoden, wenn Ihnen nicht geholfen werden kann." **

Ich gehe heute nach Hause und ruhe mich aus.

Recommended Posts

[Lernnotiz] Metaprogramming Ruby 2. Ausgabe: Kapitel 3: Methode
Effektive Java 3rd Edition Kapitel 8 Methoden
Rails Tutorial (4. Ausgabe) Memo Kapitel 6
Rails Tutorial 6. Ausgabe Lernzusammenfassung Kapitel 10
Rubin lernen 4
Rails Tutorial 6. Ausgabe Lernzusammenfassung Kapitel 7
Rubin lernen 5
Rails Tutorial 6. Ausgabe Lernzusammenfassung Kapitel 4
Rails Tutorial 6. Ausgabe Lernzusammenfassung Kapitel 9
Rails Tutorial 6. Ausgabe Lernzusammenfassung Kapitel 6
Ruby lernen 3
Rails Tutorial 6. Ausgabe Lernzusammenfassung Kapitel 5
Rails Tutorial 6. Ausgabe Lernzusammenfassung Kapitel 2
Ruby lernen 2
Ausgabe mit Methoden und Konstanten Lernnotiz
Rubin lernen 6
Rails Tutorial 6. Ausgabe Lernzusammenfassung Kapitel 3
Rubin lernen 1
Rails Tutorial 6. Ausgabe Lernzusammenfassung Kapitel 8
Manstep Meter (Ruby Edition)
Über Ruby-Methoden
Ruby on Rails5 - Schnellübungshandbuch 5.2 Kompatibel Kapitel2
Ruby on Rails5 - Schnellübungshandbuch 5.2 Kompatibel Kapitel3