[Mémo d'apprentissage] Métaprogrammation Ruby 2e édition: Chapitre 3: Méthode

Aperçu

Un mémo de ce que vous avez appris en lisant la méthode du chapitre 3 de Metaprogramming Ruby.

Premier résumé approximatif

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.

  1. Méthode dynamique (define_method)
  2. Méthode Ghost (method_missing)

exemple

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 $".

Problème de code en double

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

Solution utilisant des méthodes dynamiques

Envoi dynamique

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

Méthode dynamique

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

Instrumentation supplémentaire

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]

Solution utilisant la méthode fantôme

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

Le bogue method_missing est difficile à écraser

Exemple)

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

Ardoise vierge

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.

Conclusion

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

[Mémo d'apprentissage] Métaprogrammation Ruby 2e édition: Chapitre 3: Méthode
Méthodes efficaces du chapitre 8 de Java 3rd Edition
Tutoriel Rails (4e édition) Mémo Chapitre 6
Tutoriel Rails 6e édition Résumé d'apprentissage Chapitre 10
Ruby apprentissage 4
Rails Tutorial 6e édition Résumé de l'apprentissage Chapitre 7
Ruby apprentissage 5
Tutoriel Rails 6e édition Résumé de l'apprentissage Chapitre 4
Tutoriel Rails 6e édition Résumé de l'apprentissage Chapitre 9
Tutoriel Rails 6e édition Résumé de l'apprentissage Chapitre 6
Ruby apprentissage 3
Tutoriel Rails 6e édition Résumé de l'apprentissage Chapitre 5
Rails Tutorial 6e édition Résumé de l'apprentissage Chapitre 2
Ruby apprentissage 2
Sortie à l'aide de méthodes et de constantes Mémo d'apprentissage
Ruby apprentissage 6
Tutoriel Rails 6e édition Résumé de l'apprentissage Chapitre 3
Ruby apprentissage 1
Rails Tutorial 6e édition Résumé d'apprentissage Chapitre 8
Compteur Manstep (édition Ruby)
À propos des méthodes Ruby
Ruby on Rails5 Guide pratique d'apprentissage rapide 5.2 Chapitre 2 compatible
Ruby on Rails5 Guide pratique d'apprentissage rapide 5.2 Chapitre compatible3