Visibilité, introspection, méta-programmation et réflexion avec ruby

Publié le 31 mai 2011 par Martin Catty | back

Cet article est publié sous licence CC BY-NC-SA

Une visibilité différente

Les méthodes publiques

C’est la visibilité par défaut d’une méthode, elle n’a pas de particularité.

Les méthodes privées

Contrairement à d’autres langages, les méthodes privées sont aussi héritées dans les classes descendantes.

class Animal
  attr_accessor :name, :age, :weight

  def initialize(name, weight, age)
    @name = name
    @weight = weight
    @age = age
  end

  def to_s
    "I'm #{name}, I have #{age} years and I weigh #{weight} KG."
  end

  def older?(animal)
    age > animal.age
  end

  def bigger?(animal)
    weight > animal.weight
  end

  protected :age
  private :weight
end
class Dog < Animal
end
class Cat < Animal
  def to_s
    "I'm #{name}, I have #{age} years and I weigh #{weight} (but I have lost some kg)."
  end
end

Animal#weight est privée, pourtant si j’appelle le Cat#to_s, j’aurai bien “I’m garfield, I have 3 years and I weigh 10 (but I have lost some kg).” ce qui signifie que l’on a bien accès à la méthode.

Les autres méthodes de la classe peuvent accéder à la méthode privée, mais uniquement pour l’objet courant. Dans l’exemple garfield.bigger?(snoopy) renverra une erreur car je ne peux pas invoquer weight sur snoopy depuis garfield.

Cette notion de contexte est très importante car même l’invocation sur self ne fonctionnera pas. Si dans mon to_s j’utilisais self.weight j’aurai droit à une erreur d’invocation de méthode privée.

Les méthodes protected

Contrairement à d’autres langage comme java, le fait de définir une méthode protected, ne signifie pas qu’elle pourra uniquement être appelée dans une classe descendante. On a vu que même les méthodes privées étaient héritées.

En effet en ruby une méthode protected est très proche d’une méthode privée. La nuance se trouve au niveau du contexte. Si on a vu que l’invocation de bigger? renvoyait une erreur, dans le cadre d’un attribut protected comme age cela fonctionne, je peux donc invoquer Animal#older?

Les attributs protégés sont donc accessibles aux autres objets de la classe mais aussi aux objets de classe héritées. Par exemple ici je peux accéder à Dog#weight depuis Cat#weight car ils descendent tout deux de Animal.

Introspection

L’introspection est la capacité pour un objet de s’auto-examiner:

p garfield.class
# Cat
p garfield.class.ancestors
# [Cat, Animal, Object, Kernel]
p garfield.methods.sort
# ["==", "===", "=~", "__id__", "__send__", "age", "age=", "bigger?", "class", "clone", "display", "dup", "enum_for", "eql?", "equal?", "extend", "freeze", "frozen?", "hash", "id", "inspect", "instance_eval", "instance_exec", "instance_of?", "instance_variable_defined?", "instance_variable_get", "instance_variable_set", "instance_variables", "is_a?", "kind_of?", "method", "methods", "name", "name=", "nil?", "object_id", "older?", "private_methods", "protected_methods", "public_methods", "respond_to?", "send", "singleton_methods", "taint", "tainted?", "tap", "to_a", "to_enum", "to_s", "type", "untaint", "weight="]
p garfield.methods(false)
# []

# Object.const_get("RUBY_VERSION")
p garfield.class.ancestors.last.const_get("RUBY_VERSION")
# 1.8.7

On peut par exemple lister toute la hiérarchie de classes parentes, tout comme les constantes et les méthodes définies. Le paramètre false sur methods permet de ne lister que nos propres méthodes et pas celles héritées.

Ici Cat ne définit aucune méthode qui lui est propre, d’où le tableau vide.

Méta-programmation

Il faut visualiser la méta-programmation comme du code qui crée du code.

class Cat
  STATES = ["hungry", "sated"].freeze

  attr_accessor :state

  STATES.each do |state|
    define_method "#{state}?" do
      state == self.state
    end
  end
end

garfield.state = "hungry"

p garfield.hungry?
# true
p garfield.sated?
# false

Ici je peux ré-ouvrir ma class Cat à la volée et y définir des méthodes en fonction d’attributs existants. La clé se trouve dans le define_method qui permet de créer des méthodes dynamiquement.

Réflexion

On a vu dans la première partie des méthodes à la visibilité privée. Toutefois en ruby il n’est pas possible de complètement cacher une méthode.

p garfield.send :weight
# 10
Cat.send :public, :weight
p garfield.weight
# 10

Celle ci peut toujours être invoquée avec send. De même il est possible de changer la visibilité d’une méthode au runtime ! Vous l’aurez compris, ces concepts très puissants sont à utiliser avec parcimonie.

L’équipe Synbioz.

Libres d’être ensemble.