bem-vindos a um novo episódio de Ruby Magic! A edição deste mês é sobre metaclasses, um assunto suscitado por uma discussão entre dois desenvolvedores (Hi Maud!).Através da análise de metaclasses, aprenderemos como os métodos de classe e instância funcionam em Ruby. Ao longo do caminho, descubra a diferença entre definir um método passando um “definee” explícito e usando class << self
ou instance_eval
. Vamos!
Classe instâncias e métodos de instância
para entender por que metaclasses são usados em Ruby, vamos começar por examinar quais são as diferenças entre instância – e métodos de classe.
em Ruby, uma classe é um objeto que define um projeto para criar outros objetos. Classes definem quais métodos estão disponíveis em qualquer instância dessa classe.
definir um método dentro de uma classe cria um método de instância nessa classe. Qualquer instância futura dessa classe terá esse método disponível.
1 2 3 4 5 6 7 8 9 10 11 12 |
class User def initialize(name) @name = name end def name @name end end user = User.new('Thijs') user.name # => "Thijs" |
neste exemplo, vamos criar uma classe chamada User
, com um método de instância chamado #name
que retorna o nome do usuário. Usando a classe, então criamos uma instância de classe e a armazenamos em uma variável chamada user
. Uma vez que user
é uma instância da classe User
, tem o método #name
disponível.
uma classe armazena os seus métodos de instância na sua tabela de métodos. Qualquer instância dessa classe refere-se à sua tabela de método de classe para ter acesso aos seus métodos de instância.
Classe de Objetos
Um método de classe é um método que pode ser chamado diretamente na classe sem ter que criar uma instância primeira. Um método de classe é criado por prefixação de seu nome com self.
ao defini-lo.
uma classe é ela mesma um objeto. Uma constante se refere ao objeto de classe, então métodos de classe definidos nele podem ser chamados de qualquer lugar na aplicação.
1 2 3 4 5 6 7 8 9 |
class User # ... def self.all end end User.all # => |
Métodos definidos com um self.
-prefixo será adicionado à classe o método da tabela. Em vez disso, são adicionados ao metaclass da classe.
Metaclasses
aparte de uma classe, cada objeto em Ruby tem um metaclasse escondido. Metaclasses são singletons, o que significa que pertencem a um único objeto. Se você criar várias instâncias de uma classe, eles vão compartilhar a mesma classe, mas todos eles terão metaclasses separados.
1 2 3 4 5 6 7 8 9 |
thijs, robert, tom = User.all thijs.class # => User robert.class # => User tom.class # => User thijs.singleton_class # => #<Class:#<User:0x00007fb71a9a2cb0>> robert.singleton_class # => #<Class:#<User:0x00007fb71a9a2c60>> tom.singleton_class # => #<Class:#<User:0x00007fb71a9a2c10>> |
neste exemplo, podemos ver que, embora cada um dos objetos tem a classe User
, suas singleton classes têm diferentes Identificações de objeto, o que significa que eles são objetos separados.Ao ter acesso a um metaclass, Ruby permite adicionar métodos diretamente aos objetos existentes. Fazê-lo não irá adicionar um novo método à classe do objecto.
1 2 3 4 5 6 7 8 |
robert = User.new("Robert") def robert.last_name "Beekman" end robert.last_name # => "Beekman" User.new("Tom").last_name # => NoMethodError (undefined method `last_name' for #<User:0x00007fe1cb116408>) |
neste exemplo, vamos adicionar um #last_name
para o usuário armazenado na robert
variável. Embora robert
seja uma instância de User
, qualquer instância recém-criada de User
não terá acesso ao método #last_name
, uma vez que só existe no metaclass de robert
.
o que é o self?
ao definir um método e passar por um receptor, o novo método é adicionado ao metaclass do receptor, em vez de adicioná-lo à tabela de método da classe.
1 2 3 4 5 |
tom = User.new("Tom") def tom.last_name "de Bruijn" end |
No exemplo acima, adicionamos #last_name
diretamente na tom
objeto, passando tom
como o receptor, ao definir o método.
é também assim que funciona para métodos de classe.
1 2 3 4 5 6 7 |
class User # ... def self.all end end |
Aqui, vamos passar explicitamente self
como um receptor ao criar o .all
método. Numa definição de classe, self
refere-se à classe (User
neste caso), de modo que o método .all
é adicionado ao metaclasse de User
.
Because User
is an object stored in a constant, we’ll access the same object-and the same metaclass-whenever we reference it.
abrindo o Metaclass
aprendemos que métodos de classe são métodos no metaclass do objeto de classe. Sabendo isso, vamos olhar para algumas outras técnicas de criação de métodos de classe que você pode ter visto antes.
classe < < self
embora tenha saído um pouco do estilo, algumas bibliotecas usam class << self
para definir métodos de classe. Este truque de sintaxe Abre o metaclass da classe atual e interage diretamente com ele.
1 2 3 4 5 6 7 8 9 10 11 |
class User class << self self # => #<Class:User> def all end end end User.all # => |
Este exemplo cria um método de classe chamado User.all
adicionando um método para User
‘s metaclasse. Em vez de passar explicitamente um receptor para o método como vimos anteriormente, ajustamos o metaclass self
para User
em vez do próprio User
.
como aprendemos Antes, qualquer definição de método sem um receptor explícito é adicionado como um método de instância da classe atual. No interior do bloco, A classe actual é o metaclass User
(#<Class:User>
).
instance_eval
outra opção é usar instance_eval
, que faz a mesma coisa com uma diferença maior. Embora o metaclass da classe receba os métodos definidos no bloco, self
permanece uma referência à classe principal.
1 2 3 4 5 6 7 8 9 10 11 |
class User instance_eval do self # => User def all end end end User.all # => |
neste exemplo, vamos definir um método de instância em User
‘s metaclasse como antes, mas self
ainda aponta para User
. Embora normalmente aponte para o mesmo objeto, o” default definee ” e self
pode apontar para objetos diferentes.
o Que Aprendemos
aprendemos que as classes são os únicos objetos que podem ter métodos, e que métodos de instância são, na verdade, métodos de um objeto metaclasse. Sabemos que class << self
simplesmente troca self
ao redor para permitir que você defina métodos no metaclass, e sabemos que instance_eval
faz principalmente a mesma coisa (mas sem tocar self
).Apesar de não trabalhar explicitamente com metaclasses, Ruby usa-os extensivamente sob o capô. Saber o que acontece quando você define um método pode ajudá-lo a entender por que Ruby se comporta como ele faz (e por que você tem que prefixar métodos de classe com self.
).Obrigado pela leitura. Se você gostou do que você leu, Você pode gostar de subscrever a Ruby Magic para receber um e-mail quando publicamos um novo artigo sobre uma vez por mês.