Buscar

2020-tcc-gjtrbento

Prévia do material em texto

UNIVERSIDADE FEDERAL DO CEARÁ 
CAMPUS DE QUIXADÁ 
CURSO DE ENGENHARIA DE SOFTWARE 
 
 
 
 
 
GABRIEL JORGE TAVARES RAMOS BENTO 
 
 
 
 
 
 
REFATORAÇÃO DO JOGO BICHO UFC RAMPAGE USANDO SOLID E PADRÕES 
DE PROJETO 
 
 
 
 
 
 
 
 
 
 
 
 
QUIXADÁ 
2020 
 
 
GABRIEL JORGE TAVARES RAMOS BENTO 
 
 
 
 
 
 
 
REFATORAÇÃO DO JOGO BICHO UFC RAMPAGE USANDO SOLID E PADRÕES DE 
PROJETO 
 
 
 
 
 
 
 
Trabalho de Conclusão de Curso apresentada 
ao Curso de Engenharia de Software da 
Universidade Federal do Ceará, como requisito 
parcial para obtenção do título de Bacharel em 
Engenharia de Software. Área de concentração: 
Engenharia de Software. 
 
Orientadora: Profª. Dra. Paulyne Matthews 
Jucá. 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
QUIXADÁ 
2020 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
GABRIEL JORGE TAVARES RAMOS BENTO 
 
 
 
 
 
 
 
REFATORAÇÃO DO JOGO BICHO UFC RAMPAGE USANDO SOLID E PADRÕES DE 
PROJETO 
 
 
 
 
 
 
 
Trabalho de Conclusão de Curso apresentada 
ao Curso de Engenharia de Software da 
Universidade Federal do Ceará, como requisito 
parcial para obtenção do título de Bacharel em 
Engenharia de Software. Área de concentração: 
Engenharia de Software. 
 
Aprovada em: ___/___/______. 
 
 
BANCA EXAMINADORA 
 
 
________________________________________ 
Profa. Dra. Paulyne Matthews Jucá (Orientador) 
Universidade Federal do Ceará (UFC) 
 
 
_________________________________________ 
Profa. Me. Antonia Diana Braga Nogueira 
Universidade Federal do Ceará (UFC) 
 
 
_________________________________________ 
Prof. Me. Diego Andrade de Almeida 
Universidade Federal do Ceará (UFC) 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
A minha família. 
Aos grandes amigos que a vida nos dá. 
 
 
 
 
 
 
 
AGRADECIMENTOS 
 
Agradeço primeiramente a minha família que por tanto tempo vem me apoiando em 
decisões difíceis, sempre com confiança. Também agradeço a minha orientadora que durante 
este percurso me guiou e me apoiou sempre em prontidão em dias e horários diversos. E 
agradeço os grandes amigos que conheci durante esse percurso que jamais esquecerei e espero 
sempre poder reencontrá-los para nos embriagar e conversar. E que para todos os 
mencionados aqui, que eu sempre possa ajudá-los e estar sempre ao lado deles apesar das 
distâncias. 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
“O nitrogênio em nosso DNA, o cálcio em 
nossos dentes, o ferro em nosso sangue, o 
carbono em nossas tortas de maçã… Foram 
feitos no interior de estrelas em colapso, agora 
mortas há muito tempo. Nós somos poeira das 
estrelas.” 
(Carl Sagan, Cosmos, 1980) 
 
 
RESUMO 
 
Cada vez mais o mercado de jogos se torna mais competitivo exigindo que os jogos 
desenvolvidos estejam prontos para mudanças rápidas. Para isso, bons projetos usam das 
ferramentas e conhecimento provido pela Engenharia de Software. Esses conhecimentos são 
usados em seus processos que integram profissionais de diferentes áreas, nas ferramentas que 
agilizam o desenvolvimento e no conhecimento prévio adquirido por outros desenvolvedores. 
E em se tratando de código, para se construir um design de código bom, bons 
desenvolvedores usam de princípios SOLID e padrões de projeto. Esses dois conhecimentos 
são fundamentais para a construção de uma estrutura de código boa o suficiente para estar 
pronta para as mudanças rápidas que o mercado exige, aumentando as chances de sucesso dos 
jogos. É nesse ponto que entra o objetivo deste trabalho que aplica a refatoração do jogo 
Bicho UFC Rampage, um jogo desenvolvido por alunos da Universidade Federal do Ceará 
(UFC) em Quixadá, Ceará, usando dessas técnicas fundamentais para qualquer bom 
desenvolvedor. Este trabalho tem como público-alvo desenvolvedores que desejam aprender 
mais sobre refatoração, princípios SOLID e padrões de projeto aplicados a jogos 
desenvolvidos com a engine Unity. O processo de execução se dá com a apresentação do 
projeto em seu estado inicial, e depois parte para a identificação de maus cheiros de design, 
que são indícios de um design de código mau estruturado. Logo depois, aplica os princípios 
SOLID e padrões de projeto para amenizar e/ou eliminar esses maus cheiros melhorando a 
qualidade do design do código. Depois desses passos de refatoração, uma análise estática de 
código é aplicada na versão inicial e final que compara as duas versões mostrando as 
diferenças usando a ferramenta NDepend. 
 
Palavras-chave: Refatoração. SOLID. Padrões de projeto. 
 
 
ABSTRACT 
 
The games market is becoming more and more competitive, demanding that the games 
developed are ready for rapid changes. For this, good projects use the tools and knowledge 
provided by Software Engineering. This knowledge is used in its processes that integrate 
professionals from different areas, in the tools that speed up the development and in the 
previous knowledge acquired by other developers. And when it comes to code, to build good 
code design, good developers use SOLID principles and design patterns. These two skills are 
fundamental to building a code structure good enough to be ready for the rapid changes that 
the market requires, increasing the chances of successful games. And at this point comes the 
objective of this work that applies the refactoring of the game Bicho UFC Rampage, a game 
developed by students from the Federal University of Ceará (UFC) in Quixadá, Ceará, using 
these fundamental techniques for any good developer. This work is aimed at developers who 
want to learn more about refactoring, SOLID principles and design patterns applied to games 
developed with the Unity engine. The execution process takes place with the presentation of 
the project in its initial state, and then goes on to identify bad design smells, which are 
indications of a badly structured code design. Then, apply the SOLID principles and design 
standards to mitigate and/or eliminate these bad smells by improving the quality of the code 
design. After these refactoring steps, a static code analysis is applied in the initial and final 
versions that compare the two versions showing the differences using the NDepend tool. 
 
Keywords: Refactoring. SOLID. Design patterns. 
 
 
LISTA DE FIGURAS 
 
Figura 1 ─ Diagrama de classes UML que representa o estado inicial do projeto ................... 24 
Figura 2 ─ Classe em desacordo com o princípio SRP ............................................................ 28 
Figura 3 ─ Classe em acordo com o princípio SRP ................................................................. 28 
Figura 4 ─ Exemplo da implementação de um personagem que usa uma pistola ................... 29 
Figura 5 ─ Exemplo do isolamento da classe Player das mudanças que acontecem em relação 
as armas .................................................................................................................................... 30 
Figura 6 ─ Classe Player responsável por realizar a atualização de pontos e de vida do 
jogador quando um item é coletado .......................................................................................... 31 
Figura 7 ─ Implementação dos coletáveis mostrando a superclasse e subclasses ................... 31 
Figura 8 ─ Trecho da implementação dos coletáveis de acordo com LSP ............................... 32 
Figura 9 ─ A Player usa qualquer coletável que seja subtipo de ColectibleBase .................... 33 
Figura 10 ─ Estrutura em desacordo com ISP ......................................................................... 34 
Figura 11 ─ Serviços para diferentes clientes separados através de interfaces ........................ 34 
Figura 12─ Estrutura básica do padrão Singleton em código escrito em C# .......................... 36 
Figura 13 ─ Estrutura básica do padrão Observer em UML .................................................... 38 
Figura 14 ─ Estrutura da separação das responsabilidades da classe ControleDoPersonagem 
retirando as responsabilidades de verificar inputs, mover para direita e pular após a aplicação 
do Princípio da Responsabilidade única SRP. ......................................................................... 42 
Figura 15 ─ Estrutura da separação das responsabilidades da classe ControleDoPersonagem 
retirando as responsabilidades de verificar inputs, mover para direita e pular após a aplicação 
do Princípio da inversão de dependências DIP ....................................................................... 43 
Figura 16 ─ Estrutura do sistema de score e itens implementado com o padrão Observer ..... 44 
Figura 17 ─ Implementação do evento que notifica interessados em quando um item é 
coletado ..................................................................................................................................... 44 
Figura 18 ─ Código do dash acoplado ao código do salto do personagem tornando o design 
Viscoso, Rígido, Frágil e Opaco. .............................................................................................. 45 
Figura 19 ─ Diagrama de classes UML que mostra a estrutura da funcionalidade dash 
implementada............................................................................................................................ 46 
Figura 20 ─ Implementação da primeira versão das funcionalidades da câmera e interfaces de 
início e fim de jogo na classe ControleDaCâmera ................................................................... 47 
Figura 21 ─ Refatoração da estrutura que verifica inputs do jogador para aplicar o padrão 
Observer ................................................................................................................................... 48 
 
 
Figura 22 ─ Implementação da classe CameraController que é uma classe interessada em 
saber sobre o evento de primeiro input do jogador .................................................................. 49 
Figura 23 ─ Implementação da classe PlayerLife que exibe a interface de fim de jogo quando 
o personagem sai do enquadramento da câmera. ..................................................................... 50 
Figura 24 ─ Implementação da classe Obstaculo na primeira versão ...................................... 51 
Figura 25 ─ Trecho que mostra parte das modificações na classe 
PlayerCollisionDetectionAndPenality...................................................................................... 52 
Figura 26 ─ Diagrama de classes da estrutura que notifica quando o personagem “morre” para 
os ouvintes CameraController e PlayerControls ..................................................................... 53 
 
 
 
LISTA DE TABELAS 
 
Tabela 1 ─ Funcionalidades do jogo em sua versão inicial ...................................................... 23 
Tabela 2 ─ Linhas de código (LOC) da primeira versão. ......................................................... 53 
Tabela 3 ─ Linhas de código (LOC) da versão final. ............................................................... 54 
Tabela 4 ─ Complexidade Ciclomática (CC) das classes da primeira versão. ......................... 55 
Tabela 5 ─ Complexidade Ciclomática (CC) das classes da versão final. ............................... 55 
 
 
 
 
 
LISTA DE ABREVIATURAS E SIGLAS 
 
DIP Inversão de Dependência 
ISP Princípio da Segregação de Interfaces 
LSP Princípio de Substituição de Liskov 
OCP Princípio do Aberto/Fechado 
PACCE Programa de Aprendizagem Cooperativa em Células Estudantis 
SRP Princípio da Responsabilidade Única 
UML Unifield Modeling Language 
XML Extensible Markup Language 
LOC Linhas de Código 
CC Complexidade Ciclomática 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
SUMÁRIO 
 
1 INTRODUÇÃO ................................................................................................................... 15 
2 TRABALHOS RELACIONADOS .................................................................................... 16 
3 FUNDAMENTAÇÃO TEÓRICA ...................................................................................... 19 
3.1 Refatoração ....................................................................................................................... 19 
3.3 Sobre o jogo Bicho UFC Rampage .................................................................................. 20 
3.3.1 Sobre a primeira versão .................................................................................................. 21 
3.3.2 Estado da primeira versão .............................................................................................. 21 
3.3.3 Problemas encontrados na primeira versão .................................................................. 21 
3.2.1 Maus cheiros de design .................................................................................................. 25 
3.2.1.1 Rigidez .......................................................................................................................... 25 
3.2.1.2 Fragilidade ................................................................................................................... 26 
3.2.1.3 Imobilidade ................................................................................................................... 26 
3.2.1.4 Viscosidade ................................................................................................................... 26 
3.2.1.5 Complexidade desnecessária ........................................................................................ 26 
3.2.1.6 Repetição desnecessária ............................................................................................... 27 
3.2.1.7 Opacidade ..................................................................................................................... 27 
3.2.2 Princípios SOLID ........................................................................................................... 27 
3.2.2.1 Princípio da responsabilidade única (SRP) ................................................................. 27 
3.2.2.2 Princípio do aberto/fechado (OCP) ............................................................................. 29 
3.2.2.3 Princípio de substituição de Liskov (LSP) ................................................................... 30 
3.2.2.4 Princípio da segregação de interfaces (ISP) ................................................................ 33 
3.2.2.5 Princípio da inversão de dependência (DIP) ............................................................... 35 
3.2.3 Padrões de projeto de software ....................................................................................... 35 
3.2.3.1 Singleton ....................................................................................................................... 35 
3.2.3.2 Template Method .......................................................................................................... 36 
3.2.3.3 Strategy ......................................................................................................................... 36 
3.2.2.4 Observer ....................................................................................................................... 37 
4 METODOLOGIA ................................................................................................................ 38 
4.1 Revisão bibliográfica ........................................................................................................ 38 
4.2 Avaliação da primeira versão ..........................................................................................39 
4.3 Refatoração do código do projeto ................................................................................... 39 
4.4 Análise estática de código usando NDepend .................................................................. 40 
5 DESENVOLVIMENTO ...................................................................................................... 40 
 
 
5.1 Refatoração ....................................................................................................................... 40 
5.1.1 Separando as responsabilidades e invertendo dependências da classe 
ControleDoPersonagem .......................................................................................................... 41 
5.1.2 Mudando a forma como a contagem de pontos é feita usando o padrão Observer ..... 43 
5.1.3 Separação da funcionalidade Dash da classe ControleDoPersonagem, inversão de 
dependências e organização em camadas ............................................................................... 45 
5.1.4 Dinâmica de movimento da câmera e fim de jogo......................................................... 46 
5.1.5 Penalidade de colisão com objetos da cena, bloqueio dos controles do personagem e 
parar a câmera depois do fim de jogo ..................................................................................... 50 
6 COMPARAÇÃO ENTRE A PEIMEIRA E ÚLTIMA VERSÃO .................................. 53 
7 CONCLUSÃO ...................................................................................................................... 56 
REFERÊNCIAS ..................................................................................................................... 59 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15 
1 INTRODUÇÃO 
 
Desenvolver jogos é uma tarefa complexa. Complexidade esta que se dá pelas diversas áreas 
envolvidas na produção como programação, design, arte, cinema e música. Segundo 
(PARVIAINEN, 2017), da perspectiva de desenvolvedor, é difícil escrever um código de 
qualidade que torne fácil a adaptação aos requisitos em constante mudança, sendo 
manutenível, extensível, testável e capaz de evoluir durante a produção. 
Entretanto, a engenharia de software para o desenvolvimento de produtos tradicionais 
já evoluiu bastante na proposição de soluções que melhoram o projeto e a qualidade do 
software desenvolvidos. São exemplos dessas iniciativas a definição de padrões de projeto e 
princípios de design de código SOLID (MARTIN, R.; MARTIN, M., 2006) e os padrões 
classificados pela gangue dos quatro (GAMMA, et al., 1994). Em jogos, algumas dessas boas 
práticas da engenharia de software já vêm sendo aplicadas e surgem adaptações dos princípios 
SOLID para esse domínio. 
Este trabalho tem como principal objetivo realizar a refatoração do jogo Bicho UFC 
Rampage aplicando boas práticas da engenharia de software que são, neste caso, o uso dos 
padrões de projeto descritos no catálogo de (GAMMA, et al., 1994) e princípios de design de 
código SOLID (MARTIN, R.; MARTIN, M., 2006) no ambiente de desenvolvimento de jogos 
Unity1. O objetivo é melhorar a qualidade do código para que o projeto se torne mais fácil de 
manter. Para realizar essa tarefa, este trabalho utilizará pequenas etapas de refatoração de 
código adaptando esses conceitos para o ambiente Unity. 
E para comparar a versão inicial e a versão final, foi feita uma análise estática de 
código usando a ferramenta NDepend2. As métricas coletadas foram Linhas de código (LOC), 
Complexidade Ciclomática (CC) e Dependência. Porém a medida de dependência foi excluída 
por conta de falsos positivos. Esses falsos positivos ocorreram por conta que a Unity não 
trabalha bem com o uso de interfaces e em diversos pontos ainda são necessárias chamadas a 
implementações concretas de classes. 
O tema não é novo e trabalhos como os de (PARVIAINEN, 2017) e (FIGUEIREDO e 
RAMALHO, 2015) já trataram de aplicar princípios SOLID e padrões de projeto para jogos. 
A principal diferença do trabalho apresentado aqui é o jogo e a escolha sobre que padrões 
aplicar. 
O público alvo deste trabalho são, principalmente, desenvolvedores de jogos que usam 
a engine Unity e desejam expandir ou aperfeiçoar seus conhecimentos com boas práticas de 
engenharia de software como refatoração, princípios SOLID e padrões de projeto de software. 
 
1 https://unity.com/pt 
2 https://www.ndepend.com/ 
 
16 
 Este trabalho está dividido da seguinte forma, a Seção 2 trata de apresentar os 
trabalhos com temas semelhantes relacionados. A Seção 3 e suas subseções tratam de 
apresentar os conceitos teóricos base deste trabalho que são refatoração, maus cheiros de 
design, princípios SOLID, padrões de projeto e sobre o jogo Bicho UFC Rampage. A Seção 3 
apresenta os passos de execução deste trabalho apresentando o estado inicial do projeto, 
problemas encontrados na versão inicial, cada uma das etapas de refatoração e uma 
comparação entre a versão inicial e a final com medidas LOC e CC usando a ferramenta 
NDepend. 
 
1.1 Objetivos 
 
Partindo do pressuposto de que é possível se construir uma estrutura de código melhor a partir 
de uma estrutura ruim. E usando pequenas modificações no código junto de padrões que 
representam o conhecimento prévio de outros desenvolvedores. E usando regras de design de 
código. O objetivo principal deste trabalho é a refatoração do jogo Bicho UFC Rampage 
aplicando padrões de projeto e princípios SOLID. 
 A aplicação desse objetivo principal se dá pelos seguintes objetivos secundários: 
• Identificar maus cheiros de design na primeira versão do jogo; 
• Aplicar princípios SOLID onde existem maus cheiros de design usando refatoração; 
• Aplicar padrões de projeto durante a refatoração; 
Com isso, é esperado que a estrutura do código do projeto melhore para que novas 
modificações possam ser feitas com menos dificuldade. 
 
2 TRABALHOS RELACIONADOS 
 
Nesta seção serão apresentados os principais trabalhos relacionados encontrados durante a 
revisão bibliográfica feita buscando outros trabalhos que combinassem os temas de 
refatoração, princípios SOLID e padrões de projeto. 
 
2.1 Dependency Injection in Unity3D 
 
Em (PARVIAINEN, 2017), o autor tem como principal objetivo identificar e resolver 
problemas técnicos relacionados ao ambiente Unity. Da perspectiva de desenvolvedor, o 
trabalho identifica os problemas técnicos que estão relacionados principalmente à gerência de 
dependências no desenvolvimento de jogos focando o ambiente Unity. 
 
17 
A gerência de dependências na Unity é apresentada como um problema por conta de a 
plataforma não oferecer opções eficazes para controlar dependências. Como padrão, o 
framework oferece métodos de busca de dependências como GameObject.Find e 
Object.FindObjectOfType. Outra forma oferecida é através do Editor da Unity que oferece a 
possibilidade de realizar drag and drop de instâncias, mas essa funcionalidade está limitada a 
apenas instâncias de objetos da Unity e não é possível usar de abstrações como interfaces 
(PARVIAINEN, 2017). 
 Outro problema apresentado é que a engine não oferece um ponto de entrada único 
para a aplicação, o que torna a gerência de dependências mais difícil e dificultando o 
desenvolvedor controlar o que é instanciado (PARVIAINEN, 2017). 
 Como possível solução e melhor abordagem da gerência de dependências na Unity, o 
padrão Singleton é apresentado. Dessa forma, não é necessário o uso dos métodos 
GameObject.Find e Object.FindObjectOfType (PARVIAINEN, 2017). Porém é um padrão 
que deve ser usado com cautela por conta que com ele é difícil controlar estados e usar testes 
unitários. A Seção 2.2.3.1 Singleton descreve esse padrão. 
 Outra forma de gerenciar dependências apresentada, é o uso do framework Zenject3. 
Esse framework aplica o padrão Dependeny Injection (DI). Esse padrão é usado para 
gerenciar as dependências para que o código se torne maismodular. Dessa forma, objetos não 
instanciam suas dependências nem buscam por elas, o framework é o responsável por prover 
essas dependências. Vários benefícios são apresentados como a diminuição do acoplamento 
entre módulos, facilidade em realizar testes e mocks e late bindings. Também são 
apresentadas desvantagens no uso desse padrão. O primeiro é que com o uso de DI, em 
projetos grandes, são criados grafos de dependência complexos que são gerenciados 
manualmente. E o segundo problema é que não há controle em relação em como as instâncias 
de objetos Unity são criadas e não existe um pronto de entrada para a aplicação 
(PARVIAINEN, 2017). 
 O trabalho também apresenta princípios SOLID como forma de criar um bom design 
de código. Cada princípio é apresentado partindo de um exemplo que não aplica o princípio 
para um exemplo que aplica (PARVIAINEN, 2017). 
E por fim, é apresentado um pequeno projeto de teste que usa os conceitos de 
fundamentação teórica apresentados. O design da ideia é apresentado junto das tecnologias 
 
3 https://github.com/modesttree/Zenject 
 
18 
usadas e detalhes da implementação são apresentados (PARVIAINEN, 2017). 
A principal diferença entre (PARVIAINEN, 2017) e este trabalho é que este trabalho 
não apresenta o uso do padrão Dependency Injection (DI) com o uso do framework Zenject e 
nem cria um projeto de teste. Neste trabalho são apresentados os princípios SOLID e alguns 
padrões de projeto comportamentais usados na refatoração do jogo Bicho UFC Rampage. 
 
2.2 Gof design patterns applied to the development of digital games 
 
Outro trabalho semelhante é o (FIGUEIREDO e RAMALHO, 2015), onde são abordados os 
usos de padrões GOF para aumentar a capacidade de reuso de componentes, apesar do uso 
limitado que essa ferramenta de desenvolvimento tem dentro do desenvolvimento de jogos 
com engines. 
O trabalho propõe apresentar as melhorias alcançadas com o uso de padrões de projeto 
através de um antes e depois da aplicação de padrões GOF. Esses benefícios vêm por conta 
que os padrões são uma forma de difusão de conhecimento. Esse conhecimento já foi testado 
previamente por outros desenvolvedores em outros problemas com o mesmo contexto. E por 
conta disso, sua aplicação por si só é uma forma de documentação. E torna a comunicação do 
time mais simples (FIGUEIREDO e RAMALHO, 2015). 
No trabalho, são explicados apenas uma pequena gama de padrões por conta da 
limitação de páginas. Os padrões apresentados são GOF (GAMMA, et al., 1994) com 
adaptações para o desenvolvimento de jogos digitais. Os padrões descritos são Builder, 
Prototype, Singleton, Flywheight, Observer e State. 
Para demonstrar o impacto do uso de padrões de projeto, um experimento foi 
conduzido com estudantes de computação. Os estudantes foram divididos em 6 grupos de três 
pessoas cada. Foi aplicado um teste AB comparando os grupos que usaram padrões de projeto 
em relação aos grupos que não usaram. Penas três padrões foram usados por conta de 
limitações de tempo. Esses padrões foram Singleton, Prototype e Facade. Esse experimento, 
foi realizado com o objetivo de verificar se o uso de padrões de projeto reduzia o tempo de 
desenvolvimento, diminui a presença de bugs e reduz a quantidade de linhas de código 
(FIGUEIREDO e RAMALHO, 2015). 
O tempo de desenvolvimento dos grupos que usaram padrões foi de 06:31, enquanto o 
tempo dos que não usaram foi de 08:02. Todos os times conseguiram completar a tarefa. 
Todos os grupos que não usaram padrões apresentaram bugs, enquanto apenas um dos que 
usaram padrões apresentou um bug. Em relação a quantidade de linhas de código, os grupos 
 
19 
que usaram padrões tiveram uma contagem de 717, enquanto os que não usaram tiveram uma 
contagem de 855 linhas. Em relação a quantidade de classes, os grupos que usaram padrões 
tiveram uma contagem de 23 classes, enquanto o grupo que não usou teve uma contagem de 7 
classes. Por fim, o ganho de tempo dos grupos que usaram padrões foi de 18,9% e o ganho de 
linhas de código foi de 16,14% em relação aos grupos que não usaram (FIGUEIREDO e 
RAMALHO, 2015). 
As diferenças entre (FIGUEIREDO e RAMALHO, 2015) e este trabalho é que em 
(FIGUEIREDO e RAMALHO, 2015) foi abordado um leque maior de padrões GOF, mesmo 
essa quantidade sendo limitada por conta da contagem de páginas. E porque foram usados 
padrões arquiteturais enquanto este trabalho trata em sua maioria de comportamentais. Outra 
diferença foi a forma de validação. Neste trabalho não foram conduzidos experimentos, mas 
sim uma pequena análise estática de código usando NDepend. 
 
3 FUNDAMENTAÇÃO TEÓRICA 
 
A execução deste trabalho tem como base refatoração, princípios SOLID e padrões de projeto 
para a melhoria do design do código do jogo Bicho UFC Rampage. Os demais tópicos e 
subtópicos irão fundamentar o jogo seguido por essas três áreas bases deste trabalho 
apresentado exemplos que não fazem parte da solução final, mas que ilustram de forma 
simples a aplicação desses conceitos. 
 
3.1 Refatoração 
 
Durante boa parte da história do desenvolvimento de software muitos acreditavam que o 
design deveria preceder a implementação. Essa seria uma abordagem em cascata 
(SOMMERVILLE, 2011) onde a etapa de design iria preceder e alimentar a etapa seguinte, a 
implementação. Porém, existe uma diferença entre modelar e implementar um software. Uma 
modelagem é uma maneira abstrata de imaginar o software enquanto a implementação é o 
software concreto. Então, à medida que o design fosse implementado, detalhes antes não 
planejados seriam identificados e seriam indícios da necessidade de melhorar o design 
imaginado no início. À medida que a implementação avança, o código se torna decadente de 
maneira que o a implementação vai da engenharia para hacking (FOWLER, 2009). 
 A abordagem da refatoração segue uma ideia oposta à ideia da degradação que o 
software sofreria em uma abordagem cascata. A partir de um design ruim, transformar esse 
 
20 
código ruim implementado em um código bem estruturado. Fazendo isso seguindo um passo-
a-passo simples onde, por exemplo alguns dos passos seriam: mover uma propriedade de uma 
classe para outra, transformar uma porção de código de um método em um novo método, 
deslocar código para cima ou para baixo em uma hierarquia de classes etc. Dessa forma, o 
design do código pode melhorar drasticamente. Essa abordagem une as o que antes seriam 
duas etapas distintas que antes eram separadas. Essa união faz com que o design e a 
implementação passem a se comunicar de forma bidirecional, diferente do canal unidirecional 
entre as duas etapas no modo cascata (FOWLER, 2009). 
 O termo refatoração tem duas interpretações. Uma para a forma substantiva e outra 
para a forma verbal. No sentido da primeira, refatorar é “uma alteração feita na estrutura 
interna do software para torná-lo mais fácil de ser entendido e menos custoso de ser 
modificado sem alterar seu comportamento observável” (FOWLER, 2009, p. 52). No segundo 
sentido, a palavra refatorar se refere a uma ação que visa “reestruturar o software aplicando 
uma série de refatorações sem alterar seu comportamento observável” (FOWLER, 2009, p. 
52). De forma geral, refatorar é modificar a estrutura interna do software sem alterar o que ele 
já faz (FOWLER, 2009). 
 A partir dessas definições, é possível concluir que refatoração não é algo que impacte 
nas funcionalidades do software. O impacto acontece em relação a projeto tornando o código 
mais fácil de compreender e menos custoso de alterar. Com um código bem estruturado, é 
mais fácil realizar modificações. 
 Neste trabalho, apesar de se tratar da refatoração do código de um projeto, não serão 
destacados cada técnica e indícios de necessidade de refatoração. O foco será mantido nas 
questões relacionadas a SOLID e padrões de projeto já que essas duas técnicas estão 
entrelaçadas e já guiam o desenvolvimento paraum design de qualidade, porém, durante o 
texto, existem alguns apontamentos que podem remeter a algumas técnicas de refatoração. 
 
3.3 Sobre o jogo Bicho UFC Rampage 
 
O jogo Bicho UFC Rampage foi desenvolvido em 2017 durante a Célula de Desenvolvimento 
de jogos do PACCE na UFC no campus de Quixadá, CE. O jogo se trata de um projeto autoral 
inspirado em dois jogos antigos que já não se encontram disponíveis, Leo's Red Carpet 
Rampage e Super Impeachment Rampage. Nesse jogo o jogador controla um aluno novato na 
universidade que precisa realizar suas atividades e fugir dos inimigos que são os alunos 
veteranos. E durante essa fuga, ele deve ser rápido e coletar o máximo de itens que puder no 
 
21 
trajeto. 
 
3.3.1 Sobre a primeira versão 
 
A primeira versão do jogo foi desenvolvida usando Unity como engine e GIT4 como 
ferramenta de versionamento. O GitHub5 foi usado como repositório remoto para o projeto. 
Não foi usado nenhum processo de desenvolvimento e cada componente foi implementado 
durante encontros da Célula de desenvolvimento de jogos durante 4 meses. Nesse período, 
cada encontro acontecia uma vez por semana durante 2 horas. Os responsáveis pelo projeto se 
dividiam em tarefas de programação e criação de assets. 
 Durante o desenvolvimento dessa primeira versão, não foram gerados diagramas. 
Apenas um documento de design detalhando o jogo e suas funcionalidades com base no texto 
de (CHANDLER, 2009). Nesse documento foi especificado o gancho de jogo, a proposta de 
jogos, as mecânicas, condições de vitória e derrota e uma breve história para contextualizar o 
jogo. 
 
3.3.2 Estado da primeira versão 
 
O estado inicial do projeto está retratado no diagrama UML da Figura 1 criado usando 
engenharia reversa. Para essa criação foram usados duas ferramentas e um plug-in. A principal 
ferramenta usada para representar o diagrama UML foi o Astah UML6 com uma licença para 
estudante. Então, a segunda ferramenta usada foi o Doxygen7 que é uma ferramenta que 
transforma código em XML. Por fim, o plugin da ferramenta Astah UML, o C# Code Reverse 
Plug-in8 foi usado para transformar o XML em um diagrama UML de classes. 
 
3.3.3 Problemas encontrados na primeira versão 
 
 
4 https://git-scm.com/ 
5 https://github.com/ 
6 https://astah.net/products/astah-uml/ 
7 https://www.doxygen.nl/index.html 
8 https://astah.net/product-plugins/csharp-reverse/ 
 
22 
A partir da análise do código e do diagrama da Figura 1, é possível reparar em alguns dados 
importantes. O primeiro, existe um total de 3 classes que aplicam o padrão Singleton, 
discutido na seção 2.2.3.1 Singleton. Os problemas são a dificuldade de controlar os estados 
que uma classe que implementa o padrão está, e a dificuldade de realizar testes unitários com 
esse tipo de padrão. Outro dado importante, as duas maiores classes são 
ControleDoPersonagem e ControleDaCamera, e são essas classes que carregam as principais 
funcionalidades do jogo. 
 A classe ControleDoPersonagem implementa a maioria das funcionalidades. Essas 
responsabilidades são: verificar os inputs do jogador, realizar o movimento para direita 
alternando teclas, realizar o salto do personagem, contar a quantidade de itens e realizar o 
dash, aplicar a penalidade de colisão. São no total 4 responsabilidades de podem ser 
decompostas em mais componentes. Isso evidencia os maus cheiros de Rigidez, pois uma 
simples mudança pode causar uma cascata de modificações, Fragilidade, já que o código pode 
quebrar em diversos pontos. O código também é Viscoso, porque existem muitas “gambiarras” 
e modificações podem gerar mais “gambiarras”. O código também apresenta o mau cheiro de 
Opacidade porque o código está muito difícil de compreender. 
 A classe ControleDeCamera, a segunda maior classe, controla elementos de interface 
além do que o próprio nome propõe, a tornado Frágil e Rígida. Ela também é difícil de 
compreender e seu design cheira a Opacidade. Essa classe também apresenta Repetição 
Desnecessária porque verifica os inputs do jogador assim como outras classes. 
 A classe ControleDeTempo apresenta Repetição Desnecessária por estar verificando 
inputs do jogador assim como outras classes. 
 Esses são os principais problemas identificados na versão inicial do projeto. O 
próximo passo deste trabalho envolve descrever como esses problemas foram resolvidos ou 
amenizados através da aplicação dos princípios SOLID e padrões de projeto. Não será 
retratado o processo de refatoração de forma detalhada, apenas serão apresentadas as soluções 
explicando brevemente como e por que se chegou na solução. Também não serão usados 
testes unitários. 
 
 
 
 
 
 
 
23 
Tabela 1 ─ Funcionalidades do jogo em sua versão inicial 
 
Funcionalidades 
O personagem deve se mover constantemente para direita assim que o primeiro 
input do jogador seja emitido. Enquanto esse input não é emitido, o jogo deve 
exibir uma tela de início da fase. 
O jogador coleta itens ao longo das fases que contam pontos no score total de 
pontos do jogador. 
A câmera deve se mover constantemente para direita assim que o jogador emitir 
seu primeiro input. Caso o personagem do jogador fique fora do enquadramento 
da câmera durante a fase, o personagem morre e o jogo deve ser reiniciado. 
O tempo que o jogador leva para concluir a fase deve ser contado a partir do 
momento que o primeiro input do jogador é emitido e deve parar de contar assim 
que o personagem atinge o final da fase. 
Após o jogador coletar 5 itens, o dash do personagem deve ser liberado para uso. 
Esse dash é um aumento de velocidade que dura por um curto período. Assim que 
o dash for usado, o contador deve ser zerado. 
O jogador move o personagem para a direita pressionando alternadamente as 
teclas “a” e “d”, e para que o personagem pule, a tecla “espaço” deve ser 
pressionada. Não deve ser possível que sejam realizados saltos enquanto o 
personagem está no ar. 
Caso o jogador colida com algum obstáculo, o personagem recebe uma pequena 
penalização de 0.7 segundos. Nesse tempo, o personagem tem seus controles 
bloqueados e se movo lentamente para esquerda. 
A HUD do jogo deve exibir as seguintes informações para o jogador: vida do 
personagem, contador de itens para o dash ficar disponível e o total de pontos do 
jogador. 
Fonte: Autor. 
 
 
 
 
 
 
 
24 
Figura 1 ─ Diagrama de classes UML que representa o estado inicial do projeto 
 
 
Fonte: Autor. 
O jogo consiste em controlar o personagem pressionando alternadamente as teclas “a” 
e “d” para que o personagem se mova e não saia do enquadramento da câmera. O jogador 
também pode pular obstáculos pressionando a tecla “espaço”. Caso o jogador saia do 
enquadramento da câmera, o personagem morre e a fase deve ser reiniciada. O jogador deve 
coletar os itens espalhados pelas fases para aumentar sua pontuação de escore e deve se mover 
o mais rápido possível para que seu tempo durante o percurso da fase seja o menor possível. 
Essas funcionalidades estão representadas na Tabela 1 que lista uma descrição simplificada de 
cada funcionalidade. 
 
3.2 Princípios SOLID e maus cheiros de design 
 
Em um projeto de software, principalmente em projetos ágeis, a ideia geral do projeto evolui 
durante o desenvolvimento. O código que é implementado não é criado para antecipar 
features que podem ser necessárias no futuro. Ao contrário, os desenvolvedores focam na 
estrutura atual do sistema fazendo-o o melhor que possa ser. Dessa forma o software evolui de 
forma incremental até atingir a sua arquitetura e design ideal sem que esforço e custo sejam 
desperdiçados com possíveis features que não se sabe se serão realmente necessárias 
 
25 
futuramente (MARTIN, R.; MARTIN, M., 2006). 
 Para que o código do projeto evolua sendo construído da melhor forma possível, os 
desenvolvedores precisam de uma base para firmar suas decisões de design. Para isso, existemprincípios que servem como guias para o design do código. Os princípios abordados neste 
trabalho são SOLID, um acrônimo que nomeia um conjunto de cinco princípios para criar 
estruturas de nível médio que: tolerem mudanças; sejam fáceis de entender e; sejam a base de 
componentes que possam ser usados em muitos sistemas de software (MARTIN, 2019). 
Entretanto, os princípios não devem ser aplicados sem justificativa, ou complexidade 
desnecessária pode ser adicionada ao código do projeto tornando-o difícil de manter. Para isso, 
existem certos sintomas de maus cheiros que são usados como indicadores de que o código 
está em desacordo com um ou mais princípios. Esses maus cheiros diferem dos maus cheiros 
de código por conta de estarem relacionados ao design, e consequentemente estão em um 
nível de abstração mais alto (MARTIN, R.; MARTIN, M., 2006). 
A seguir, primeiro serão apresentados os maus cheiros que indicam a necessidade de 
refatoração do código para que ele fique de acordo com um ou mais princípios. Em seguida 
cada um dos princípios será apresentado. 
 
3.2.1 Maus cheiros de design 
 
Usamos princípios para guiar o código a um bom design, entretanto, um bom design não usa 
desses princípios de forma descontrolada e impensada. Então, uma boa prática é aplicar os 
princípios apenas onde é possível identificar maus cheiros de design. Esses maus cheiros 
estão descritos nas subseções abaixo. 
A refatoração também abre espaço para a aplicação de Padrões de projeto, tratados na 
seção 3.2.3 Padrões de projeto de software. Usar padrões significa usar conhecimento prévio 
de outros desenvolvedores que resolveram problemas com contextos semelhantes. E eles 
foram criados tentando usar os princípios de orientação a objetos da melhor forma. Além de 
melhorar a comunicação entre os desenvolvedores. E estarem de acordo com os princípios 
SOLID (FIGUEIREDO, 2015). 
 
3.2.1.1 Rigidez 
 
Rigidez é a tendência para um software de ser difícil de modificar mesmo em modificações 
simples. Então se uma simples mudança causa uma cascata de outras modificações em 
 
26 
módulos dependentes, o design apresenta o mau cheiro de Rigidez. E quanto mais mudanças 
forem necessárias, mais rígido o design é (MARTIN, R.; MARTIN, M., 2006). 
 
3.2.1.2 Fragilidade 
 
Fragilidade é a tendência de o software quebrar em vários lugares quando uma simples 
mudança é feita. Frequentemente essas quebras acontecem em módulos que não tem relação 
conceitual com o módulo onde foi feita a mudança. Ou seja, módulos que deveriam ser 
independentes, são completamente dependentes de forma que uma mudança em um módulo 
quebra os demais (MARTIN, R.; MARTIN, M., 2006). 
 
3.2.1.3 Imobilidade 
 
Imobilidade é a incapacidade de reaproveitamento de módulos de software que podem ser 
úteis em outros sistemas, mas que seu reuso é impossível por conta dos riscos envolvidos em 
separar o dito módulo de seu sistema original (MARTIN, R.; MARTIN, M., 2006). 
 
3.2.1.4 Viscosidade 
 
A viscosidade pode acontecer tanto em software quanto em relação ao ambiente. E acontece 
quando uma mudança tem mais de uma maneira de ser feita, e as maneiras que preservam o 
design são mais difíceis do que as maneiras que criam “gambiarras”. Dessa forma o design 
cheira a Viscosidade. A Viscosidade em relação ao ambiente acontece quando o ambiente de 
desenvolvimento é lento e ineficiente. Em ambos os casos a Viscosidade é alta quando o 
design é mais difícil do que o uso de “gambiarras” (MARTIN, R.; MARTIN, M., 2006). 
 
3.2.1.5 Complexidade desnecessária 
 
Um design cheira a Complexidade desnecessária quando o código carrega recursos que não 
estão sendo usados. Acontece frequentemente quando os desenvolvedores adicionam 
facilidades no código visando futuras mudanças. Porém, isso é desperdício de esforço e custo 
já que essas facilidades podem não ser necessárias futuramente (MARTIN, R.; MARTIN, M., 
2006). 
 
 
27 
3.2.1.6 Repetição desnecessária 
 
A repetição desnecessária acontece quando o mesmo trecho de código aparece repetidas vezes, 
de formas levemente diferentes, em diversas partes do código. Esse é um forte indício que os 
desenvolvedores estão fazendo mau uso de abstrações. Dessa forma, o trabalho de realizar 
mudanças pode ser muito oneroso por conta que os trechos de código semelhante podem 
necessitar de modificação também (MARTIN, R.; MARTIN, M., 2006). 
 
3.2.1.7 Opacidade 
 
O código evolui ao decorrer do tempo e essa evolução torna o código cada vez mais difícil de 
entender. Quando um módulo se torna muito difícil de entender, o design cheira a Opacidade 
(MARTIN, R..; MARTIN, M., 2006). 
 
3.2.2 Princípios SOLID 
 
SOLID é um acrônimo para um conjunto de princípios para design de código. Esses 
princípios são o guia para criar estruturas de código que: tolerem mudanças; sejam fáceis de 
entender e; sejam a base de componentes que possam ser usados em muitos sistemas de 
software. Entretanto, o uso desses princípios deve ser feito apenas quando modificações são 
necessárias e as necessidades dessas modificações evidenciem maus cheiros presentes no 
design do código (MARTIN, 2019). 
3.2.2.1 Princípio da responsabilidade única (SRP) 
 
O SRP aponta que “uma classe deve ter apenas uma razão para mudar”. Esse princípio se 
baseia na ideia de que cada responsabilidade é um único eixo de mudança. Ou seja, quando 
um requisito muda, apenas os eixos de responsabilidade atrelados a esse requisito e essa 
mudança que devem sofrer alteração (MARTIN, R.; MARTIN, M., 2006). 
 Então, como exemplo, uma classe Player que carrega duas responsabilidades. Uma de 
conectar com o servidor e outra de tratar da comunicação com o servidor, como retrata a 
Figura 1. Agora imagine que a lógica como a conexão acontece precisa mudar. Nesse caso, os 
métodos Connect e Disconnect, contidos em Player devem ser modificados. Porém, 
conceitualmente, é claro que a classe Player não deveria ser modificada por conta de uma 
mudança relacionada a conexão, pois como seu próprio nome sugere, a classe deveria tratar 
 
28 
apenas de funcionalidades relacionadas ao personagem que o jogador controla. Isso mostra 
que a classe tem dois eixos de mudança. 
 
Figura 2 ─ Classe em desacordo com o princípio SRP 
 
 
Fonte: Autor. 
 
 Uma possível solução para este problema seria a separação a responsabilidade de tratar 
da conexão para uma classe separada. Isso se trata da extração de dois métodos para uma nova 
classe chamada Connection. Dessa forma, qualquer mudança que precise ser feita em como a 
conexão acontece deve ser modificado apenas na classe Connection. A solução está 
representada na Figura 2. 
 
Figura 3 ─ Classe em acordo com o princípio SRP 
 
 
Fonte: Autor. 
 
Designs de código que têm classes que carregam mais de uma responsabilidade 
cheiram a Fragilidade. Perceba que a modificação pode quebrar tanto em relação as 
funcionalidades do personagem, quanto em relação a conexão. 
 Em Martin (2019) são apresentados conceitos mais concisos a respeito da definição de 
SRP. A nova definição é “um módulo deve ser responsável apenas por um, e apenas um, ator”. 
Essa “redefinição” deixa claro que um eixo de mudança está mais relacionado com um ator do 
que diretamente com um requisito. Isso porque as mudanças nos requisitos são necessárias 
quando as necessidades dos stakeholders envolvidos no projeto mudam. 
 
29 
 
3.2.2.2 Princípio do aberto/fechado (OCP) 
 
Segundo a definição de OCP “Um artefato de software deve ser aberto para extensão, mas 
fechado para modificações” (MARTIN, 2019, p. 70). Então, os artefatos devem estar prontos 
para sofrer mudanças ou extensões em seu comportamento sem que o código antigo seja 
modificado (MARTIN, 2019). 
 Partindo da definição, artefatos que estão de acordo com OCP apresentam duas 
características. A primeira, é que são abertos a extensão. Então, o comportamento do artefato 
deve ser fácilde estender alterando seu comportamento. A segunda característica é que o 
artefato deve ser fechado para modificação, ou seja, estender o comportamento não deve 
resultar em modificações ao código fonte, módulo ou binário (MARTIN, 2019). 
 Artefatos que não estão de acordo com OCP tendem a sofrer uma cascata de mudanças 
em módulos dependentes quando uma simples modificação é feita. Essa cascata de 
modificações decorrentes de uma simples mudança, indica que o design tem o mau cheiro de 
Rigidez (MARTIN, R.; MARTIN, M., 2006). 
 Para que esse isolamento do que já foi desenvolvido em relação a extensões é 
alcançado através do uso de abstrações. Quando os artefatos se isolam de outros artefatos 
através de abstrações(contratos), as modificações acontecem na implementação dessas 
abstrações sem que o contrato seja desrespeitado. Dessa forma modificações não causam 
impactos em dependentes (MARTIN, 2019). 
 Como exemplo, imagine que temos um jogador que controla um personagem que usa 
uma pistola no jogo. A Figura 3 mostra essa funcionalidade com as classes Player e Pistol. 
Agora, supondo que o projeto do jogo evoluiu e a possibilidade de o personagem usar uma 
pistola mudou para que agora o jogador possa escolher entre duas armas, uma pistola e uma 
calibre 12. A classe Pistol precisa mudar e essa mudança impacta diretamente a classe Player. 
Isso acontece porque a classe Player depende diretamente de uma implementação de Pistol. 
 
 
Figura 4 ─ Exemplo da implementação de um personagem que usa uma pistola 
 
 
Fonte: Autor. 
 
30 
 Para que a classe Player esteja protegida das mudanças que acontecem e ele usar uma 
arma independente de qual seja, a classe Player deve deixar de depender diretamente de uma 
classe concreta. Para isso, a dependência de Player muda para depender de uma interface. 
Dessa forma Player passa a depender de uma abstração e qualquer arma que implemente a 
interface IGun pode satisfazer essa dependência, como mostra a Figura 4. Dessa forma, 
Player agora está isolada de modificações relacionadas as armas que usa e ao mesmo tempo 
aberta a qualquer extensão que adicione uma nova arma ao jogo. 
 
Figura 5 ─ Exemplo do isolamento da classe Player das mudanças que acontecem em relação 
as armas 
 
Fonte: Autor. 
A troca da dependência de Player de uma classe concreta pela interface IGun foi a 
aplicação de um outro princípio, a Inversão de Dependência (DIP) tratada na seção 2.2.2.5. 
Essa inversão de dependência aplicou o padrão de projeto Strategy que é tratado na seção 
xxxx. 
 
3.2.2.3 Princípio de substituição de Liskov (LSP) 
 
O LSP é um princípio que guia o bom uso de heranças e até mesmo a implementação de 
interfaces. Sua definição é “Subtipos devem ser substituíveis pelos seus tipos bases” 
(MARTIN, R.; MARTIN, M., 2006, p. 136). 
 O mau uso de herança e implementação de abstrações são características que mostram 
quando um design está em desacordo com LSP. Esse tipo de artefato apresenta o mau cheiro 
de Fragilidade. Isso porque quando subtipos não são substituíveis por seus tipos base, o 
código tende a quebrar em diversas partes. 
 Por exemplo, considere um jogo que tem diferentes tipos de itens coletáveis, um de 
pontos de score e outro de vida para o personagem que o jogador controla. A Figura 5 mostra 
um trecho de código que mostra como a contagem de pontos acontece. E a Figura 6 mostra 
como os tipos e subtipos estão implementados. 
 
31 
Figura 6 ─ Classe Player responsável por realizar a atualização de pontos e de vida do 
jogador quando um item é coletado 
 
 
Fonte: Autor. 
 
Figura 7 ─ Implementação dos coletáveis mostrando a superclasse e subclasses 
 
 
Fonte: Autor. 
 
32 
 Suponha que o jogo agora tem um novo tipo de coletável para ser adicionado. O 
jogador poderá coletar vidas durante as fases do jogo. Essa necessidade de modificação irá 
causar impacto na classe Player com a adição de uma nova verificação if para o novo tipo de 
coletável. E será necessária a criação de um novo tipo que herda de CollectibleBase e a adição 
do novo tipo em CollectibleType. Isso mostra que o exemplo também está em desacordo com 
o Princípio de aberto/fechado (OCP) tratado na seção 2.2.2.2 e o design cheira a Rigidez já 
que essa modificação pode causar uma cascata de modificações em artefatos dependentes. E 
este design não está de acordo com LSP porque nenhum dos tipos derivados de 
CollectibleBase são substituíveis pelo seu tipo base. A substituição dos subtipos pelos tipos 
base resultaria em “gambiarras” e com isso o design iria cheirar a Fragilidade por conta da 
facilidade de o código quebrar em diferentes partes por conta desses hacks. 
 Uma possível solução seria criar uma abstração que represente qualquer coletável do 
jogo. Isso porque qualquer coletável no jogo incrementa ou decrementa um contador próprio. 
Para isso, a Figura 7 mostra a modificação feita no tipo CollectibleBase que define um 
método geral que qualquer subtipo deve implementar. Nessa modificação, as instâncias de 
tipos que controlam os contadores descem na hierarquia saindo da superclasse para as 
subclasses e o tipo CollectibleType não mais necessário. 
 
Figura 8 ─ Trecho da implementação dos coletáveis de acordo com LSP 
 
Fonte: Autor. 
 
 Agora, como mostra a Figura 8, qualquer subtipo de CollectibleBase é substituível 
pelo tipo base. A aplicação de LSP neste exemplo foi feita com a aplicação de um padrão de 
 
33 
projeto chamado Template Method que é discutido na seção 3.2.3.2 Template Method. 
Figura 9 ─ A Player usa qualquer coletável que seja subtipo de ColectibleBase 
 
 
Fonte: Autor. 
 
3.2.2.4 Princípio da segregação de interfaces (ISP) 
 
O ISP diz que “Clientes não devem ser forçados a implementar métodos que eles não usam” 
(MARTIN, R.; MARTIN, M., 2006, p. 166). Esse princípio lida com as desvantagens de lidar 
com interfaces que não são coesivas. Essas interfaces podem ser divididas em grupos de 
serviço para cada grupo de clientes tornando-as mais coesivas. 
 Classes que não tem interfaces coesivas não devem ser apresentadas aos clientes de 
forma concreta apresentando serviços que um cliente consome e outro não. Do contrário, 
clientes que consomem serviços diferentes podem acabar impactados por mudanças em 
serviços que eles não consomem. Para evitar esse problema, classes que apresentam serviços a 
diferentes grupos de cliente, devem apresentar apenas os serviços que cada grupo de clientes 
precisa. Para isso, cada grupo de clientes deve depender de uma interface da classe de serviço 
que expõe apenas o que esse grupo de clientes precisa consumir (MARTIN, R.; MARTIN, M., 
2006). 
 Como exemplo, imagine um jogo onde o personagem controlado pelo jogador precise 
mover para direita, esquerda e pular. E que a implementação dessas funcionalidades foi feita 
usando da composição de componentes. O componente Player identifica os inputs do jogador 
e delega o que deve ser feito para um outro componente que mantém as funcionalidades e 
mover e pular. A Figura 9 mostra o diagrama UML que representa essa estrutura em 
 
34 
desacordo com ISP. 
Figura 10 ─ Estrutura em desacordo com ISP 
 
 
Fonte: Autor. 
 
 Nesse exemplo, a classe PlayerInputs oferece serviços a vários clientes. E cada um 
desses clientes usa serviços diferentes. O problema com esse design é que mudanças em 
serviços relacionados a UI podem impactar no serviço do PlayerControls. Para que o exemplo 
esteja de acordo com ISP e mudanças não impactem clientes não relacionados, os serviços 
prestados a cada grupo de clientes devem ser separados através do uso de interfaces. A Figura 
10 mostra a solução. 
 
Figura 11 ─ Serviços para diferentes clientes separados através de interfaces 
 
 
Fonte: Autor. 
 Dessa forma os diferentes serviços que cada um dos clientes depende está separado 
 
35 
através de interfaces. Assim, o impacto em mudanças em serviçosnão relacionados menor. 
No exemplo da Figura 10, o não uso de ISP pode implicar em “gambiarras” para diminuir o 
impacto das mudanças tornando o código Viscoso. 
 
3.2.2.5 Princípio da inversão de dependência (DIP) 
 
Segundo DIP, sistemas flexíveis não tem dependências de código fonte, eles apenas se 
referem a abstrações (MARTIN, 2019). Dessa forma os sistemas são flexíveis o suficiente 
para que suas implementações possam mudar diminuindo o impacto em dependentes. Isso 
pode ser resumido como: não se deve depender de nada que seja concreto (MARTIN, 2019). 
 Depender de elementos concretos é arriscado. Esse risco decorre do fato que 
implementações são menos estáveis do que abstrações. Então, depender de abstrações é mais 
seguro (MARTIN, 2019). 
 Os exemplos das seções 2.2.2.2 Princípio de aberto/fechado (OCP), 2.2.2.3 Princípio 
de substituição de Liskov e 2.2.2.4 Princípio da segregação de interfaces todos usam de 
inversão de dependência. Apenas com uma pequena diferença no exemplo de aplicação de 
LSP em que é usado herança. Entretanto é uma herança onde existe um método abstrato que 
as subclasses devem implementar. 
 
3.2.3 Padrões de projeto de software 
 
Um padrão de projeto de software é um conjunto de contexto, problema e uma solução 
documentada. Essa solução não é nova, ela é uma solução consolidada que já foi usada e 
testada em outros projetos por outros desenvolvedores (GUERRA, 2014). Nos subtópicos a 
seguir serão tratados os principais padrões usados neste trabalho. 
 
3.2.3.1 Singleton 
 
O padrão Singleton garante que exista apenas uma instância de objeto. E para garantir que só 
exista uma única instância, a classe controla como uma instância é criada. Essa classe garante 
que não exista outra instância do mesmo objeto, e que esse objeto seja de fácil acesso 
(GAMMA, et al., 1994). 
 Esse padrão deve ser usado com muito cuidado por conta das dificuldades envolvidas 
em controlar estados e usar testes unitários. Também é necessário cuidado em relação a 
 
36 
destruição da instância de objetos Singleton. O trecho de código da Figura 11 mostra a 
estrutura básica de um Singleton. 
 
Figura 12 ─ Estrutura básica do padrão Singleton em código escrito em C# 
 
 
Fonte: Autor. 
 
3.2.3.2 Template Method 
 
O padrão Template Method é um padrão comportamental que define um esqueleto básico de 
um determinado algoritmo onde certos passos específicos são delegados as subclasses 
(GAMMA, et al., 1994). O código genérico que são passos que as subclasses executam de 
forma igual, são adicionados a uma superclasse. Os passos específicos de cada subtipo são 
implementados através de um método abstrato que é definido na superclasse. Dessa forma o 
código que representa os passos gerais é implementado na superclasse e o código é 
reaproveitado através da herança, e os passos específicos ficam a cargo das subclasses. 
 O exemplo da Figura 7, na seção 2.2.2.3 Princípio de substituição de Liskov (LSP), 
temos uma aplicação do padrão Template Method onde o método void UpdateValue(int value) 
é uma abstração do que qualquer coletável do jogo precisa para incrementar ou decrementar 
qualquer contador do jogo. Esse método gancho (GUERRA, 2014) que inicia a execução do 
código que é específico da subclasse. 
3.2.3.3 Strategy 
 
 
37 
O padrão Strategy também é um padrão comportamental em que é possível definir uma 
família de algoritmos, onde cada um é encapsulado e cada um deles é intercambiável de 
acordo com os clientes que os usam (GAMMA, et al., 1994). Esse padrão permite que 
comportamento seja trocado em tempo de execução de acordo com a instância usada, 
contanto que a classe dessa instância seja a implementação de uma interface. 
 O exemplo da Figura 4, da seção 2.2.2.2 Princípio do aberto/fechado (OCP), é um 
exemplo da aplicação do padrão. Com essa aplicação, o jogador alterna entre as armas 
disponíveis apenas trocando entre instâncias de classes que implementam IGun. Isso também 
é uma clara aplicação do Princípio da inversão de dependência (DIP) descrito na seção 
2.2.2.5 porque a classe Player não depende de uma instância concreta de uma classe que 
represente uma arma, mas sim de uma interface IGun. 
 
3.2.2.4 Observer 
 
O padrão Observer é um outro padrão comportamental. Esse padrão define uma dependência 
de um para muitos entre objetos de forma que quando um objeto observado muda de estado, 
os objetos observadores são notificados a respeito da mudança de estado (GAMMA, et al., 
1994). Esse padrão é muito usado em frameworks de várias linguagens como forma de 
notificação do acontecimento de interações do usuário com a interface do sistema. 
 A implementação desse padrão apresenta um objeto que muda de comportamento 
chamado de Subject. Os objetos que desejam ser notificados a respeito da mudança de estado 
do Subject, são chamados de Observers. Quando um Observer deseja saber a respeito da 
mudança de estado de um Subject, ele se inscreve na lista de notificação do Subject. Dessa 
forma, quando a mudança de estado acontecer, os Observers são notificados. A estrutura 
básica do padrão Observer está representado na Figura 12. 
 
 
 
 
 
 
 
 
 
 
38 
Figura 13 ─ Estrutura básica do padrão Observer em UML 
 
 
Fonte: Gamma, et al. (1994). 
 
4 METODOLOGIA 
 
A metodologia deste trabalho se divide em 4 passos fundamentais. O primeiro foi o passo de 
revisão bibliográfica em busca de livros e trabalhos semelhantes aos temas tratados. O 
segundo passou foi uma avaliação da estrutura da primeira versão verificando funcionalidades 
e estrutura. O terceiro passo foi o processo de refatoração usando os conceitos descritos na 
Seção 3 de fundamentação. E o último passo foi a análise estática de código usando o 
NDepend. Os subtópicos a seguir detalham a metodologia usada na execução deste trabalho. 
 
4.1 Revisão bibliográfica 
A etapa de levantamento bibliográfico, foi feita em busca de trabalhos e livros sobre cada um 
dos principais fundamentos abordados neste trabalho. Os temas de busca foram relacionados a 
refatoração, princípios SOLID e padrões de projeto. As buscas com relação a padrões de 
projeto e princípios de design SOLID, foram realizadas através do Scholar Google, e entre os 
anais da SBGames de diversos anos. As principais palavras-chave de busca foram: 
 
• “code quality”; 
• “code metrics”; 
• “software quality”; 
• “qualidade de software”; 
• “métricas de código”; 
 
39 
• “design smells”; 
• “code smells”; 
• “design patterns”; 
• “padrões de projeto”; 
• “game patterns”; 
• “game design patterns”; 
• “agile design”; 
• “solid principles”; 
• “princípios solid”; 
• “agile principles solid”; 
 
Após as buscas, foi feita uma filtragem de trabalhos e livros que tinham conteúdo 
coerente com o tema deste trabalho. Para isso, a introdução e a estrutura de tópicos dos 
trabalhos e livros foram analisados. Os que passaram pela primeira filtragem depois foram 
lidos por completo ou lidos apenas os tópicos necessários, e somente os que tinham relação 
com os temas deste trabalho foram mantidos e usados como referencial teórico. 
 
4.2 Avaliação da primeira versão 
 
A avaliação da primeira versão foi feita para rever os conceitos usados na elaboração da 
primeira versão. Para isso, o código foi analisado tanto com leitura quanto com o uso de 
diagramas. Os diagramas usados na avaliação inicial foram obtidos através de engenharia 
reversa do código do projeto usando a ferramenta Doxygen para criar uma versão do código 
em arquivos XML, e o plugin do Astah UML C# Code Reverse Plug-in que usa o projeto em 
XML para transformá-lo em diagramas. 
 A leitura do código e a análise da estrutura por meio do código e dos diagramas, foi 
possível perceber os problemas relacionados a maus cheiros de design, dependências, 
tamanho das classes etc. Essa etapa foi fundamental para a etapa seguinte de refatoração. 
 
4.3 Refatoraçãodo código do projeto 
 
A terceira etapa foi a aplicação do processo de refatoração. A refatoração foi realizada em 
pequenos passos. Em cada passo, uma parte da estrutura ou funcionalidade era analisada em 
 
40 
busca de maus cheiros de design e possíveis padrões de projeto que poderiam ser aplicados. 
Em seguida, o código era refatorado aplicando os princípios SOLID e padrões de projeto 
quando aplicáveis. Durante esse processo, não foram usados testes unitários, os testes eram 
apenas de uso verificando se a funcionalidade era mantida em relação a primeira versão. 
 
4.4 Análise estática de código usando NDepend 
 
Para verificar as diferenças entre a versão inicial e a final, foi feita uma análise estática de 
código na primeira e na versão final. As medidas realizadas foram em relação as Linhas de 
Código (LOC), Complexidade Ciclomática (CC) e Dependências. Mas infelizmente as 
medidas de Dependência tiveram de ser desconsideradas pois apresentavam falsos positivos 
por conta de como a Unity funciona. Os dados obtidos mostraram diferenças sutis entre as 
versões, mas que mesmo assim indicaram que houve uma mudança significativa. 
 
5 DESENVOLVIMENTO 
 
Para o desenvolvimento deste trabalho, primeiro será apresentado o estado inicial do projeto 
apresentando os componentes, suas funcionalidades e como esses componentes se relacionam. 
Logo depois, serão apresentados os problemas dessa versão inicial em relação ao design desse 
código. Serão indicados os maus cheiros de design que mostram a necessidade da refatoração 
para os princípios SOLID (MARTIN, R.; MARTIN, M., 2006). E por fim, serão apresentadas 
as versões finais dos componentes após a refatoração para aplicar os princípios e os padrões. 
Os padrões serão consequência da aplicação dos princípios. Quando não, será justificado sua 
aplicação. 
 
5.1 Refatoração 
 
O processo de refatoração não irá contemplar todas as funcionalidades da Tabela 1. Serão 
refatoradas as funcionalidades de controles do personagem, inputs do jogador, sistema de 
contagem de pontos de escore, sistema de movimento e vida do personagem e penalidade de 
colisão com obstáculos na cena. A ordem de refatoração foi feita partindo dos pontos no 
código do projeto mais importantes. Classes com maior importância são as classes que 
concentram as principais funcionalidades do jogo. Então a sequência de mudanças segue a 
ordem de prioridade da classe mais importante até a menos importante. 
 
41 
 
5.1.1 Separando as responsabilidades e invertendo dependências da classe 
ControleDoPersonagem 
 
A classe ControleDoPersonagem que é onde ocorre a maior concentração de funcionalidades. 
Por conta disso essa classe carrega um total de 4 eixos de mudança. Isso significa que uma 
mudança pode gerar uma cascata de outras mudanças e essas responsabilidades devem ser 
desacopladas. 
As responsabilidades foram separadas em novas classes. Essas classes são: 
PlayerInputs, PlayerControls, Mover, Jumpper foram criadas. O diagrama UML representado 
na Figura 14 mostra como essa estrutura foi criada. PlayerControls é a classe responsável por 
receber as verificações de inputs que vem através da classe PlayerInputs, e executar as ações 
do personagem de acordo com esses inputs. As ações de pular e mover para a direita são 
executadas através do padrão Template Method, visto na seção 2.2.3.2 Template Method. 
Dessa forma, caso uma nova ação do personagem seja necessária, basta criar uma subtipo de 
ActionBase e sobrescrever o método void DoAction(). Dessa forma, o design está de acordo 
com o princípio SRP, descrito na seção 2.2.2.1 Princípio da responsabilidade única (SRP), 
pois as responsabilidades estão bem definidas e cada classe tem apenas um eixo de mudança. 
E está de acordo com OCP descrito na seção 2.2.2.2 Princípio de aberto/fechado (OCP). 
Entretanto, o design ainda não está de acordo com DIP, descrito na seção 2.2.2.5 
Princípio da inversão de dependência (DIP) porque as dependências entre as classes são 
todas concretas. Então as o DIP precisa ser aplicado para tornar as dependências concretas em 
dependências abstratas. 
MoveAction precisa saber se o personagem está em contato com o chão para poder 
realizar o movimento para direita. Então, para não depender da classe concreta JumpAction, a 
dependência foi invertida para a interface IGroudChecker. Essa dependência é necessária 
porque só quando o personagem está em contato com o chão que ele pode mover para direita. 
A classe PlayerControls depende apenas da abstração de ActionBase para as ações de mover e 
saltar, então já está de acordo com DIP. Entretanto, PlayerControls depende diretamente de 
uma instância da classe PlayerInputs. Isso é um problema caso seja necessária a inclusão de 
controles para uma nova plataforma, como por exemplo se o jogo precisar ser portado para 
dispositivos Android. Nesse caso, a inversão de dependências abre espaço para aplicação do 
padrão Strategy, discutido na seção 2.2.3.3 Strategy. A Figura 15 mostra as dependências 
citadas invertidas para interfaces entre as classes. 
 
42 
 
Figura 14 ─ Estrutura da separação das responsabilidades da classe ControleDoPersonagem 
retirando as responsabilidades de verificar inputs, mover para direita e pular após a aplicação 
do Princípio da Responsabilidade única SRP. 
 
 
Fonte: Autor. 
 
A aplicação da inversão de dependências entre a classe MoveAction e JumpAction é 
uma clara aplicação de ISP, descrito na seção 2.2.2.4 Princípio da segregação de interfaces 
(ISP) porque MoveAction é um cliente de JumpAction e esse cliente deve ser isolado das 
mudanças que podem ocorrer em outras funcionalidades de JumpAction. Com o uso da 
inversão de dependências, esse isolamento acontece. 
 
 
 
 
 
 
 
 
43 
Figura 15 ─ Estrutura da separação das responsabilidades da classe ControleDoPersonagem 
retirando as responsabilidades de verificar inputs, mover para direita e pular após a aplicação 
do Princípio da inversão de dependências DIP 
 
 
Fonte: Autor. 
 
5.1.2 Mudando a forma como a contagem de pontos é feita usando o padrão Observer 
 
Sempre que um item é coletado pelo jogador, o contador de score deve ser incrementado e o 
contador de itens para o dash também deve ser incrementado. Para isso foi implementado o 
padrão Observer, discutido na seção 2.2.3.4 Observer. Esse padrão será implementado com o 
uso de serialização usando os recursos da classe ScriptableObject. Então uma classe chamada 
OnItemCollectedEvent foi criada com a responsabilidade de adicionar, remover e notificar 
ouvintes que implementam a interface IOnItemCollectedListener. A figura 16 mostra um 
diagrama UML que retratando a estrutura proposta. Em seguida, a Figura 17 mostra o código 
que implementa a classe OnItemCollectedEvent estendendo de ScriptableObject. 
 Classes que estendem de ScriptableObject, são adicionados no projeto como assets. 
Esse tipo de objeto é facilmente serializado e todo esse processo fica a cargo da própria Unity. 
Então, foi criado um asset no projeto que é a instância do evento de item coletado. Essa 
mesma instância é adicionada as classes interessadas em se inscrever no evento e nas que 
 
44 
disparam o evento. 
 
Figura 16 ─ Estrutura do sistema de score e itens implementado com o padrão Observer 
 
 
Fonte: Autor. 
 
Com esse padrão, agora é possível que qualquer ouvinte seja notificado a respeito de itens 
coletados durante a fase, contanto que o ouvinte implemente a interface 
IItemCollectedListsner e se inscrever na instância de um objeto OnItemCollectedEvent. 
 
Figura 17 ─ Implementação do evento que notifica interessados em quando um item é 
coletado 
 
 
Fonte: Autor. 
 
45 
 
5.1.3 Separação da funcionalidade Dash da classe ControleDoPersonagem, inversão de 
dependências e organização em camadas 
 
O dash do personagem foi implementado na classe ControleDoPersonagem. O código está 
distribuídoem dois métodos como mostra a Figura 16. O código apresenta o mau cheiro de 
Opacidade já que é difícil de ser compreendido e qualquer alteração resultaria em uma cascata 
de mudanças mostrando que o design cheira a Rigidez. Uma alteração no salto ou no dash 
resultaria em falhas, mostrando que o design do código cheira a Fragilidade. E supondo uma 
mudança, seria mais fácil realizar “gambiarras” para que uma extensão de funcionalidade 
fosse adicionada a base de código existente o que é um sinal do mau cheiro de Viscosidade. 
 
Figura 18 ─ Código do dash acoplado ao código do salto do personagem tornando o design 
Viscoso, Rígido, Frágil e Opaco. 
 
 
Fonte: Autor. 
 
 Para remover o dash da classe ControleDoPersonagem, foi necessário decompor a 
funcionalidade em outras classes que são: DashAction que é uma subclasse de ActionBase, o 
DashCounter que controla a contagem de itens coletados e a DashUI que exibe a quantidade 
de itens coletados para o jogador. A Figura 17 mostra um diagrama de classes com a estrutura 
 
46 
de solução. 
Essa estrutura divide-se em camadas, uma que realiza a ação representada pela classe 
DashAction, outra pela contagem e controle de quando o dash deve ser usado ou não que é a 
classe DashCouter. E a classe responsável pela exibição da contagem na interface, a classe 
DashUI. Sempre que o jogador pressionar a tecla “backspace”, caso cinco itens tenham sido 
coletados pelo jogador, o personagem realiza o dash. 
 
Figura 19 ─ Diagrama de classes UML que mostra a estrutura da funcionalidade dash 
implementada 
 
 
Fonte: Autor. 
 
 Dessa forma, o design desse módulo está de acordo com SRP, descrito na seção 2.2.2.1 
Princípio da responsabilidade única (SRP) pois cada classe tem apenas um eixo de 
responsabilidade. O design também está de acordo com 2.2.2.4 Princípio da segregação de 
interfaces (ISP) porque as classes servem seus clientes através de abstrações de interface que 
expõem apenas o que o cliente usa. O design também está de acordo com DIP o 2.2.2.5 
Princípio da inversão de dependência (DIP) porque as dependências das classes 
implementadas são direcionadas apenas a abstrações e não implementações concretas. 
 
5.1.4 Dinâmica de movimento da câmera e fim de jogo 
 
 
47 
A câmera do jogo deve iniciar parada enquanto a interface de início da fase é iniciada. Assim 
que o jogador dá o primeiro input, a interface some e a câmera começa a se mover para direita 
em velocidade constante. Caso o personagem saia do enquadramento da câmera, o fim de 
jogo acontece com a câmera parando de se mover e a interface de fim de jogo aparecendo. 
 O design do código cheira a Fragilidade porque uma pequena modificação pode 
resultar na quebra das funcionalidades de mover a câmera, fim de jogo e iniciar a fase. Outro 
cheiro desse design é a Viscosidade porque simples modificações são difíceis de manter o 
design atual. Isso aumenta as chances de “gambiarras” serem adicionadas ao design na 
necessidade de uma modificação. Também apresenta o cheiro de Repetição desnecessária 
porque o código de verificação dos inputs iniciais do jogador se repete em outros pontos do 
código de outras classes. O design também apresenta o cheiro de Opacidade porque o código 
é difícil de entender. A Figura 20 mostra a implementação da classe ControleDaCamera. 
 
Figura 20 ─ Implementação da primeira versão das funcionalidades da câmera e interfaces de 
início e fim de jogo na classe ControleDaCâmera 
 
 
Fonte: Autor. 
 
 O primeiro passo foi refatorar o nome da classe ControleDaCamera para 
 
48 
CameraController. Depois, o padrão Observer descrito na seção 2.2.3.4 Observer foi 
implementado novamente modificando a estrutura da classe PlayerInputs. Agora PlayerInputs 
notifica os interessados em saber quando o primeiro input do jogador acontece. Esses 
interessados são as classes PlayerControls e CameraController. A decisão dessa modificação e 
aplicação desse padrão decorreu da ideia de que existem mais de um interessado no 
acontecimento do primeiro input vindo do jogador. Além de que esse padrão diminui o 
acoplamento entre classes e módulos e está de acordo com os princípios SOLID. A Figura 21 
mostra um diagrama de classe retratando as relações entre CameraController e PlayerInputs. 
Primeiro foi necessário realizar as modificações na estrutura que verifica os inputs 
vindos do jogador. A Figura 21 mostra essa modificação feita na estrutura. Agora a classe 
PlayerControls, que é uma interessada em saber quando o jogador pressiona qualquer botão 
do jogo, e para isso ela se inscreve no subject OnPlayerFirstInputEvent implementa a 
interface IPlayerFirstInputListener. 
 
Figura 21 ─ Refatoração da estrutura que verifica inputs do jogador para aplicar o padrão 
Observer 
 
 
Fonte: Autor. 
 
 
49 
 Em seguida, a classe CameraController foi refatorada para também se inscrever e 
implementar no subject OnPlayerFirstInputEvent e IPlayerFirstInputListener. Dessa forma, 
assim como retratado na Figura 20, CameraController também é uma classe interessada no 
evento de primeiro input do jogador. A Figura 22 mostra a nova implementação da classe 
CameraController para aplicar o padrão Observer e estar de acordo com os princípios SOLID. 
Dessa forma a Fragilidade diminuiu porque as chances de o código quebrar em outras partes 
diminuíram. a Viscosidade também diminuiu porque agora é mais fácil manter o design. A 
Repetição desnecessária foi eliminada com a aplicação do padrão Observer e a opacidade 
diminuiu, pois, a classe está mais fácil de entender. 
 
Figura 22 ─ Implementação da classe CameraController que é uma classe interessada em 
saber sobre o evento de primeiro input do jogador 
 
 
Fonte: Autor. 
 
50 
 
 Por fim, é necessário que o fim de jogo aconteça quando o personagem sai do 
enquadramento da câmera. Para isso, a classe PlayerLife verifica quando o personagem sai do 
enquadramento da câmera e realiza o fim de jogo. A Figura 23 mostra a implementação dessa 
classe. 
 
Figura 23 ─ Implementação da classe PlayerLife que exibe a interface de fim de jogo quando 
o personagem sai do enquadramento da câmera. 
 
 
Fonte: Autor. 
 
5.1.5 Penalidade de colisão com objetos da cena, bloqueio dos controles do personagem e 
parar a câmera depois do fim de jogo 
 
Quando o personagem colide com algum obstáculo da cena, o jogador é penalizado com os 
movimentos do personagem bloqueados por um curto período enquanto o personagem é 
lentamente jogado para esquerda. A Figura 24 mostra a implementação da classe Obstaculo na 
primeira versão. 
 
51 
O primeiro problema da implementação na Figura 24, é que existe um trecho de 
código que está comentado sem explicação nenhuma. No momento da escrita deste trabalho, 
não fica claro o porquê de o trecho comentado permanecer na classe. Outro problema é que 
existe uma chamada através de mensagens que invoca um método na classe 
ControleDoPersonagem. Esse tipo de chamada é suscetível a erros de escrita e é um problema 
que pode se tornar difícil de identificar. A responsabilidade da aplicação da penalidade de 
colisão está distribuída de forma que não é clara entre as classes Obstaculo e 
ControleDoPersonagem e o design cheira a Opacidade. 
Figura 24 ─ Implementação da classe Obstaculo na primeira versão 
 
 
Fonte: Autor. 
 
Essa mudança gerou modificações na classe PlayerControls em que os controles do 
personagem devem ser passíveis de bloquei e desbloqueio para que a penalidade de colisão 
seja aplicada de forma correta. A classe responsável por verificar e aplicar essas colisões se 
chama Obstaculo na primeira versão. O primeiro passo dessa refatoração foi renomear essa 
classe para PlayerCollisionDetectionAndPenality. A Figura 25 mostra um trecho de como a 
penalidade é aplicada quando uma colisão com um obstáculo acontece. 
 
 
 
 
 
52 
Figura 25 ─ Trecho que mostra parte das modificações

Continue navegando

Outros materiais