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 sapiensNo 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") #trueO 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!
Nenhum comentário:
Postar um comentário