Un mémo de ce que vous avez appris en lisant la méthode du chapitre 3 de Metaprogramming Ruby.
Le contenu est de fournir une solution au problème du code dupliqué dans la définition de méthode.
――Il existe deux principaux types de solutions.
define_method
)method_missing
)Nous allons procéder en nous basant sur l'exemple concret de "création d'un système qui détecte des pièces informatiques supérieures à 99 $".
Comment améliorer une telle méthode avec beaucoup de duplication de code ...
#Cours d'informatique avec beaucoup de duplication
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
Utilisez ʻObject # send(ʻobj.send (: my_method, arg)
) au lieu de la notation par points habituelle (ʻobj.my_method (arg)) pour appeler la méthode. En utilisant
send`, le nom de la méthode que vous voulez appeler devient un argument, et vous pouvez spécifier dynamiquement le nom de la méthode.
Une telle technique est appelée ** envoi dynamique **.
#Refactoring avec envoi dynamique
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
Vous pouvez utiliser Module # define_method
pour définir dynamiquement une méthode.
Vous devez transmettre le nom de la méthode et le bloc, et le bloc devient le corps de la méthode.
Ici, nous voulons appeler define_method
dans la définition de la classe Computer, nous devons donc en faire une méthode de classe.
# define_Refactoring supplémentaire en utilisant la méthode
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
Ensuite, vous pouvez l'utiliser comme ça
obj = Computer.new(42, data_source)
obj.mouse # => "Wireless Touch"
obj.price # => 60
Pour éliminer davantage la duplication, installez data_source
(*) et développez-le jusqu'au nom du composant.
# data_Instruisez la source!
class Computer
def initialize(computer_id, data_source)
@id = computer_id
@data_source = data_source
# get_xxx_Passez le bloc à une liste de méthodes appelées info
#Une chaîne qui correspond à l'expression régulière(mouse,cpu etc.)Définissez une méthode avec le nom de
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
Maintenant, même si un composant est ajouté du côté data_source
, il peut être pris en charge sans altérer la classe Computer.
(*) Instrumentation ... Demander à un objet un élément de langage (variable, classe, méthode, etc.)
"hoge".class #Demandez à la classe
=> String
"hoge".methods.grep(/to_(.*)/) # "to_"Écoutez la méthode en commençant par
=> [: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
Lorsqu'une méthode qui n'existe pas est appelée, BasicObject # method_missing
est appelé.
C'est un sentiment commun.
class Lawyer; end
nick = Lawyer.new
nick.talk
=> NoMethodError: undefined method `talk' for #<Lawyer:0x00007f921c0f2958>
En surchargeant cette method_missing
, vous pouvez appeler une méthode qui n'existe pas réellement.
Une telle méthode qui est traitée par method_missing
mais n'a pas de méthode correspondante du côté récepteur est appelée ** méthode fantôme **.
class Computer
def initialize(computer_id, data_source)
@id = computer_id
@data_source = data_source
end
def method_missing(name)
# @data_S'il n'y a pas de méthode correspondante dans la source, méthode de la classe parent_Appel manquant
super if !@data_source.respond_to?("get_#{name}_info")
#S'il existe une méthode, procédez comme suit
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
method_missing
est difficile à écraserExemple)
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}" #Une boucle infinie se produit ici. Je me demande pourquoi?
end
end
Un autre piège pour method_missing
Dans la classe précédente Computer
, seule la méthode display
ne fonctionne pas correctement.
my_computer = Computer.new(42, DS.new)
my_computer.display # => nil
Pourquoi.
=> Parce que la méthode display
est déjà définie dans la classe ʻObject` héritée.
Object.instance_methods.grep /^d/
=> [:define_singleton_method, :display, :dup]
Je pense que j'appelle Computer # display
, mais je ne parviens pas à method_missing
car ʻObject # display` est trouvé.
Pour résoudre ce problème, les méthodes inutiles doivent être supprimées. Une classe avec des méthodes minimales est appelée une ** ardoise vierge **.
En tant que méthode de réalisation d'une ardoise vierge, une méthode d'héritage de la classe BasicObject
et une méthode de suppression des méthodes inutiles sont introduites.
Comme vous pouvez le voir, la méthode fantôme comporte le risque d'inclure des bogues utiles mais difficiles à trouver. Donc, la conclusion suivante.
** "Utilisez des méthodes dynamiques dans la mesure du possible et des méthodes fantômes lorsque cela ne peut pas être aidé." **
Je rentre chez moi et je me repose aujourd'hui.
Recommended Posts