DEV Community

Cover image for Orientação a Objetos com Ruby (parte 3) - Mais sobre métodos
Daniel Moreto
Daniel Moreto

Posted on • Updated on

Orientação a Objetos com Ruby (parte 3) - Mais sobre métodos

Fala comiigo!!

Dando continuidade à nossa série de POO (Programação Orientada a Objetos) com Ruby, nessa parte vamos nos aprofundar mais nos métodos, passando por alguns métodos especiais e métodos estáticos na Orientação a Objetos e como o Ruby lida com cada um deles.

Pra você que está chegando neste post, o código que escreveremos aqui parte da branch parte_02 que foi construído no post anterior e está nesse link:

https://github.com/dfmoreto/dev_to-poo_ruby/tree/parte_02


Construção de Objetos

No post anterior, nós vimos como construir um objeto. Por exemplo a comanda:

minha_comanda = Comanda.new
Enter fullscreen mode Exit fullscreen mode

Lembrando que o objeto tem um nome mais chique que é instância e que o ato de criar um objeto também pode ser chamado de instanciar.

Toda vez que este método new é chamado, ele cria o objeto e chama o método initialize da Classe para que o objeto possa ser configurado. Este método é um tipo de método especial que chamamos de construtor e mesmo que não tenhamos declarado esse método na nossa classe Comanda, o Comanda.new funcionou porque todas as classes possuem esse método de maneira implícita.

Vamos lá na classe Comanda e adicione o seguinte:

class Comanda
  def initialize
  end

  def numero=(numero)
  ...
end
Enter fullscreen mode Exit fullscreen mode

Repare que o método está vazio, ele não está fazendo nada realmente útil. É exatamente desta forma que initialize existe de maneira implícita: vazio. Nos posts futuros nós vamos entender como ele recebe esse método de maneira implícita mas por hora podemos assumir que ele sempre esteve ai.

Pra que serve um método construtor?

Acima nós declaramos ele vazio, mas o método construtor tem uma função importante. É nele que nós podemos informar os valores com os quais queremos que nossos objetos sejam criados.

Vamos ao exemplo da Comanda: ela tem os atributos numero, atendente, mesa e itens. Enquanto não atribuímos valor nenhum para estes atributos, eles estão vazios, sem nenhum valor. Para ser mais exato estão com valores nulos (que no Ruby é representado pelo nil).

Aqui podemos fazer umas perguntas interessantes. Uma Comanda pode ser aberta, ou passar a existir, sem um número? E sem a mesa? E sem uma pessoa que atenda? E sem item nenhum?

Sem item nenhum até que vai, porque no começo realmente não haverá itens, mas ainda assim não é legal que deixemos o atributo item nulo. Porém sem um número, sem uma mesa e sem atendente, acredito que não faça muito sentido. No nosso caso, o propósito de existir de uma Comanda é ter um número, uma mesa que ela representa e quem é a pessoa atendente. Ela tem uma DEPENDÊNCIA desses dados existirem.

Alterando o Construtor da Comanda

Se uma Comanda não deveria existir sem um número, sem uma mesa e sem uma pessoa que atenda, podemos exigir que ela só seja instanciada com o número, com a mesa e com atendente.

Pra fazer isso, vamos alterar o método initialize que declaramos antes pra esse:

class Comanda
  def initialize(numero, mesa, atendente)
    @numero = numero
    @mesa = mesa
    @atendente = atendente
    @itens = []
  end

  def numero=(numero)
  ...
end
Enter fullscreen mode Exit fullscreen mode

Repare que agora o método está esperando um argumento para o numero, outro para a mesa e um terceito para atendente.

Se a gente for tentar executar o código (no README do repositório tem como fazer isso), uma exceção como essa será levantada:

main.rb:2:in `initialize': wrong number of arguments (given 0, expected 3) (ArgumentError)
Enter fullscreen mode Exit fullscreen mode

Talvez no seu código não esteja apontando pra linha 2 do arquivo main.rb (main.rb:2), mas o erro será o mesmo.

Este erro está nos informando que o método initialize está recebendo um número errado de argumentos, que 0 argumentos foram passados e ele espera que sejam enviados 3.

Lembra que eu falei que quando chamamos Comanda.new para construir o objeto, ele chama o método initialize? Como nosso initialize agora está definido com 3 parâmetros, o new também precisa ser chamado com 3.

Bora alterar a instanciação da Comanda para enviar agora o número, a mesa e a atendente. Onde estava:

minha_comanda = Comanda.new
minha_comanda.numero = 5421
minha_comanda.atendente = "Maria"
minha_comanda.mesa = 17
Enter fullscreen mode Exit fullscreen mode

Agora podemos colocar:

minha_comanda = Comanda.new(5421, 17, 'Maria')
Enter fullscreen mode Exit fullscreen mode

Se voltarmos a executar o código Ruby, teremos o resultado novamente sem erros:

Número: 5421 | Atendente: Maria | Mesa: 17 | Itens: Café pingado,Pão de queijo
Enter fullscreen mode Exit fullscreen mode

Não vamos mexer nos itens por ora, só nos interessa por enquanto que na instanciação ele receba um array vazio.

Métodos de Escrita e Leitura

É importante que uma classe proteja o livre acesso aos seus atributos. Especificamente no Ruby isso já vem por padrão, tanto que criamos métodos que os manipulam.

Olhando por um ponto de vista de manter os seus atributos encapsulados (é um conceito que logo vamos ver) dentro dela, os métodos são uma estratégias interessante, já que com os métodos a classe é capaz de decidir como essa alteração vai ocorrer.

No caso da Comanda, nós já criamos métodos deste tipo mesmo sem perceber.

Os métodos de escrita são chamados de Setters, que utilizamos para atribuir algum valor num atributo. Repare que nosso código contém métodos que fazem isso, e eles tem um = no final e são eles: numero=, atendente=, mesa= e itens=. Este são métodos Setters.

Os relacionados a leitura são os Getters. Ou seja, são criados quando queremos disponibilizar a leitura de algum atributo. Ele simplesmente retorna o valor do atributo. No caso dos Getters eles não tem nenhum caractere especial no nome, mas olhando o corpo do método você é capaz de inferir. Temos: numero, mesa, atendente e itens. O método fechar não é um Getter porque ele não tem como objetivo único retornar o valor de um atributo.

Enxugando o código

Como no Ruby o único jeito de acessar os atributos de um objeto é por meio dos métodos, isso faz com que obrigatoriamente precisemos dos Getters e dos Setters. Mas pra não precisarmos ficar criando sempre esses métodos, o Ruby fornece pra gente meios de criá-los automaticamente.

Para Getters, os métodos criados especificamente para ler o valor de um atributo, podemos utilizar o attr_reader.

Atualmente, na nossa classe Comanda, temos quatro métodos cuja única finalidade é retornar o valor de um atributo e são eles:

class Comanda
  ...

  def numero
    @numero
  end

  ...

  def atendente
    @atendente
  end

  ...

  def mesa
    @mesa
  end

  ...

  def itens
    @itens
  end

  ...
end

...
Enter fullscreen mode Exit fullscreen mode

Nós vamos REMOVER estes métodos e adicionar no topo da classe Comanda o attr_reader dessa forma:

class Comanda
  attr_reader :numero, :atendente, :mesa, :itens

  def initialize(numero, mesa, atendente)
  ...
end

...
Enter fullscreen mode Exit fullscreen mode

Os quatro métodos numero, atendente, mesa e itens foram removidos e este attr_reader foi adicionado. O attr_reader é um método interno do Ruby que está pegando este Array de valores :numero, :atendente, :mesa e :itens e fazendo, por baixo dos panos, a criação daqueles quatro métodos que acabamos de remover.

Para os Setters, os métodos que fazem a escrita de valores dos atributos, nós temos o attr_writer. Na nossa classe Comanda, os métodos Setters são:

class Comanda
  ...

  def numero=(numero)
    @numero = numero
  end

  def atendente=(atendente)
    @atendente = atendente
  end

  def mesa=(mesa)
    @mesa = mesa
  end

  def itens=(itens)
    @itens= itens
  end

  ...
end

...
Enter fullscreen mode Exit fullscreen mode

Da mesma forma que fizemos anteriormente, vamos REMOVER estes quatro métodos e adicionar o seguinte no topo da classe:

class Comanda
  attr_reader :numero, :atendente, :mesa, :itens
  attr_writer :numero, :atendente, :mesa, :itens

  ...
end
Enter fullscreen mode Exit fullscreen mode

Logo abaixo do attr_reader, colocamos o attr_writer que faz a mesma coisa: é um método interno do Ruby que está pegando este Array de valores :numero, :mesa e :itens e fazendo, por baixo dos panos, a criação daqueles três métodos que acabamos de remover.

Uma informação importante é que não precisamos deixar os dois necessariamente nesta ordem. O attr_reader não precisa vir antes do attr_writer e eles também não precisam estar no topo da classe. Eles só precisam existir em algum momento. Como eles são um recurso que, como disse anteriormente, cria aqueles métodos por baixo dos panos, a ordem em que eles aparecem não faz muita diferença porque os métodos estarão lá para serem utilizados.

Com estes dois recursos adicionados, quando executamos o nosso arquivo no terminal temos exatamente o mesmo resultado que tínhamos antes:

Número: 5421 | Atendente: Maria | Mesa: 17 | Itens: Café pingado,Pão de queijo
Enter fullscreen mode Exit fullscreen mode

Isso mostra que a substituição dos métodos pelos attr_reader e pelo attr_writer foi um sucesso

Mas ainda não acabou :)

O Ruby nos disponibiliza mais uma facilidade para os casos que queremos criar tanto um Getter quanto um Setter para um atributo nosso, e ele é o attr_accessor.

Com o attr_accessor, nós criados um "acesso completo" a um atributo do objeto e como temos isso sendo feito atualmente para numero, atendente, mesa e itens, nós podemos fazer uma substituição interessante. Onde temos o attr_reader e o attr_writer na classe Comanda:

class Comanda
  attr_reader :numero, :atendente, :mesa, :itens
  attr_writer :numero, :atendente, :mesa, :itens

  ...
end
Enter fullscreen mode Exit fullscreen mode

Podemos SUBSTITUIR pelo attr_accessor:

class Comanda
  attr_accessor :numero, :atendente, :mesa, :itens

  ...
end
Enter fullscreen mode Exit fullscreen mode

Desta forma estamos criando ambos Getters e Setters para numero, atendente, mesa e itens.

E se executamos nosso arquivo novamente, temos o mesmo resultado:

Número: 5421 | Atendente: Maria | Mesa: 17 | Itens: Café pingado,Pão de queijo
Enter fullscreen mode Exit fullscreen mode

Ou seja, tudo certo.

Métodos Estáticos

Até o momento vimos que vários recursos que dizem respeito ao objeto, que é criado a partir de uma classe. Porém existem recursos que não podemos atrelar à instância de uma classe (um objeto), mas à classe em si.

Vamos a um exemplo: atualmente nós instanciamos a classe Comanda passando número, atendente e mesa. Porém nós vamos seguir por um outro caminho agora; vamos fazer com que a Comanda gere seu próprio número em ordem de criação. Seguindo o raciocínio, a primeira comanda receberá o número #1, a segunda o número #2 e assim por diante.

Se olharmos para o conceito do objeto, seria impossível conseguirmos fazer isso porque cada objeto é responsável apenas por si mesmo: por seu próprio número, sua própria mesa e seus próprios itens. Para que isso aconteça nós precisamos manter uma contagem da quantidade de objetos da comanda que foram criados.

Existem vários meios de fazer isso, mas neste momento nós vamos colocar um método que será responsável por gerenciar a quantidade de instâncias de uma Comanda na própria classe Comanda.

Primeiro, vamos criar uma variável na classe para conseguirmos manter a quantidade de Comandas instanciadas.

Adicione o seguinte na classe Comanda:

class Comanda
  @contador_de_comandas = 0

  attr_accessor :numero, :atendente, :mesa, :itens
  ...
end
Enter fullscreen mode Exit fullscreen mode

Uai, adicionamos uma variável para o objeto do topo da Classe? Podemos dizer que não. Quando colocamos uma variável de instância fora dos métodos do objeto, ela pertence à própria Classe. Ou seja, este valor está atrelado à própria classe Comanda, ou seja, independente do objeto o valor será sempre o mesmo.

Com essa variável criada, que podemos chamar de atributo estático porque vai possuir o mesmo valor em todos os objetos, vamos criar também um método para gerar o número da comanda a partir dessa variável. Para isso, vamos adicionar no nossa classe comanda o seguinte:

class Comanda
  ...

  def fechar
  ...

  def self.gerar_numero
    @contador_de_comandas += 1
  end
end

...
Enter fullscreen mode Exit fullscreen mode

Quando colocamos este self na frente de um método, ele é criado na própria classe, ou seja, é um método estático. E neste método estamos pegando o valor do @contador_de_comandas, somando o valor 1, reatribuindo o novo valor na própria própria variável e retornado. E pelo fato dele ser um método estático, nos conseguimos acessar o valor de um atributo estático do mesmo jeito que acessamos o valor de um atributo do objeto num método do objeto.

Vamos fazer um teste pra ver como funciona um método de classe. Primeiro, vamos tentar executar esse método a partir de um objeto da classe Comanda. Adicione o seguinte no seu código:

class Comanda
  ...
end

...

puts minha_comanda.gerar_numero
Enter fullscreen mode Exit fullscreen mode

Quando tentamos executar o código, é retornado essa mensagem

Número: 5421 | Atendente: Maria | Mesa: 17 | Itens: Café pingado,Pão de queijo
main.rb:26:in `<main>': undefined method `gerar_numero' for #<Comanda:0x00007fb0a50a81d0 @numero=5421, @mesa=17, @atendente="Maria", @itens=["Café pingado", "Pão de queijo"]> (NoMethodError)

puts minha_comanda.gerar_numero
                  ^^^^^^^^^^^^^
Enter fullscreen mode Exit fullscreen mode

Na primeira linha ele imprimiu o texto de antes, mas da segunda em diante ele está trazendo um erro que informa que o método gerar_numero não existe pro objeto que está na variável minha_comada

Este é um erro esperado, já que o método gerar_numero não existe para os objetos da classe Comanda, mas para a classe em si. Para conseguimos chamar este método direto na classe, vamos substituir este código que acabamos de colocar:

puts minha_comanda.gerar_numero
Enter fullscreen mode Exit fullscreen mode

Por este

puts Comanda.gerar_numero
Enter fullscreen mode Exit fullscreen mode

Estamos chamando o método gerar_numero a partir da própria classe Comanda. Repare que é o mesmo formato de quando chamamos o Comanda.new

E ao executar o arquivo não temos mais nenhum erro

Número: 5421 | Atendente: Maria | Mesa: 17 | Itens: Café pingado,Pão de queijo
1
Enter fullscreen mode Exit fullscreen mode

Imprimimos o conteúdo das linhas anteriores e executamos o método gerar_numero a partir da classe Comanda, cujo valor impresso foi 1.

Agora que vimos como funciona, podemos APAGAR essa linha que adicionamos para teste:

puts Comanda.gerar_numero
Enter fullscreen mode Exit fullscreen mode

Enriquecendo nossos exemplos

Com o entendimento de como podemos fazer a geração do número da Comanda, vamos colocar isso no nosso código e vamos aproveitar para enriquecer mais nosso exemplo para vermos que está tudo funcionado bem.

No nosso arquivo de código, onde temos:

minha_comanda = Comanda.new(5421, 17)
Enter fullscreen mode Exit fullscreen mode

Vamos substituir por:

minha_comanda = Comanda.new(Comanda.gerar_numero, 17, 'Maria')
Enter fullscreen mode Exit fullscreen mode

Repare que antes nós passamos um número aleatório, agora estamos passando o retorno do método estático gerar_numero da classe Comanda.

Além disso, vamos aproveitar para criar mais 2 comandas. Adicione no seu código:

puts "Número: #{minha_comanda.numero} | Atendente: #{minha_comanda.atendente} | Mesa: #{minha_comanda.mesa} | Itens: #{minha_comanda.itens.join(',')}"

segunda_comanda = Comanda.new(Comanda.gerar_numero, 13, 'João')
segunda_comanda.itens = ['Capuccino com Chatilly', 'Coxinha Assada']
puts "Número: #{segunda_comanda.numero} | Atendente: #{segunda_comanda.atendente} | Mesa: #{segunda_comanda.mesa} | Itens: #{segunda_comanda.itens.join(',')}"

terceira_comanda = Comanda.new(Comanda.gerar_numero, 9, 'Maria')
terceira_comanda.itens = ['Café coado', 'Pastel de Frango']
puts "Número: #{terceira_comanda.numero} | Atendente: #{terceira_comanda.atendente} | Mesa: #{terceira_comanda.mesa} | Itens: #{terceira_comanda.itens.join(',')}"
Enter fullscreen mode Exit fullscreen mode

E pra finalizar, se executarmos este código, teremos o resultado:

Número: 1 | Atendente: Maria | Mesa: 17 | Itens: Café pingado,Pão de queijo
Número: 2 | Atendente: Maria | Mesa: 13 | Itens: Capuccino com Chatilly,Coxinha Assada
Número: 3 | Atendente: Maria | Mesa: 9 | Itens: Café coado,Pastel de Frango
Enter fullscreen mode Exit fullscreen mode

Repare que o número da Comanda está em ordem crescente de criação. Isso significa que nosso método estático está funcionado perfeitamente independente os objetos da classe Comanda que são criados.

Com a geração automática dos números e vendo trabalhadora mulher atender sozinha três mesas graças à precarização do trabalho no capitalismo, finalizamos este post.


O código que produzimos aqui está no mesmo repositório na branch parte_03.
https://github.com/dfmoreto/dev_to-poo_ruby

Já já nos vemos nos próximos posts. Ainda tem conteúdo pra chegar =)

Top comments (1)

Collapse
 
rochaisadora profile image
Isadora Rocha

Muito bom! Aprendi muito