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
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
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
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:
minha_comanda = Comanda.new
minha_comanda.numero = 5421
minha_comanda.atendente = "Maria"
minha_comanda.mesa = 17
Agora podemos colocar:
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:
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:
class Comanda
attr_reader :numero, :atendente, :mesa, :itens
def initialize(numero, mesa, atendente)
...
end
...
Os quatro métodos
numero
,atendente
,mesa
eitens
foram removidos e esteattr_reader
foi adicionado. Oattr_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
...
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
Logo abaixo do
attr_reader
, colocamos oattr_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
Isso mostra que a substituição dos métodos pelos
attr_reader
e peloattr_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
Podemos SUBSTITUIR pelo attr_accessor:
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:
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:
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:
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:
class Comanda
...
end
...
puts minha_comanda.gerar_numero
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
^^^^^^^^^^^^^
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ávelminha_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
Por este
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 oComanda.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
Imprimimos o conteúdo das linhas anteriores e executamos o método
gerar_numero
a partir da classe Comanda, cujo valor impresso foi1
.
Agora que vimos como funciona, podemos APAGAR essa linha que adicionamos para teste:
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:
minha_comanda = Comanda.new(5421, 17)
Vamos substituir por:
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:
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:
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 =)
Top comments (1)
Muito bom! Aprendi muito