Orientação a Objetos com Ruby (parte 3) - Mais sobre métodos
Tags: ruby, programming, braziliandevs
Published: 2023-09-19 11:08:41

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:

```ruby
minha_comanda = Comanda.new
```
> 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:
```ruby
class Comanda
  def initialize
  end

  def numero=(numero)
  ...
end
```

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:
```ruby
class Comanda
  def initialize(numero, mesa, atendente)
    @numero = numero
    @mesa = mesa
    @atendente = atendente
    @itens = []
  end

  def numero=(numero)
  ...
end
```
> 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)
```
> 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:
```ruby
minha_comanda = Comanda.new
minha_comanda.numero = 5421
minha_comanda.atendente = "Maria"
minha_comanda.mesa = 17
```
Agora podemos colocar:
```ruby
minha_comanda = Comanda.new(5421, 17, 'Maria')
```

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
```

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:
```ruby
class Comanda
  ...

  def numero
    @numero
  end

  ...

  def atendente
    @atendente
  end

  ...

  def mesa
    @mesa
  end

  ...

  def itens
    @itens
  end

  ...
end

...
```

Nós vamos REMOVER estes métodos e adicionar no topo da classe **Comanda** o `attr_reader` dessa forma:
```ruby
class Comanda
  attr_reader :numero, :atendente, :mesa, :itens

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

...
```
> 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:
```ruby
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

...
```

Da mesma forma que fizemos anteriormente, vamos REMOVER estes quatro métodos e adicionar o seguinte no topo da classe:
```ruby
class Comanda
  attr_reader :numero, :atendente, :mesa, :itens
  attr_writer :numero, :atendente, :mesa, :itens

  ...
end
```
> 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:
```bash
Número: 5421 | Atendente: Maria | Mesa: 17 | Itens: Café pingado,Pão de queijo
```
> 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**:
```ruby
class Comanda
  attr_reader :numero, :atendente, :mesa, :itens
  attr_writer :numero, :atendente, :mesa, :itens

  ...
end
```
Podemos SUBSTITUIR pelo _attr_accessor_:
```ruby
class Comanda
  attr_accessor :numero, :atendente, :mesa, :itens

  ...
end
```
> Desta forma estamos criando ambos _Getters_ e _Setters_ para _numero_, _atendente_, _mesa_ e _itens_.

E se executamos nosso arquivo novamente, temos o mesmo resultado:
```bash
Número: 5421 | Atendente: Maria | Mesa: 17 | Itens: Café pingado,Pão de queijo
```
> 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**:
```ruby
class Comanda
  @contador_de_comandas = 0

  attr_accessor :numero, :atendente, :mesa, :itens
  ...
end
```
> 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:
```ruby
class Comanda
  ...

  def fechar
  ...

  def self.gerar_numero
    @contador_de_comandas += 1
  end
end

...
```
> 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:
```ruby
class Comanda
  ...
end

...

puts minha_comanda.gerar_numero
```

Quando tentamos executar o código, é retornado essa mensagem
```bash
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
                  ^^^^^^^^^^^^^
```
> 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:
```ruby
puts minha_comanda.gerar_numero
```
Por este
```ruby
puts Comanda.gerar_numero
```
> 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
```bash
Número: 5421 | Atendente: Maria | Mesa: 17 | Itens: Café pingado,Pão de queijo
1
```
> 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:
```ruby
puts Comanda.gerar_numero
```

### 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:
```ruby
minha_comanda = Comanda.new(5421, 17)
```
Vamos **substituir** por:
```ruby
minha_comanda = Comanda.new(Comanda.gerar_numero, 17, 'Maria')
```
> 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:
```ruby
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(',')}"
```

E pra finalizar, se executarmos este código, teremos o resultado:
```bash
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
```
> 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 =)