Ce billet en prépare un autre sur les metaclass de ruby. Mais pour bien pouvoir le comprendre, il faut bien avoir conscience de certaines notions fondamentales sur le langage.
1. Visibilité des méthodes
public : toujours accessible.
private : accessible seulement par cet instance. On dit souvent que les méthodes privée ne peuvent avoir de receveur explicite, même self. Ainsi, les méthodes privées ne sont accessibles qu’au sein d’un contexte où self été fixé à l’instance voulue. Ceci inclue bien sûr dans le corps des méthodes d’instance, mais aussi dans d’autres cas ( voir l’article sur les metaclass ).
Une astuce consiste à utiliser send pour bypasser cette restriction :
class Foo
private
def hello
puts "Hello !"
end
end
Foo.new.send(:hello) #=> Hello !
Foo.new.hello #=> private.rb:8: private method `hello' called
# for #<Foo:0xb7caaba4> (NoMethodError)
protected : accessible par tout objet de la même classe ou d’une classe enfant. Les appels à ces méthodes protected ne peuvent se faire qu’au sein des classes autorisées.
2. Tout est objet
class Foo
end
puts Foo.new.object_id #=> -605813328
puts Foo.object_id #=> -605813298
puts Class.object_id #=> -605776258
puts Object.object_id #=> -605776238
puts self.object_id #=> -605772628
puts object_id #=> -605772628
Les classes sont elles même des instances. Ces instances sont uniques car assignées à des constantes ( le nom d’une classe commence par une majuscule ). Il est néanmoins possible modifier cette constante ( on aura alors un warning, c’est déconseillé ).
Quand il n’y a pas d’objet spécifié, une méthode s’applique sur self. Par exemple, lorsque l’on fait des puts dans irb :
irb(main):001:0> puts "Hello!"
Hello!
=> nil
irb(main):002:0> puts self
main
=> nil
irb(main):003:0> puts self.class
Object
=> nil
irb(main):004:0> puts self.private_methods.include?("puts")
true
=> nil
La méthode puts vient de la classe Object qui mixin le module Kernel. La méthode puts étant privée, il n’est pas possible d’appeler directement self.puts. Par contre, comme on a vu auparavant, il est possible de faire :
self.send(:puts, "Hello !") #=> Hello !
Les classes, étant elles même des instances, peuvent elles aussi avoir leurs variables d’instance :
class Foo
@price = 99.9
def self.price;
puts "Class price is #{@price}"
end
def price;
puts "Instance price is #{@price}"
end
end
class Bar < Foo
@price = 19.45
end
Bar.price #=> Class price is 19.45
Foo.price #=> Class price is 99.9
Foo.new.price #=> Instance price is <= nil price here
Évidement on ne peut pas utiliser des variables dans des méthodes d’instance de la classe car elles auraient le sens des variables d’instance des instances de cette classe. Vous me suivez toujours ? :-)
Tout ceci peut être confu, c’est normal. La confusion vient souvent du fait que les classes sont des instances (de l’objet Class). Du coup, quand on dit "méthode de classe", cela désigne simplement des méthodes d’instance appliquée à un objet de type Class.
Notez enfin que les classes sont des objets comme les autres à la différence qu’elles seules peuvent contenir des méthodes.
3. include
La méthode include qui est définie sur la classe Module rajoute sur le module receveur les méthodes d’instance, les constantes et les variables de classe du module.
module Mod
Tic = "I'm Tic"
@@tac = "I'm @@tac"
def hello; "Hello!"; end
end
class Foo
include Mod
def tic; Tic; end
def tac; @@tac; end
end
puts Foo.new.tic #=> "I'm Tic"
puts Foo.new.tac #=> "I'm @@tac"
puts Foo.new.hello #=> "Hello!"
Ca s’appelle mixin, et c’est bien pratique.
4. extend
La méthode extend qui est définie sur Object rajoute sur le receveur les méthodes du module.
module Mod
def hello; "Hello!"; end
end
o = Object.new
o.extend( Mod )
puts o.hello #=> "Hello!"
class Foo
extend Mod # s'applique à self = Foo
end
puts Foo.hello #=> "Hello!"
Comme on le voit, le comportement n’est pas le même que pour include. En effet, extend va définir des méthode directement sur l’Object receveur, alors que include va définir des méthodes sur les instances de la classe sur laquelle il s’applique.
5. Héritage
class Foo
def hello; "Hello!"; end
def self.goodbye; "Goodbye!"; end
end
class Bar < Foo
end
puts Bar.new.hello #=> "Hello!"
puts Bar.goodbye #=> "Goodbye!"
L’héritage se fait sur les méthodes d’instance ET sur les méthodes de classe.
Voila, c’est tout pour cet article. Toutes ces notions sont fondamentales et vous devrez les comprendre parfaitement pour être à l’aise dans le langage.