No
artigo anterior, escrevi sobre algumas peculiaridades da orientação
a objetos no Ruby. Agora será importante compreendermos como as
classes e os objetos se interagem e relacionam entre si.
Inicialmente
podemos dizer que um objeto Ruby possui:
- Conjunto
de flags: Cada objeto possui um conjunto de flags compondo
algumas informações (metadados). Essas informações podem “dizer
algo” sobre o objeto. Por exemplo, existe um flag chamado Freeze
que indica se o respectivo objeto está “congelado” ou não.
Estar congelado significa que o objeto não pode ser alterado. - Variáveis de instância: Um objeto é capaz de armazenar
variáveis de instâncias. Internamente, o objeto possui uma tabela de
hash contendo essas variáveis a fim de representar o atual estado do
objeto. - Referência para sua classe: Um objeto Ruby
possui uma referência interna chamada “klass” (com
k). Essa é uma referência que podemos considerar como um ponteiro.
É importante destacar que essa referência não é para a
superclasse do objeto.
Os objetos precisam de ter essa referência
klass, pois eles necessitam de um local para armazenar seus
métodos de instância, tendo em vista que o objeto não os armazena
em si.
Logo, klass é uma referência para a classe associada ao
objeto.
Veja abaixo as estruturas em C de um objeto Ruby e
observe iv_tbl (ponteiro para as variáveis de instância),
klass, flags e basic:
struct RBasic {
unsigned long flags;
VALUE klass;
};
struct RObject {
struct RBasic basic;
struct st_table *iv_tbl;
};
Vamos
analisar agora como são as classes do Ruby. Se você não lembra que uma classe em Ruby é um objeto da classe Class, leia o artigo
anterior, chamado “Ruby Orientação a Objetos em
Detalhes”.
Eventualmente, klass pode referenciar a
dois tipos de classes. Uma pode ser a própria classe na qual ele foi
instanciado. A outra pode ser sua singleton class (também
conhecida como eigenclasses ou virtual class). Porém, a singleton
class irá existir somente se necessitarmos adicionar um
comportamento particular a um objeto.
Vamos realizar um
comparativo com o mundo real a fim de compreendermos de forma mais
clara.
Imaginemos que todos nós somos instâncias da classe
pessoa. A classe pessoa define nosso comportamento. Com isso temos
comportamentos comuns a todas as pessoas.
Em contrapartida, também
somos indivíduos com personalidades próprias, e temos nossos
próprios comportamentos.
Logo, podemos imaginar que os
comportamentos comuns a todas as pessoas estão armazenados na classe
pessoa (classe de onde o objeto foi instanciado). E os nossos
comportamentos individuais são armazenados em “nossa singleton
class”, ou seja, em uma classe que somente nós temos acesso e
não compartilhamos com as demais pessoas.
Com esses conceitos
definidos, podemos destacar que uma classe Ruby possui os seguintes
itens/referências:
- Conjunto de flags: Como a classe
também é um objeto, ela também tem alguns flags como explicado
acima. - Variáveis de instância: Também como
explicado acima, um objeto é capaz de armazenar variáveis de
instância. - Métodos: As classes armazenam os
métodos (para os objetos, estes são os métodos de instância). - Referência para superclass: A referência para superclass
“aponta” para a classe “pai” do objeto. Ou seja,
é uma relação de herança. - Referência para sua
classe: Novamente, como a própria classe é um objeto, ela
também tem uma referência klass.
Veja abaixo a
estrutura em C de uma classe Ruby:
struct RClass {
struct RBasic basic;
struct st_table *iv_tbl;
struct st_table *m_tbl;
VALUE super;
};
Vamos
agora verificar um trecho de código para identificarmos todas essas
relações. O código abaixo ilustra a singleton class em uma
classe:
class Humano
def falar
puts "falando..."
end
end
class Humano
def Humano.especie
puts "Homo sapiens"
end
end
objeto_humano = Humano.new
h.falar #falando...
puts Humano.especie #Homo sapiens
No
exemplo acima, “falar” é um método de instância. Isso
significa que instâncias da classe Humano responderão ao método
“falar”.
Logo abaixo, definimos o método
“especies” na classe Humano. Nesse caso, o método foi
definido na singleton class do objeto, já que a classe Humano é um
objeto da classe Class.
Como em Ruby as classes são
objetos, quando definimos um método em uma classe (como no exemplo
acima), devemos acessá-lo explicitamente referenciando o nome da
classe, pois o método somente existe no objeto em questão.
Com
isso concluímos que em Ruby não existem métodos de
classe.
Vejamos abaixo um diagrama que ilustra essas
relações:
- No
diagrama acima, object_humano possui uma referência(klass)
para a classe que o define. - Como definimos o método especie
na classe Humano, precisamos de uma singleton class para
armazená-lo. - A singleton class que foi criada também precisa
de uma superclass, que no caso também é “virtual”.
Vejamos
agora um código que ilustra os singleton methods:
class Humano
def falar
puts "falando..."
end
end
humano_normal = Humano.new
humano_normal.falar #falando...
humano_bravo = Humano.new
def humano_bravo.gritar
puts "gritando..."
end
humano_bravo.gritar #gritando...
puts humano_normal.respond_to?("gritar") #false
puts humano_bravo.respond_to?("gritar") #true
O
código é bem simples. Inicialmente definimos uma classe chamada
Humano que possui um método(falar). Esse será o método
de instância dos objetos da classe Humano.
Em seguida criamos um
objeto da classe Humano. Para recebermos o retorno “falando…”
enviamos a mensagem “falar” ao objeto humano_normal.
Na
sequência criamos um outro objeto chamado humano_bravo para em
seguida definirmos o método gritar.
Para finalizar,
enviamos a mensagem “gritar” ao objeto humano_bravo, e nas
duas últimas linhas observamos que embora os objetos (humano_normal
e humano_bravo) sejam ambos da classe Humano, apenas o objeto
humano_bravo responde à mensagem “gritar”.
Você
pode estar se perguntando: como é possível dois objetos da mesma
classe responderem a métodos diferentes?
Isso acontece pois ao
definirmos o método “gritar” no objeto “humano_bravo”,
esse método é definido na singleton classe do objeto.
O método
gritar também pode ser chamado de singleton method.
Como
foi citado acima, todo objeto tem uma referência “para sua
classe”, e esta classe é o que chamamos de singleton class.
Por
esse motivo é que somente o objeto humano_bravo responde ao método
“gritar.”
Para finalizar, vejamos este diagrama:
Na primeira
situação do humano_normal:
- O objeto humano_normal
possui a classe Humano relacionada a ele. Isso fica claro na
referência klass. - Como a classe Humano herda da classe
Object, a referência super aponta para Object.
Na segunda
situação do humano_bravo:
- Como o objeto
humano_bravo possui um comportamento particular (um novo método), a
singleton class aparece como uma classe intermediária na sequência
dos relacionamentos. - A referência klass de humano_bravo aponta
diretamente para a singleton class. - A singleton class herda da
classe Humano.
Abraços a todos!