Back-End

15 set, 2010

Ruby: relações e interações entre classes e objetos

Publicidade

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:

  1. 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.
  2. 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.
  3. 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:

  1. Conjunto de flags: Como a classe
    também é um objeto, ela também tem alguns flags como explicado
    acima.
  2. Variáveis de instância: Também como
    explicado acima, um objeto é capaz de armazenar variáveis de
    instância.
  3. Métodos: As classes armazenam os
    métodos (para os objetos, estes são os métodos de instância).
  4. Referência para superclass: A referência para superclass
    “aponta” para a classe “pai” do objeto. Ou seja,
    é uma relação de herança.
  5. 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:

  1. No
    diagrama acima, object_humano possui uma referência(klass)
    para a classe que o define.
  2. Como definimos o método especie
    na classe Humano, precisamos de uma singleton class para
    armazená-lo.
  3. 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:

  1. O objeto humano_normal
    possui a classe Humano relacionada a ele. Isso fica claro na
    referência klass.
  2. Como a classe Humano herda da classe
    Object, a referência super aponta para Object.

Na segunda
situação do humano_bravo:

  1. 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.
  2. A referência klass de humano_bravo aponta
    diretamente para a singleton class.
  3. A singleton class herda da
    classe Humano.

Abraços a todos!