Escrevendo sua própria classe

O paradigma procedural foi utilizado até agora para escrevermos nossos programas (note que este paradigma continua sendo utilizado em linguagens como C). Programar proceduralmente quer dizer que focamos nos passos requeridos para completar uma tarefa (de programação) sem dar muita atenção em como os dados são manipulados.

No paradigma de Orientação a Objetos, objetos são nossos agentes, “procuradores”, no escopo do nosso programa. Pedimos informações para os objetos. Atribuímos para eles tarefas. Dizemos para eles executarem cálculos e, em seguida, retornar o valor calculado. Juntamos uns aos outros para que trabalhem conjuntamente.

Quando projetamos uma classe, pensamos sobre os objetos que serão criados com base naquele tipo de classe. Pensamos sobre as coisas que o objeto sabe e faz.

Coisas que o objeto sabe sobre ele mesmo são chamadas variáveis de instância. Elas representam o estado do objeto (os dados: por exemplo, a quantidade e o código do produto), e podem ter valores únicos para cada objeto daquele tipo.

Coisas que um objeto pode fazer são chamados métodos.

Um objeto é uma entidade que serve como um contêiner de dados e que controla o acesso a eles. Associando a um objeto um conjunto de atributos, que, essencialmente, não são mais do que variáveis que pertencem ao objeto. Também podem ser associados a um objeto um conjunto de funções que disponibilizam um meio (interface) de operarmos sobre os dados do objeto, chamados métodos. – Hal Fulton (N.T.: Tradução Livre do original)

Um objeto é uma combinação de estados (dados) e métodos que modificam estes estados (funções).

Dessa forma uma classe é usada para construir um objeto. Uma classe é um molde para um objeto. Por exemplo, podemos usar uma classe Botão para fazer dezenas de botões diferentes, cada botão tendo sua própria cor, tamanho, forma, entre outras. Um objeto é uma instância de uma classe.

Leia cuidadosamente

Classes em Ruby são objetos de primeira classe – todas são instâncias da classe Class. Quando uma nova classe é definida (normalmente usando class Nome … end), um objeto do tipo Class é criado e é associado a uma constante (Nome, neste caso). Quando Nome.new é chamado para criar um novo objeto, o método de classe new da classe Class é executado primeiro para alocar memória para o objeto (através do método allocate) e, em seguida, o método initialize do novo objeto é executado. As fases de construção e inicialização de um objeto são separadas e ambas podem ser sobrescritas. A inicialização é feita pelo método de instância initialize, enquanto a construção é feita pelo método de classe new. O método initialize não é um construtor.

Vamos escrever nossa primeira classe – p029dog.rb

 1 # p029dog.rb
 2 # defina a classe Cachorro
 3 class Cachorro
 4   def initialize(raca, nome)
 5     # Variáveis de instância
 6     @raca = raca
 7     @nome = nome
 8   end
 9 
10   def late
11     puts 'Au! Au!'
12   end
13 
14   def mostra
15     puts "Sou da raca #{@raca} e meu nome eh #{@nome}"
16   end
17 end
18 
19 # cria um objeto
20 # Objetos são criados na pilha
21 d = Cachorro.new('Labrador', 'Benzy')
22 
23 =begin
24   Cada objeto "nasce" com certas características inatas.
25   Para ver uma lista dos métodos do objeto, você pode chamar
26   o método methods (e usar uma operação de sort para facilitar
27   a visualização). Remova o comentário e execute
28 =end
29 
30 # puts d.methods.sort
31 
32 # Entre todos esses métodos, os métodos object_id
33 # e respond_to são importantes.
34 # Cada objeto no Ruby tem um id único associado ao mesmo.
35 
36 puts "O id do obj é #{d.object_id}."
37 
38 
39 # Para saber se o objeto sabe como lidar com uma mensagem que
40 # você queira enviar a ele você pode usar o método respond_to?.
41 if d.respond_to?("fala")
42   d.fala
43 else
44   puts "Desculpe, o objeto nao entende a mensagem 'fala'."
45 end
46 
47 d.late
48 d.mostra
49 
50 # fazendo d e d1 apontar para o mesmo objeto
51 d1 = d
52 d1.display
53 
54 # tornando d uma referência nula, o que significa
55 # que ele não se referencia a nada
56 d = nil
57 d.display
58 
59 # Se eu faço
60 d1 = nil
61 # Então o objeto Cachorro é abandonado e elegível para ser
62 # coletado para o lixo (GC)

A saída é esta:

1 >ruby p029dog.rb
2 O id do ocj é 22982920.
3 Desculpe, o objeto nao entende a mensagem 'fala'.
4 Au! Au!
5 Sou da raca Labrador e meu nome eh Benzy.
6 Sou da raca Labrador e meu nome eh Benzy.
7 >Exit code: 0

O método new é usado para criar um objeto da classe Cachorro. Objetos são criados na pilha (heap). A variável d é conhecida como variável de referência. Isso quer dizer que ela não contém o objeto. Contém somente alguma coisa como um ponteiro ou um endereço (de memória) para o objeto. Utilizamos o operador ponto (.) em uma variável de referência para “dizer” ao objeto: “use a coisa antes do ponto (objeto) para fazer a coisa depois do ponto (executar o método).” Por exemplo: d.late

No framework Rails:
Se estivermos escrevendo uma aplicação em Rails em que uma das entidades do modelo seja Cliente, então quando escrevermos o código que fazem as coisas acontecerem – um cliente acessa a área restrita de um site, atualiza seu número de telefone, adiciona um item em seu carrinho de compras – em todas essas situações estamos enviando mensagens para os objetos cliente.

Mesmo um objeto criado recentemente não é algo vazio. Tão breve quanto um objeto passa a existir, ele já responde a certo número de mensagens. Todo objeto “nasce” com certas habilidades inatas. Para ver uma lista desses métodos inatos, podemos chamar o método methods (e ainda solicitar que o resultado seja ordenado – chamando o método sort, – para facilitar a visualização):

1 puts d.methods.sort

O resultado é uma lista com todas as mensagens (métodos) que este novo e recém-criado objeto possui. Entre todos estes muitos métodos, os métodos object_id e respond_to? são importantes.

Todo objeto em Ruby possui um identificador numérico único associado a ele. Podemos ver este identificado pedindo para o objeto mostrar seu identificador (via método object_id):

1 puts "O id do objeto é #{d.object_id}."

Podemos determinar antecipadamente (antes de perguntarmos alguma coisa para o objeto) se o objeto sabe como responder a mensagem que pretendemos enviar. Fazemos isso utilizando o método respond_to?. Esse método existe em todos os objetos; podemos questionar qualquer objeto para verificar se ele responde alguma mensagem. O método respond_to? normalmente aparece em conjunto com um teste lógico condicional (if).

1 if d.respond_to?("fala")
2   d.fala
3 else
4   puts "Desculpe, o objeto nao entende a mensagem 'fala'."
5 end

As seguintes instruções:

1 d1 = d
2 d1.display

fazem com que d e d1 apontem para o mesmo objeto.

Podemos perguntar para qualquer objeto de qual classe ele é membro utilizando o método Object.class. No programa acima, se escrevermos as seguintes instruções:

1 d = Cachorro.new('Pastor', 'Lassie')
2 puts d.class.to_s

O resultado será:

1 >ruby p029dog.rb
2 Cachorro
3 >Exit code: 0

O método instance_of? retorna verdadeiro (true) se um objeto é instância de dada classe, como no exemplo abaixo:

1 num = 10
2 puts(num.instance_of? Fixnum) # saída true

Construtores Literais

Isso significa que podemos utilizar uma notação especial, ao invés de chamar new, para criar um novo objeto de determinada classe. As classes com construtores literais são demonstradas abaixo. Quando usamos algum desses construtores literais, fazemos que um novo objeto passe a existir.

Exemplos:

String – ‘hello’ or “hello”
Symbol – :symbol or :“hello wrold”
Array – [x, y, z]
Hash – {"India" => "IN"}
Range – 3..7 or 3…7

Coleta de Lixo (Garbage Collection)

A instrução abaixo:

d = nil

faz com que d receba uma referência para nil, isto significa que d não aponta para coisa alguma. Agora, se fizermos:

d1 = nil

então o objeto Cachorro é abandonado e eleito para a Coleta de Lixo (Garbage Collection – GC). O Ruby aloca ao menos 8 megabytes de memória na pilha. O coletor de lixo do Ruby é do tipo de marque-e-limpa. O estágio de “marcação” verifica se o objeto está em uso. Se um objeto é referenciado por uma variável que pode ser utilizado no escopo atual, o objeto (e qualquer objeto dentro dele) é marcado para ser mantido. Se a variável deixa de existir e o objeto não possuir outras referências, ele não é marcado. O estágio da “limpeza” remove da memória os objetos que não foram marcados.

O Ruby usa um mecanismo conservador de marca-e-limpar. Não há garantia de que o objeto será liberado pelo coletor de lixo antes do programa terminar.

Se você colocar algo dentro de um vetor e você mantiver esse vetor ativo, ele estará todo marcado. Se você referenciar um objeto por uma constante ou por uma variável global, ele sempre estará marcado.

Métodos de Classe

A ideia dos métodos de classe é que você envie uma mensagem para o objeto que representa a classe ao invés de um objeto que é instância da classe. Métodos de classe cumprem esse papel. Algumas operações devem ser executadas por pela classe e não por uma instância dela. O método new é um excelente exemplo disso. Nós chamamos Cachorro.new porque até que uma instância esteja instanciada, não poderemos enviar nenhuma mensagem para o objeto. Além de que, a tarefa de gerar um novo objeto pertence à classe.

Não faz sentido um objeto da classe Cachorro gere outro objeto do mesmo tipo. Faz sentido, contudo, que o processo de criação de um objeto seja centralizado como uma tarefa da classe Cachorro. É vital entender que por Cachorro.new, nós podemos acessar todo o objeto da classe Cachorro, mas não suas instâncias. Os objetos do tipo cachorro (instâncias da classe Cachorro) não possuem esse método. Um objeto de classe (como Cachorro) possui seus próprios métodos, seu próprio estado, sua própria identidade e não divide essas características com suas instâncias.

Aqui temos um exemplo:

Cachorro#late – o método de instância late na classe Cachorro

Cachorro.cor – o método cor da classe Cachorro

Cachorro::cor – outra forma de escrever o método cor da classe Cachorro

Nos textos sobre Ruby, a notação do caractere # (tralha, sustenido, jogo-da-velha) é usada para indicar um método de instância, por exemplo, File.chmod indica o método de classe chmod da classe File, e File#chmod indica o método de instância que possui o mesmo nome. Esta notação não faz parte da sintaxe do Ruby.

Você aprenderá como criar seus métodos de classe nos próximos capítulos deste tutorial.

Logo do Guru-SP

Este material tem como base o tutorial do RubyLearning.com de Satish Talim e foi traduzido por membros do GURU-SP com a permissão do autor.

Ajude o RubyLearning participando em algum dos cursos pagos ou fazendo uma doação para o projeto

Voltar para o índice