quarta-feira, 15 de setembro de 2010

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

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!

Nenhum comentário:

Postar um comentário