Ein Memo von dem, was Sie durch Lesen von Kapitel 3 Methode von Metaprogramming Ruby gelernt haben.
Der Inhalt soll eine Lösung für das Problem des doppelten Codes in der Methodendefinition bieten.
――Es gibt zwei Haupttypen von Lösungen.
define_method
)method_missing
)Wir werden anhand des konkreten Beispiels "Erstellen eines Systems, das Computerteile über 99 US-Dollar erkennt" fortfahren.
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
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
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
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]
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
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
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.
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