Um júnior já deveria saber como reduzir a quantidade de IFs de um código (e você, sabe?)
0Mas não tem problema, porque é justamente isso que a gente
está treinando dentro dessa playlist. Então, bora aniquilar alguns IFs. Então, a situação que a
gente tem aqui é uma Factory que criou uma instância do jogo e a gente aprendeu a programar
esse design pattern no vídeo passado. Inclusive, nesse mesmo vídeo,
a gente aprendeu a programar outro design pattern mega
poderoso, chamado: Observer. Se você tem interesse nisso, eu
coloquei o link desse vídeo na descrição. Vale muito a pena! Mas a nossa maior irritação
aqui, na verdade, é a quantidade de IFs que a gente
tem dentro desse método movePlayer(). Daí, aconteceu
uma coisa muito legal e eu realmente gostaria que
isso continuasse aqui no canal. Muitos de vocês
surgeriram nos comentários que eu trocasse tudo
isso por uma série de Switch/Cases, só que na minha visão, isso não
resolve o problema. Porque sabe qual é o problema?
Identificar qual é o problema. Daí, eu pergunto para você: qual é
o real problema desse código? E para esclarecer o
que eu estou falando, considera por um breve momento que
o problema não foi usar o IF ou o Switch. Mas se não é isso,
o que sobra de problema? Aí eu peço que você se
lembre que nessa playlist, a gente está conversando muito
sobre Arquitetura de Software, sobre modelagem, sobre como a
gente consegue fazer o nosso programa ser modificado, ser expandido
com a menor fricção possível. Mas então tá… Sabe qual é o
real problema desse código aqui? E é óbvio que podem ter vários outros
problemas, mas um que está gritante é que: para cada novo input que
eu quiser aceitar no meu jogo, para cada nova tecla pressionada, eu vou precisar vir aqui e
adicionar uma nova condicional. E eu não estou falando em ter que
criar uma funcionalidade nova, porque isso você não vai ter como
escapar, é uma nova regra de negócio… Vão ter as condicionais relacionadas a
essa regra de negócio ou não, tanto faz. Mas o que estou falando é além disso. Então, além da regra de negócio, você vai ter que programar
manualmente mais uma condicional para conseguir chegar nessa
regra de negócio. E quando a gente trata de inputs,
isso pode escalar muito rápido. Por exemplo, implementar
uma funcionalidade em que o jogador aperta a tecla B
e ele solta uma espécie de bomba, que faz todos os outros jogadores
ao redor perderem pontos. Imagina que massa: você chega bem
próximo da frutinha, mas não pega ela… Aí espera todos os outros jogadores
se aproximarem para explodir a bomba. É muito massa! Ou usar mais teclas para
fazer mais funcionalidades, como, por exemplo, a tecla 1, 2 e 3… E tem um jeito bem simples de tudo isso
não escalar em um monte de condicionais, que é usando
simplesmente Object Literals. E é tão simples que eu nem sei se
dá para dizer que isso é um design pattern. É questão de usar objetos normais do
JavaScript para guardar suas funções e acessá-las através da chave. E para colocar todo mundo na mesma
página, porque essa parte é importante, o segredo está na relação
entre a chave e o valor, onde a chave vai ser uma string ou qualquer
coisa, e o valor vai ser uma função. Então, cavucando no
console do navegador, eu vou criar um objeto
chamado “pessoa”. Ele vai ter um nome, “Filipe”; um sobrenome, “Deschamps”. E é isso.
Fechado. Agora, para eu acessar
a propriedade “nome”, para conseguir o valor que está
dentro da propriedade “nome”, eu faço pessoa.nome,
assim correto… Ou pessoa[“nome”],
vai retornar a mesma coisa. Com exatamente a mesma mecânica,
ao invés de eu salvar no valor uma string, eu posso salvar uma função. Por exemplo, vou criar
um outro objeto aqui: const calculadora = { E nesse objeto tem uma
propriedade chamada soma: ou somar: vai… é melhor. E aí, o valor dela é uma função, em que a única coisa que ela
faz é retornar 1+1. Fechado? A gente fecha o objeto. E
agora para acessar essa função, vamos digitar calculadora.somar E quando eu apertar
o Enter… não somou! Que bom! Que bom que não
somou. Porque dessa forma, eu estou acessando somente
o valor que está lá dentro. E por acaso, como a gente está
vendo aqui, o valor é uma função. E outra forma de acessar
é calculadora.somar, vai retornar da mesma forma. Então, se ele
retorna uma função, eu consigo executar essa
função logo em seguida. Então, eu posso fazer isso aqui… Só que eu acho feio,
então eu faria uma outra coisa. Eu faria:
const somar = calculadora[“somar”]. Então, agora o retorno disso aqui,
que é uma função, vai ser colocado aqui. E isso se transforma numa função. Então, vou apertar o Enter e
se eu digitar “somar() ” e executar, porque é uma função,
ele vai retornar 2. Exelente! E eu posso ir mais
além, eu posso passar parâmetros para dentro dessa função. Então, eu vou dar um refresh aqui
e vamos pegar a definição do objeto. Eu vou criar uma nova funcionalidade
dentro da calculadora, que é duplicar. Que também uma função, só que essa
função recebe um valor… Que a gente vai colocar
com o nome de “valor” mesmo. E retorna esse valor vezes 2,
porque ele quer duplicar. Vou apertar o Enter… Eu vou atribuir
essa função por uma outra variável const duplicar =
calculadora[“duplicar”], está aqui… E agora, quando eu executar essa função
passando um número como, por exemplo, 4, ele deveria me retornar 8.
Sensacional! Pô, então se aquele
parâmetro “keyPressed”, que está dentro
do objeto “command”, os valores são, por exemplo,
“ArrowUp” ou “ArrowDown”, não é só questão então
de casar essas strings com nomes de funções
dentro de um objeto? Pimba! É exatamente isso
que eu vou programar. Só que eu vou, inclusive, utilizar
um novo recurso do JavaScript. Olha que delicinha… A primeira coisa que eu fiz foi criar um
objeto chamado movimentos aceitos, e dentro dele, eu programei as
funções relacionadas aos movimentos que eu quero aceitar agora. Mas, por enquanto, o legal é que eu
utilizei um novo recurso do JavaScript, como eu comentei, que vai
reaproveitar esse nome aqui para colocar como o
nome da chave do objeto. Então, se eu copiar esse objeto
inteiro e colar no navegador… Vou colar ele inteiro aqui… Olha que legal, se eu acessar
“acceptedMoves” e der um Enter, ele vai printar um objeto que tem
aquele nome como o valor da chave, e também com o valor da função.
É sensacional! Então, novamente consigo acessar
acceptedMoves como por exemplo, [“ArrowDown”], como ele está sugerindo
aqui, e é uma função. E agora, a gente vai programar
a lógica que ativa essas funções e é muito simples! Realmente, sem nenhum mistério. Eu já tinha no código o valor do
“keyPressed”, e ele vem de dentro do comando. Então, aqui dentro dessa variável
vão vir strings do tipo ‘ArrowUp’, ‘ArrowRight’, escrito exatamente
dessa forma. Então, com esses valores e strings, eu vou usar exatamente a
mesma técnica de antes, que é: de dentro do nosso objeto,
eu quero acessar a função que está lá dentro para
esse valor de “keyPressed”. Então, por exemplo, se
vier a string ‘ArrowUp’, eu quero acessar a função que
está lá dentro, que nesse caso vai ser essa função inteira aqui. E aí, tudo isso vai ser passado
para dentro dessa variável “moveFunction”. E em seguida, eu executo essa
função com o valor do player, passando o player para
dentro, porque mais para frente, a gente vai colocar a
regra de negócio ali dentro. E aí, um detalhe importante: eu
coloquei esse “return” aqui temporário, porque eu não quero
executar essas coisas, mas eu também não quero
deletar o código, vou reaproveitar. Então, por enquanto,
deixa o “return” aqui. E se eu salvar e
voltar para o navegador… Se eu der um refresh,
olha que interessante: vou apertar arrow up e
ele me trouxe três logs. Perfeito! O primeiro é o log do notifier,
falando que está notificando um Observer. Perfeito. Segundo, é quando
entra na função de mover, que é “Moving player1
with ArrowUp”, que já tinha. E o terceiro log é o log
do “Moving player Up”, sinalizando que a gente, de fato,
conseguiu entrar dentro daquele objeto, pegar a função e executá-la. Só que agora,
uma coisa interessante… Se eu apertar a tecla B, para aquele
nosso recurso de bomba, por exemplo. O que será que vai acontecer? O que você acha que vai acontecer? A gente não implementou nada e eu
quero apertar a tecla B. Vamos ver? Então, vamos lá: 3, 2, 1… Eita!! Quebrou!
Um bug maluco aqui. Ele está falando que o “moveFunction”
não é uma função, na linha 58. Então, vamos pegar o nosso Client
e ir lá na linha 58. Essa linha aqui. Ele está falando que para
a tecla B, por exemplo, isso aqui que está dentro
disso aqui, não é uma função. E óbvio que não é uma função,
porque o que aconteceu? No “keyPressed”, veio uma
string chamada “b”… Ele tentou acessar isso aqui, passando
o valor “b”, mas não existe o valor “b”. Então, ele vai retornar “undefined”
para dentro disso aqui… E a gente tenta executar
como se fosse uma função, um valor que na
verdade é “undefined”. E agora, a gente tem um belo de um
dilema e eu quero ver a sua opinião. Onde você acha que a gente deveria
implementar alguma espécie de filtro? Por exemplo, se a gente
for aqui no “keyboardListener”, onde a gente emite
evento para todo mundo, a cada “keyPressed”, a cada “handleKeydown”
que acontece de qualquer tecla, a gente notifica todo mundo. E eu me pergunto: a gente deveria notificar
só das teclas que aceitamos hoje? Então, a gente poderia criar
uma espécie de um array, e se passar nesse
teste de array, se a tecla que foi pressionada
está incluída dentro desse array, ele emite o evento? Mas pensa comigo…
Está certo isso? Colocar essa responsabilidade
dentro do “keyboardListener”? Eu acho que não. E eu vou te dar três motivos do
porquê eu acho que isso está errado. Primeiro: o Keyboard Listener tem
a responsabilidade de escutar o teclado. Ele nasceu para isso, ou ao menos
é o que eu espero, dado o nome dele. Para ficar mais claro, caso
ele se chamasse Arrow Listener, aí eu esperaria outra coisa. Eu realmente esperaria que ele só
escutasse pelas “arrows” do teclado. Segundo: a cada
novo tipo de movimento, a gente teria que modificar o
código em dois lugares diferentes: nesse array que vai fazer o filtro
dentro do “keyboardListener”, e também lá dentro da lógica do jogo. Terceiro: eu não gosto de
deixar o método público assim frágil, até porque como é uma interface, qualquer coisa pode utilizar esse método,
pode passar o que quiser lá para dentro. Então, eu acho que fica
um código muito mais seguro se a gente conseguir se proteger
de qualquer coisa. Agora, um argumento muito válido é quando a gente programar
a nossa camada de network, principalmente se ela for um
Observer do “keyboardListener”. Ou seja, o “keyboardListener”, que é o subject, vai emitir todas as teclas
que a gente pressionar. E o Observer vai receber
todas essas teclas… O Network vai receber
todas essas teclas. Será que a gente vai fazer ele passar
todas essas teclas para o nosso backend? Não sei, vamos deixar esse
problema um pouco mais para frente. Para agora, vamos nos proteger do
que for, dentro do método “movePlayer”. Novamente, muito, muito simples. A única coisa que eu fiz
foi colocar uma verificação se esse “moveFunction”
tem algum valor verdadeiro. Porque se vier um “undefined” aqui,
eu quero descartar. E se vier algum valor real, eu
quero executar essa função. E aí, eu aproveitei também para
melhorar um pouquinho os logs, para mostrar que eu estou
aqui dentro do “game.movePlayer” ou “game.movePlayer.ArrowUp”… E lá embaixo também, no “keyboardListener”,
eu coloquei um trechinho novo no log. Então, vamos salvar tudo isso,
vamos dar um refresh aqui… Agora, se eu apertar a seta para
cima, deveriam aparecer três logs. Então, vamos lá? Show de bola! Aqui o “keyboardListener”, notificado os Observers, o “movePlayer” falando
que vai mover para cima e, de fato, executando
a função “ArrowUp”. Agora, se eu apertar
a tecla B da bomba, deveriam aparecer
só dois logs. Correto? Porque ele não deveria entrar numa
condição como essa, por exemplo. Ele também não deveria explodir,
como se fosse uma bomba. Olha que legal esse trocadilho…
Então vamos lá… Desculpa! Vamos apertar a tecla B, vai! 3, 2, 1… Ae! Perfeito, apareceram os dois
logs, o primeiro do “keyboardListener”, notificando os Observers, e depois o
“movePlayer”, porque ele de fato entrou, mas quando foi pego por aquela
condicional, ele não passou. Show! Agora, eu vou
reimplementar a regra de negócio dentro de cada uma
daquelas funções. Show de bola!
Removi todos os IFs lá de baixo, passei todas as regras de negócio
para dentro de cada função… E agora, daqui para frente, a gente nunca mais vai precisar
implementar uma nova condicional para aceitar um novo movimento. É só questão de
programar uma nova função e ela vai respeitar a regra de negócios
que estiver dentro dessa função. Então, vamos salvar,
testar dentro do navegador… Vou dar um refresh e deveria se
comportar exatamente da mesma forma, a diferença é logar três logs
para sempre. Então, vamos lá? Vamos vir para cima, normal; para
a esquerda; para baixo; para a direita… Se eu apertar até o ponto
de ele chegar no limite da tela, ele deveria ficar preso, para baixo
também preso; para o lado, tudo normal. E você pode, inclusive, extrair do
código todas essas regras de negócio. Porque agora, a única
coisa que você precisa casar é a chave com o comportamento
que você quer, que é a função. Então, pensa nesse
tipo de arquitetura quando você se encontrar em situações
em que precisa mapear várias coisas a várias funcionalidades… E você quer ter só o trabalho de
programar essas novas funcionalidades, e não sair pesquisando
no restante do código por filtros ou condições que vão
inibir a execução do código chegar nessa nova funcionalidade. E deixa eu te fazer uma pergunta: eu sou o único que está gostando
de ficar pareando aqui com vocês? De ficar discutindo sobre Arquitetura
de Software no meio do vídeo? Eu estou achando muito massa,
principalmente a interação nos comentários! Não sei, está diferente. Parece
que o nível está cada vez mais alto. Então, parabéns para todos nós, principalmente para aquela
pessoa que está acompanhando todos os vídeos da playlist! Eu queria mandar um salve
especialmente para você! Então, eu deixei esse
vídeo aqui mais reservado para a gente discutir esse design
pattern, essa Arquitetura de Software, nem sei como é
que se chama isso. E eu empurrei para o vídeo
seguinte as implementações que vão fazer a gente
finalizar a camada do jogo, porque nesse vídeo seguinte,
eu consigo explorar essas coisas com mais calma
e com mais profundidade. A gente vai ver funcionalidades
fundamentais do jogo, por exemplo: adicionar e remover jogadores,
adicionar e remover frutinhas… E talvez o mais importante: detectar a
colisão entre um jogador e uma frutinha. É muito massa implementar isso! Então, como sempre, para
ver esse vídeo é só clicar aqui. Fechado? Valeu!