Prévia do material em texto
<p>Autor: Prof. Tarcísio de Souza Peres</p><p>Colaboradores: Prof. Angel Antonio Gonzalez Martinez</p><p>Profa. Larissa Rodrigues Damiani</p><p>Programação Orientada</p><p>a Objetos II</p><p>Professor conteudista: Tarcísio de Souza Peres</p><p>É formado em Engenharia da Computação e mestre pela Unicamp. Atuou como pesquisador no Human Cancer</p><p>Genome Project, onde aplicou algoritmos e programação orientada a objetos. É MBA em Gestão de TI pela Fiap,</p><p>formado em Gestão de Projetos pela FIA/USP e cursou Gestão Estratégica e Competitividade pela Fundação Dom</p><p>Cabral. Com perfil multidisciplinar, tem experiência em tecnologia, saúde, governança, novos negócios e inovação.</p><p>Trabalhou em multinacionais e órgãos públicos. É certificado pelo PMI, Cobit e autor de livro sobre mercado financeiro.</p><p>Atua como empreendedor (startups de inovação em tecnologia), professor na UNIP, cFGV e no Centro Paula Souza.</p><p>© Todos os direitos reservados. Nenhuma parte desta obra pode ser reproduzida ou transmitida por qualquer forma e/ou</p><p>quaisquer meios (eletrônico, incluindo fotocópia e gravação) ou arquivada em qualquer sistema ou banco de dados sem</p><p>permissão escrita da Universidade Paulista.</p><p>Dados Internacionais de Catalogação na Publicação (CIP)</p><p>U520.13 – 24</p><p>Dados Internacionais de Catalogação na Publicação (CIP)</p><p>P437p Peres, Tarcísio de Souza.</p><p>Programação Orientada a Objetos II / Tarcísio de Souza Peres. –</p><p>São Paulo: Editora Sol, 2024.</p><p>298 p., il.</p><p>Nota: este volume está publicado nos Cadernos de Estudos e</p><p>Pesquisas da UNIP, Série Didática, ISSN 1517-9230.</p><p>1. Elementos gráficos. 2. C#. 3. Desenvolvimento. I. Título.</p><p>CDU 681.3.062</p><p>Profa. Sandra Miessa</p><p>Reitora</p><p>Profa. Dra. Marilia Ancona Lopez</p><p>Vice-Reitora de Graduação</p><p>Profa. Dra. Marina Ancona Lopez Soligo</p><p>Vice-Reitora de Pós-Graduação e Pesquisa</p><p>Profa. Dra. Claudia Meucci Andreatini</p><p>Vice-Reitora de Administração e Finanças</p><p>Prof. Dr. Paschoal Laercio Armonia</p><p>Vice-Reitor de Extensão</p><p>Prof. Fábio Romeu de Carvalho</p><p>Vice-Reitor de Planejamento</p><p>Profa. Melânia Dalla Torre</p><p>Vice-Reitora das Unidades Universitárias</p><p>Profa. Silvia Gomes Miessa</p><p>Vice-Reitora de Recursos Humanos e de Pessoal</p><p>Profa. Laura Ancona Lee</p><p>Vice-Reitora de Relações Internacionais</p><p>Prof. Marcus Vinícius Mathias</p><p>Vice-Reitor de Assuntos da Comunidade Universitária</p><p>UNIP EaD</p><p>Profa. Elisabete Brihy</p><p>Profa. M. Isabel Cristina Satie Yoshida Tonetto</p><p>Prof. M. Ivan Daliberto Frugoli</p><p>Prof. Dr. Luiz Felipe Scabar</p><p>Material Didático</p><p>Comissão editorial:</p><p>Profa. Dra. Christiane Mazur Doi</p><p>Profa. Dra. Ronilda Ribeiro</p><p>Apoio:</p><p>Profa. Cláudia Regina Baptista</p><p>Profa. M. Deise Alcantara Carreiro</p><p>Profa. Ana Paula Tôrres de Novaes Menezes</p><p>Projeto gráfico: Revisão:</p><p>Prof. Alexandre Ponzetto Caio Ramalho</p><p>Vitor Andrade</p><p>Programação Orientada a Objetos II</p><p>APRESENTAÇÃO ......................................................................................................................................................7</p><p>INTRODUÇÃO ...........................................................................................................................................................8</p><p>Unidade I</p><p>1 REVISÃO DE CONCEITOS DE POO E INTRODUÇÃO AO AMBIENTE C# ........................................ 11</p><p>1.1 Revisando conceitos de POO: exercícios práticos (console) ............................................... 12</p><p>1.1.1 Considerações sobre o código-fonte .............................................................................................. 69</p><p>1.2 Apresentação geral dos recursos e elementos de interface no ambiente</p><p>de programação C# .................................................................................................................................... 80</p><p>1.3 Ambiente de desenvolvimento integrado (IDE) ....................................................................... 83</p><p>1.4 Introdução ao .NET Framework e .NET Core .............................................................................. 95</p><p>2 DESENVOLVIMENTO DA PRIMEIRA INTERFACE GRÁFICA .............................................................100</p><p>2.1 Windows Forms ...................................................................................................................................101</p><p>2.2 Formulários e caixas de mensagem ............................................................................................103</p><p>2.3 Elementos gráficos básicos: rótulos, caixas de texto e botões ........................................109</p><p>2.4 Elementos gráficos de apoio: painéis, grupos, rótulos com vínculo e dicas</p><p>de ferramenta ..............................................................................................................................................111</p><p>Unidade II</p><p>3 ELEMENTOS GRÁFICOS DE ESCOLHA E ORGANIZAÇÃO ................................................................122</p><p>3.1 Elementos gráficos de escolha: caixas de opção, de seleção e de combinação .......122</p><p>3.2 Elementos gráficos de escolha: calendário mensal e seletor de data ..........................125</p><p>3.3 Elementos gráficos de organização: menus e caixa de listagens ...................................128</p><p>3.4 Elementos gráficos de organização: controle de abas, visualização em</p><p>árvore e em lista .........................................................................................................................................131</p><p>4 MANIPULAÇÃO DE EVENTOS E JANELA MDI ......................................................................................137</p><p>4.1 Manipulando eventos de mouse e teclado ..............................................................................138</p><p>4.2 Interface de documentos múltiplos (multiple document interface – MDI) ...............142</p><p>4.3 Projeto básico: juntando todas as partes com interface</p><p>humano-computador (IHC) e user experience (UX) ....................................................................146</p><p>Unidade III</p><p>5 ARQUITETURA DE SOFTWARE E PADRÕES DE DESIGN ..................................................................162</p><p>5.1 Desenvolvimento em camadas e MVC ......................................................................................163</p><p>5.2 Entendendo os componentes do Windows Presentation Foundation (WPF) ............165</p><p>Sumário</p><p>5.3 MVVM e sua integração com XAML ...........................................................................................167</p><p>5.4 Aprimoramento: projeto de interface desktop com XAML ...............................................168</p><p>6 ACESSO A DADOS, ADO.NET E LINQ .......................................................................................................205</p><p>6.1 String de conexão: SQLClient e comentários sobre outros provedores</p><p>de dados do .NET Framework (OLE DB e OBDC) ............................................................................206</p><p>6.2 Conexões locais e servidores remotos no contexto da computação</p><p>em nuvem .....................................................................................................................................................219</p><p>6.3 Arquitetura do ADO.NET: DataSet, DataTable, DataView, DataRow</p><p>e DataColumn .............................................................................................................................................224</p><p>6.4 LINQ e ADO.NET ...................................................................................................................................229</p><p>6.5 Projeto intermediário: interface desktop com acesso aos dados do banco ...............230</p><p>Unidade IV</p><p>7 TÉCNICAS AVANÇADAS E PRÁTICAS SEGURAS EM C# .................................................................236</p><p>7.1 Técnicas de programação assíncrona em C# (async e await) ..........................................236</p><p>7.2 Princípios e práticas de domain-driven design (DDD) ........................................................241</p><p>7.2.1 Contexto delimitado 1: gestão de produtos ............................................................................. 245</p><p>7.2.2 Contexto delimitado 2: processamento</p><p>considerando que cada carta encapsula sua própria lógica de efeito; isso facilita a</p><p>leitura do código, pois a lógica relacionada a um efeito específico da carta está contida dentro dela. Terceiro,</p><p>simplifica o processo, uma vez que se evita criar uma grande estrutura condicional e se dispensa herança</p><p>ou polimorfismo para lidar com diferentes tipos de efeito de carta. Para finalizar, facilita a POO, na qual os</p><p>efeitos das cartas podem ser vistos como respostas a eventos (neste caso, o evento de uma carta ser jogada).</p><p>Na prática isso significa que, ao criar uma nova CartaDeEvento, você pode fornecer diferentes lógicas</p><p>de efeito simplesmente passando diferentes métodos para o construtor. Por exemplo, uma carta pode</p><p>ter um efeito que aumenta a fortuna do jogador, enquanto outra pode pausar o jogador no próximo</p><p>turno – este é um exemplo de polimorfismo, porque diferentes implementações de ação podem ser</p><p>usadas de forma intercambiável.</p><p>Veremos em breve a classe Baralho, que contém uma coleção de CartaDeEvento, mecanismo pelo qual</p><p>os eventos são introduzidos no jogo. Ao chamar SortearCarta na classe Baralho, uma carta é selecionada</p><p>aleatoriamente, e seu efeito é aplicado ao jogador relevante. Esse aspecto aleatório é fundamental para</p><p>criar uma experiência de jogo dinâmica e imprevisível, na qual os participantes devem constantemente</p><p>adaptar suas estratégias a eventos inesperados.</p><p>Do ponto de vista técnico, a abordagem usada para implementar a classe CartaDeEvento é elegante</p><p>e eficiente. Na programação C#, um código considerado elegante é o que harmoniza vários aspectos</p><p>importantes do desenvolvimento de software. Legibilidade é essencial: um código elegante é fácil de ler e</p><p>entender, o que se consegue através de uma boa estrutura, formatação adequada e nomes significativos</p><p>para variáveis e métodos. A clareza na expressão das intenções do programador facilita tanto o trabalho</p><p>em equipe quanto a manutenção futura do código.</p><p>Manutenibilidade é outro aspecto importante: código elegante é o que pode ser facilmente</p><p>modificado e estendido. Isso é frequentemente alcançado adotando bons princípios de design (como</p><p>os princípios SOLID) e boa modularização do código, facilitando a atualização e a adição de novas</p><p>funcionalidades, além de tornar o código mais compreensível para outros desenvolvedores. Utilizando</p><p>delegados e expressões lambda, dispensa-se uma hierarquia extensa de classes ou um switch-case longo</p><p>para lidar com os diferentes efeitos das cartas. Além disso, essa abordagem torna o código mais legível</p><p>e fácil de manter, já que a lógica de cada carta está contida dentro de sua própria instância, em vez de</p><p>espalhada em várias partes do código.</p><p>35</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>A figura 11 ilustra como a simplicidade da CartaDeEvento se manifesta na interação da</p><p>classe Baralho.</p><p>1. class Baralho</p><p>2. {</p><p>3. private List cartas;</p><p>4. private Random random;</p><p>5.</p><p>6. public Baralho(Random random)</p><p>7. {</p><p>8. this.random = random;</p><p>9. cartas = new List() {</p><p>10. new CartaDeEvento(“Desafio do Cardeal: Você foi pego em uma</p><p>emboscada pelos guardas de Richelieu. Perca uma rodada enquanto escapa.”,</p><p>jogador => jogador.PausarProximoTurno()),</p><p>11. new CartaDeEvento(“O Olhar de Constance: Constance Bonacieux,</p><p>por quem você se apaixona, lhe deu um olhar encorajador. Avance duas</p><p>casas com a alegria do amor.”, jogador => jogador.Posicao += 2),</p><p>12. new CartaDeEvento(“Intriga da Rainha: A rainha Ana da Áustria</p><p>confiou a você uma missão secreta para recuperar diamantes. Avance três</p><p>casas e ganhe uma ficha de Honra.”, jogador => { jogador.Posicao += 3;</p><p>jogador.FichasDeHonra += 1; }),</p><p>13. new CartaDeEvento(“Duelo com um Mosqueteiro: Um desentendimento</p><p>levou a um duelo com um de seus camaradas. Volte uma casa e perca uma ficha</p><p>de Honra.”, jogador => { jogador.Posicao = Math.Max(1, jogador.Posicao –</p><p>1); jogador.FichasDeHonra = Math.Max(0, jogador.FichasDeHonra – 1); }),</p><p>14. new CartaDeEvento(“Trama de Milady: A sedutora e astuta Milady</p><p>de Winter tramou contra você. Volte duas casas enquanto desvenda sua</p><p>trama.”, jogador => jogador.Posicao = Math.Max(1, jogador.Posicao - 2)),</p><p>15. new CartaDeEvento(“Auxílio de Buckingham: O Duque de Buckingham,</p><p>aliado da rainha, oferece ajuda em sua jornada. Avance duas casas com o</p><p>apoio dele.”, jogador => jogador.Posicao += 2),</p><p>16. new CartaDeEvento(“Festa em Paris: Uma festa extravagante</p><p>é realizada em Paris, e você é o convidado de honra. Perca uma rodada</p><p>enquanto celebra.”, jogador => jogador.PausarProximoTurno()),</p><p>17. new CartaDeEvento(“Missão Secreta: Uma carta selada da rainha</p><p>lhe é entregue, pedindo uma missão sigilosa. Avance três casas com sua</p><p>nova responsabilidade.”, jogador => jogador.Posicao += 3),</p><p>18. new CartaDeEvento(“Emboscada na Estrada: Enquanto viaja, você é</p><p>emboscado por inimigos desconhecidos. Volte duas casas enquanto escapa</p><p>do perigo.”, jogador => jogador.Posicao = Math.Max(1, jogador.Posicao – 2)),</p><p>36</p><p>Unidade I</p><p>19. new CartaDeEvento(“Defesa do Rei: O Rei Luís XIII está sob</p><p>ameaça e você o defendeu bravamente. Avance duas casas e ganhe</p><p>duas fichas de Honra.”, jogador => { jogador.Posicao += 2;</p><p>jogador.FichasDeHonra += 2; }),</p><p>20. new CartaDeEvento(“Convite do Cardeal: Richelieu, em um movimento</p><p>inesperado, convida você para um banquete. Perca uma rodada devido à</p><p>cautela, mas ganhe uma ficha de Fortuna pelas oportunidades que surgem.”,</p><p>jogador => { jogador.PausarProximoTurno();</p><p>jogador.FichasDeFortuna += 1; }),</p><p>21. new CartaDeEvento(“Refúgio em um Mosteiro: Escapando de</p><p>perseguidores, você se refugia em um mosteiro. Perca uma rodada, mas</p><p>ganhe uma ficha de Honra pela proteção oferecida.”, jogador =></p><p>{ jogador.PausarProximoTurno(); jogador.FichasDeHonra += 1; }),</p><p>22. new CartaDeEvento(“Treinamento Intensivo: Você treina</p><p>duro com seus companheiros mosqueteiros. Avance uma casa e ganhe uma</p><p>ficha de Fortuna.”, jogador => { jogador.Posicao += 1;</p><p>jogador.FichasDeFortuna += 1; }),</p><p>23. new CartaDeEvento(“Intriga na Corte: Rumores sobre você</p><p>circulam na corte. Volte uma casa, mas ganhe uma ficha de Fortuna pelo</p><p>aprendizado com a situação.”, jogador => { jogador.Posicao = Math.Max(1,</p><p>jogador.Posicao – 1); jogador.FichasDeFortuna += 1; }),</p><p>24. new CartaDeEvento(“Mensagem de Socorro: Constance lhe envia</p><p>uma mensagem urgente. Avance duas casas enquanto se apressa em ajudá-la e</p><p>ganhe uma ficha de Honra.”, jogador => { jogador.Posicao += 2;</p><p>jogador.FichasDeHonra += 1; }),</p><p>25. new CartaDeEvento(“Dádiva do Duque: O Duque de Buckingham</p><p>presenteia você com ricas vestimentas. Ganhe duas fichas de Fortuna.”,</p><p>jogador => jogador.FichasDeFortuna += 2),</p><p>26. new CartaDeEvento(“Perseguição nas Ruas: Envolvido em uma</p><p>perseguição pelas ruas de Paris, você escapa por pouco. Perca uma ficha de</p><p>de Honra, mas ganhe uma ficha de Fortuna pela experiência.”, jogador =></p><p>{ jogador.FichasDeHonra = Math.Max(0, jogador.FichasDeHonra – 1);</p><p>jogador.FichasDeFortuna += 1; }),</p><p>27. new CartaDeEvento(“Rivalidade na Guarda: Um rival na Guarda do</p><p>Rei desafia você. Volte duas casas enquanto lida com a situação, mas ganhe</p><p>uma ficha de Fortuna pelo aprendizado.”, jogador => { jogador.Posicao =</p><p>Math.Max(1, jogador.Posicao – 2); jogador.FichasDeFortuna += 1; }),</p><p>28. new CartaDeEvento(“Conspiração Desvendada: Você descobre uma</p><p>conspiração contra os mosqueteiros. Avance duas casas e ganhe uma ficha de</p><p>Honra.”, jogador => { jogador.Posicao += 2; jogador.FichasDeHonra += 1; }),</p><p>29. new CartaDeEvento(“Perdido em uma Floresta: Durante uma</p><p>missão, você se perde em uma floresta densa. Volte duas casas enquanto</p><p>encontra seu caminho.”, jogador => jogador.Posicao =</p><p>Math.Max(1, jogador.Posicao – 2)),</p><p>37</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>30. new CartaDeEvento(“Presente da Rainha: A Rainha Ana da Áustria</p><p>lhe dá um</p><p>presente por sua lealdade. Ganhe três fichas de Fortuna.”,</p><p>jogador => jogador.FichasDeFortuna += 3)</p><p>31. };</p><p>32. }</p><p>33.</p><p>34. public CartaDeEvento SortearCarta()</p><p>35. {</p><p>36. int indice = random.Next(cartas.Count);</p><p>37. return cartas[indice];</p><p>38. }</p><p>39. }</p><p>Figura 11 – Classe Baralho</p><p>A classe Baralho é fundamental pois adiciona elementos de aleatoriedade e variedade ao gameplay.</p><p>Essa classe encapsula a lógica e a gestão das CartaDeEvento, cada uma representando um evento</p><p>específico que afeta os jogadores de maneiras diversas. Ao explorar a estrutura e o funcionamento dessa</p><p>classe, fica claro como ela contribui significativamente para a dinâmica do jogo.</p><p>Na linha 3 vemos a lista genérica List, escolha que não apenas oferece</p><p>segurança de tipo – assegurando que apenas objetos do tipo CartaDeEvento sejam adicionados à</p><p>lista –, mas também uma série de métodos úteis para manipular a coleção de cartas. Diferentes</p><p>de arrays (de tamanho fixo), as listas são dinâmicas e podem crescer ou diminuir em tempo de</p><p>execução, proporcionando grande flexibilidade e permitindo adicionar ou remover elementos da lista</p><p>sem se preocupar com gerenciamento do tamanho da coleção.</p><p>Isso oferece uma maneira prática de gerenciar as cartas do baralho, pois podemos facilmente</p><p>adicionar novas cartas, removê-las ou percorrer a lista para encontrar uma carta específica. Por exemplo,</p><p>o método SortearCarta() da linha 34 combina a geração de um índice aleatório, fornecido pela classe</p><p>Random, com o acesso indexado oferecido pela lista. Essa abordagem permite selecionar um elemento</p><p>aleatório da lista de maneira eficiente e direta, aproveitando as vantagens de ambas as classes (o que</p><p>seria mais complicado com outras estruturas de dados).</p><p>Essa característica fundamental da lista a diferencia de outras coleções que não permitem acesso</p><p>direto por índice, como HashSet ou LinkedList. Para a dinâmica do jogo, o método SortearCarta()</p><p>é particularmente interessante: utilizando o objeto Random, ele seleciona uma carta aleatória da lista, e</p><p>isso não só garante que o jogo permaneça imprevisível e emocionante, mas também demonstra uma</p><p>aplicação prática do conceito de randomização em programação.</p><p>38</p><p>Unidade I</p><p>No construtor da classe Baralho (linha 6), observamos a injeção de uma dependência, um objeto</p><p>Random – escolha de design inteligente, pois permite que o baralho controle sua própria fonte de</p><p>aleatoriedade, característica essencial em jogos de tabuleiro. Injeção de dependência é um conceito</p><p>utilizado na POO e se refere ao processo de fornecer a um objeto as dependências externas de que</p><p>ele precisa. Em outras palavras, em vez de um objeto criar suas próprias dependências ou utilizar</p><p>instâncias globais, as dependências são passadas para ele, normalmente através do construtor ou de</p><p>métodos específicos.</p><p>Esse conceito é vital para criar um código mais modular, testável e fácil de manter. No contexto</p><p>do jogo, injeção de uma dependência refere-se a como a classe Baralho recebe um objeto Random</p><p>através do seu construtor. Isso significa que o Baralho não gera nem controla sua própria instância</p><p>de Random, mas utiliza uma fornecida de fora. Essa abordagem tem várias vantagens significativas:</p><p>primeiro, aumenta a flexibilidade do código. Ao injetar a dependência, podemos facilmente substituir a</p><p>instância de Random por outra, talvez uma versão mock ou stub para fins de teste, sem alterar o código</p><p>interno da classe Baralho. Isso torna o código mais adaptável e fácil de testar.</p><p>Observação</p><p>Mock e stub são termos usados no contexto de testes de software,</p><p>especialmente em testes automatizados e no desenvolvimento orientado</p><p>a testes (test-driven development – TDD), e se referem a objetos que</p><p>simulam o comportamento de objetos reais de maneira controlada, usados</p><p>principalmente para isolar o código testado de suas dependências externas,</p><p>permitindo que desenvolvedores verifiquem a assertividade do código de</p><p>maneira mais eficiente e isolada.</p><p>Mock é a imitação de um objeto real usada para simular o</p><p>comportamento de uma dependência externa num teste. Seu propósito</p><p>principal é verificar interações entre o objeto sob teste e sua dependência.</p><p>Stub, por outro lado, é mais simples que um mock; também imita um</p><p>objeto real, mas apenas fornece respostas predefinidas para chamadas de</p><p>métodos, independentemente da entrada.</p><p>A principal diferença entre mocks e stubs são suas intenções e</p><p>capacidades. Mocks são mais sofisticados e usados para afirmar como</p><p>o código interage com suas dependências (por exemplo, verificar se um</p><p>método específico foi chamado). Já as stubs são usadas sobretudo para</p><p>fornecer respostas predeterminadas, sem qualquer verificação adicional</p><p>sobre como são usadas pelo código sob teste.</p><p>Além disso, injeção de dependência ajuda a reduzir o acoplamento entre classes. No jogo, a classe</p><p>Baralho não precisa saber como criar ou obter uma instância de Random; ela apenas precisa saber</p><p>que pode usá-la – isso separa as preocupações entre diferentes partes do código, tornando-o mais</p><p>39</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>organizado e manutenível. Por fim, ao utilizar a injeção de dependência, podemos ter um controle mais</p><p>preciso sobre o comportamento do programa. Por exemplo, ao compartilhar uma única instância de</p><p>Random entre diferentes partes do jogo, garantimos uma melhor consistência na geração de números</p><p>aleatórios. Isso pode ser importante em jogos, nos quais a randomização precisa ser justa e previsível</p><p>em certa medida.</p><p>A inicialização da lista cartas dentro do construtor – no qual cada carta é criada com uma descrição</p><p>e um efeito específico (conforme o quadro 4) – é exemplo claro do princípio da responsabilidade única</p><p>(single responsibility). Cada CartaDeEvento é uma entidade autônoma com um propósito claro: modificar</p><p>o estado do jogo de alguma forma quando sorteada.</p><p>Em termos de encapsulamento, a classe Baralho exemplifica bem o conceito. Ela esconde seus</p><p>detalhes internos, como a lista de cartas e a instância de Random, expondo apenas o que é necessário</p><p>para outras partes do jogo interagirem com ela – no caso, o método SortearCarta(). Esse nível de</p><p>abstração é vital em POO, pois permite que partes do código sejam modificadas independentemente,</p><p>sem afetar outras partes.</p><p>Dentro do construtor da classe Baralho, diversas cartas de eventos são criadas e adicionadas à lista</p><p>cartas. Cada CartaDeEvento é inicializada com uma descrição e um efeito, que é onde as expressões</p><p>lambda e Action entram em jogo. Usar Action para definir os efeitos das cartas</p><p>é uma decisão de design sofisticada, como descrevemos ao analisar a classe CartaDeEvento. Ao usá-lo,</p><p>a classe Baralho ganha flexibilidade e poder, pois pode atribuir diferentes ações a diferentes cartas de</p><p>forma muito limpa e organizada. Essa técnica é um ótimo exemplo de como os delegados podem ser</p><p>usados para criar código mais modular e reutilizável.</p><p>Uma expressão lambda em C# é basicamente uma função anônima, ou seja, uma função sem nome</p><p>que pode ser definida no local onde é utilizada. Na figura 11 cada expressão lambda define o efeito de uma</p><p>carta de evento quando for sorteada e aplicada a um jogador. Por exemplo, uma das cartas tem o efeito</p><p>de fazer o jogador avançar duas casas, o que é expresso pela lambda jogador => jogador.Posicao += 2.</p><p>Aqui, jogador é o parâmetro da lambda, representando o jogador que será afetado pelo efeito da carta.</p><p>A expressão jogador.Posicao += 2 é o corpo da lambda e descreve a ação de mover o jogador duas</p><p>casas para frente no tabuleiro. Essas expressões lambda são associadas a um Action, um tipo</p><p>de delegado fornecido pelo C#.</p><p>Lembrete</p><p>Delegado Action é um tipo de referência que pode encapsular um método</p><p>que não retorna um valor e que aceita parâmetros do tipo especificado –</p><p>no caso, um Jogador. Ao criar cada CartaDeEvento, associamos uma dessas</p><p>expressões lambda ao delegado Efeito, que é do tipo Action.</p><p>Quando uma carta é sorteada</p><p>durante o jogo e seu efeito é invocado,</p><p>executa-se essa expressão lambda associada.</p><p>40</p><p>Unidade I</p><p>No código da figura 11, as linhas de 10 a 30 contêm expressões específicas, conforme a necessidade</p><p>do jogo. As expressões lambda e Action conferem ao código uma grande flexibilidade: se no</p><p>futuro decidirmos adicionar novas cartas ao jogo com efeitos diferentes, podemos simplesmente criar</p><p>novas expressões lambda para esses efeitos e associá-las a novas instâncias de CartaDeEvento.</p><p>Outro aspecto relevante para nossa revisão é a função Math.Max, exemplo do uso prático e eficiente</p><p>de uma das bibliotecas fundamentais em C#: a System.Math. É uma coleção de métodos estáticos que</p><p>fornecem funcionalidades matemáticas essenciais, incluindo operações básicas – máximo, mínimo, raiz</p><p>quadrada, além de constantes matemáticas, como PI.</p><p>O uso de Math.Max na classe Baralho (linhas 13, 14, 18, 23 e 29) visa garantir que o valor de uma</p><p>expressão não fique abaixo de determinado limite. No contexto do jogo, isso é crucial para manter sua</p><p>lógica e regras intactas. Por exemplo: ao calcular a nova posição de um jogador após ele tirar uma carta</p><p>que o fez recuar no tabuleiro, Math.Max assegura que a nova posição não seja menor que o valor que</p><p>representa a posição inicial no tabuleiro. Isso evita que a posição se torne negativa, o que não faria</p><p>sentido nesse contexto e poderia causar erros ou comportamento inesperado.</p><p>Esse uso de Math.Max é um excelente exemplo de como funções matemáticas podem ser aplicadas</p><p>em contextos além de simples cálculos, servindo como ferramentas para impor regras lógicas e garantir</p><p>que os dados do programa permaneçam válidos e consistentes. Além disso, a biblioteca System.Math</p><p>é parte integral do .NET Framework e disponibiliza uma gama de funções matemáticas essenciais para</p><p>diversos tipos de aplicativo, desde jogos (como neste caso) até aplicações científicas e financeiras.</p><p>A vantagem de bibliotecas-padrão como essa é que elas são otimizadas, bem testadas e confiáveis, o</p><p>que reduz a chance de erros e inconsistências se comparadas a funções implementadas manualmente.</p><p>O método jogador.PausarProximoTurno() (linha 20) é uma representação clara e eficiente de como</p><p>as regras e mecânicas do jogo são integradas na programação. Pertencente à classe Jogador, encapsula</p><p>a lógica de alterar o estado de um jogador para indicar que ele deve pausar seu próximo turno.</p><p>Quando um jogador tira uma carta específica do baralho, pode ser que ele precise pular seu próximo</p><p>turno – comportamento modelado no código através da chamada jogador.PausarProximoTurno.</p><p>Essa chamada de método é um exemplo de como os efeitos das cartas interagem diretamente com os</p><p>objetos Jogador, modificando seu estado de acordo com as regras do jogo. É um exemplo da abstração</p><p>em POO: ações complexas são encapsuladas em métodos que podem ser facilmente chamados e</p><p>gerenciados. Ao usar PausarProximoTurno, a classe Baralho não precisa conhecer os detalhes internos</p><p>de como um jogador pausa seu turno; ela simplesmente sabe que, ao chamar o método, o jogador</p><p>afetado terá seu próximo turno pausado de acordo com as regras.</p><p>Neste tópico já detalhamos o desenvolvimento das classes Programa, RoletaDeDuelo, Casa, Tabuleiro,</p><p>CartaDeEvento e Baralho da figura 3. Também criamos duas enumerações: ResultadoDuelo e TipoCasa.</p><p>Vamos detalhar agora a classe Jogador, já usada em CartaDeEvento. Ela representa um jogador que</p><p>mantém informações sobre seu estado e fornece métodos para manipular esse estado de acordo com</p><p>as regras estipuladas.</p><p>41</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>1. class Jogador</p><p>2. {</p><p>3. public string Nome { get; set; }</p><p>4. public int Posicao { get; set; }</p><p>5. public int FichasDeFortuna { get; set; }</p><p>6. public int FichasDeHonra { get; set; }</p><p>7. public bool DevePausar { get; set; } // Indica se o jogador deve</p><p>pausar no próximo turno</p><p>8. public bool NoReconhecimentoDaCorte { get; set; } = false;</p><p>9. public bool Eliminado { get; set; } = false;</p><p>10. private Random aleatorio;</p><p>11.</p><p>12.</p><p>13. // Construtor do Jogador</p><p>14. public Jogador(string nome, Random random)</p><p>15. {</p><p>16. aleatorio = random;</p><p>17. Nome = nome;</p><p>18. Posicao = 0; // Todos começam na posição inicial</p><p>19. FichasDeFortuna = 5; // Valor inicial de fichas de fortuna</p><p>20. FichasDeHonra = 5; // Valor inicial de fichas de honra</p><p>21. }</p><p>22.</p><p>23. // Método para pausar o próximo turno do jogador</p><p>24. public void PausarProximoTurno()</p><p>25. {</p><p>26. DevePausar = true;</p><p>27. }</p><p>28.</p><p>29. // Método para atualizar o estado do jogador a cada turno</p><p>30. public void AtualizarTurno(int numeroTotalDeCasas)</p><p>31. {</p><p>42</p><p>Unidade I</p><p>32. if (DevePausar)</p><p>33. {</p><p>34. Console.WriteLine($”{Nome} está pausando este turno.”);</p><p>35. DevePausar = false;</p><p>36. return; // Adiciona um retorno aqui para evitar movimento</p><p>adicional neste turno</p><p>37. }</p><p>38.</p><p>39. int movimento = RolarDado();</p><p>40. Posicao += movimento;</p><p>41.</p><p>42. // Garante que a posição não ultrapasse o número de casas no</p><p>tabuleiro</p><p>43. if (Posicao > numeroTotalDeCasas)</p><p>44. {</p><p>45. Posicao = numeroTotalDeCasas; // Ajusta para a última casa</p><p>do tabuleiro</p><p>46. }</p><p>47.</p><p>48. Console.WriteLine($”{Nome} avançou {movimento} casas até a</p><p>posição {Posicao}.”);</p><p>49.</p><p>50. }</p><p>51.</p><p>52.</p><p>53.</p><p>54. private int RolarDado()</p><p>55. {</p><p>56. return aleatorio.Next(1, 7); // Dado de 6 faces</p><p>57. }</p><p>58.</p><p>59. }</p><p>Figura 12 – Classe Jogador</p><p>43</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>Comecemos pela estrutura da classe Jogador, que tem várias propriedades para armazenar</p><p>informações essenciais sobre ele, como Nome, Posicao, FichasDeFortuna e FichasDeHonra (linhas 3,</p><p>4, 5 e 6, respectivamente), acessíveis publicamente, permitindo que outras partes do programa (como</p><p>a classe Jogo) interajam com elas. Além disso, existem propriedades booleanas, como DevePausar,</p><p>NoReconhecimentoDaCorte e Eliminado (linhas 7, 8 e 9, respectivamente), usadas para controlar o fluxo</p><p>do jogo baseado no estado do jogador.</p><p>Observe que NoReconhecimentoDaCorte está inicializado como false, uma vez que só será true</p><p>quando o jogador chegar à casa 60. Similarmente, Eliminado também foi inicializado como false, pois</p><p>só será true após o jogador chegar à casa 60, escolher a Roleta do Julgamento Real, a roleta ser girada</p><p>e ele ter o revés de ser eliminado. Esse registro é importante se considerarmos que um eliminado não</p><p>pode mais participar de duelos nem das demais etapas do jogo.</p><p>A propriedade Nome é um simples armazenamento de string que identifica o jogador. Posicao</p><p>é um inteiro que rastreia a posição do jogador no tabuleiro, fundamental para a mecânica do jogo.</p><p>As FichasDeFortuna e FichasDeHonra são contadores numéricos que podem influenciar o resultado e</p><p>representam recursos que o jogador acumula durante a jornada.</p><p>O construtor da classe Jogador inicializa essas propriedades de acordo com as especificações dadas</p><p>(vide quadro 1), recebe o nome de um dos quatro mosqueteiros e uma instância de Random que pode</p><p>ser usada para ações que requerem aleatoriedade. A posição inicial do jogador é zero, ou seja, todos os</p><p>jogadores começam do mesmo ponto no tabuleiro.</p><p>Um aspecto importante da classe Jogador é como ela gerencia o estado e as regras através de</p><p>seus métodos. O método PausarProximoTurno (linha 24), por exemplo, define a propriedade DevePausar</p><p>como verdadeira, indicando que o jogador deve pular seu próximo turno. Isso é um exemplo claro de</p><p>como a lógica do jogo é incorporada diretamente nos objetos que representam os participantes.</p><p>O método AtualizarTurno (linha 30) encapsula a lógica do que acontece com um jogador a cada</p><p>turno (ou rodada). Se ele deve pausar seu turno (DevePausar é verdadeiro), o método redefine essa</p><p>variável para falso e retorna, indicando o fim do turno do jogador. Caso contrário, o método simula o</p><p>movimento do jogador no tabuleiro usando a função RolarDado, que é uma chamada ao gerador de</p><p>números aleatórios para determinar quantas casas ele deve mover. Isso ilustra</p><p>a integração da classe</p><p>Jogador com outros componentes, como o dado virtual.</p><p>Além disso, o método AtualizarTurno verifica se a posição do jogador após o movimento excede o</p><p>número de casas disponíveis. Se isso acontecer, ajusta sua posição para ser igual ao número máximo de</p><p>casas, garantindo que ele não ultrapasse o limite do tabuleiro (note que aqui não foi necessário usar</p><p>exceções, pois arbitrariamente resolvemos a questão). É importante utilizar exceções de forma judiciosa,</p><p>apenas em situações verdadeiramente excepcionais, uma vez que o processo de lidar com elas pode ser</p><p>custoso em termos de desempenho. Esse cuidado com as condições de limite é um exemplo de como a</p><p>classe Jogador mantém a integridade do estado do jogo.</p><p>44</p><p>Unidade I</p><p>Nas linhas 34 e 48 a interpolação de strings cria mensagens informativas e personalizadas com base</p><p>no estado atual do jogador. Trata-se de um recurso de muitas linguagens de programação modernas,</p><p>incluindo C#, que facilita a inserção de valores de variáveis dentro de strings. Essencialmente, permite</p><p>a incorporação direta de variáveis e expressões dentro de uma string literal sem precisar de concatenação</p><p>explícita nem usar métodos de formatação de strings. Isso é feito usando o símbolo $ antes da string e</p><p>colocando as variáveis ou expressões entre chaves dentro dela (marcadas por { }).</p><p>Por exemplo: a linha 34 usa interpolação para inserir o valor da propriedade Nome do jogador</p><p>diretamente na mensagem. Isso torna o código mais legível e direto, pois você pode ver claramente</p><p>o layout da mensagem final e onde cada variável será inserida. Da mesma forma, a linha 48 combina</p><p>a interpolação de múltiplas variáveis – Nome, movimento e Posicao – para formar uma mensagem</p><p>detalhada sobre o movimento dele no tabuleiro.</p><p>Além desses exemplos, a interpolação de strings poderia ser usada de várias outras maneiras. Por</p><p>exemplo: se quisermos informar os participantes sobre o número de fichas de fortuna ou honra de um</p><p>jogador, podemos usar uma linha como Console.WriteLine($“{Nome} tem {FichasDeFortuna} fichas de</p><p>fortuna e {FichasDeHonra} fichas de honra.”). Isso forneceria uma atualização instantânea e clara sobre</p><p>o status do jogador em termos de recursos no jogo.</p><p>Outro detalhe na classe Jogador é a implementação do método RolarDado, que encapsula a ideia de</p><p>rolar um dado de seis faces – operação comum em jogos de tabuleiro. Ao encapsular essa lógica num</p><p>método separado, o código fica mais organizado e reutilizável, pois qualquer ação no jogo que requeira</p><p>rolar um dado pode simplesmente chamar esse método.</p><p>Finalmente, vamos completar o código-fonte analisando a classe Jogo. Optamos por deixá-la para</p><p>o final por dois motivos: é uma classe mais extensa (com mais linhas de código) do que as anteriores,</p><p>e seu entendimento é facilitado agora, considerando que ela se integra com as demais classes e que</p><p>todas as classes do jogo já foram devidamente esmiuçadas. A classe Jogo serve como espinha dorsal de</p><p>um tabuleiro virtual; ela encapsula sua lógica central, gerenciando o estado e a interação do jogo entre</p><p>diferentes componentes.</p><p>A classe Jogo é responsável por iniciá-lo, executar cada turno e determinar quando ele termina,</p><p>seguindo regras específicas definidas em seu corpo; é um exemplo de como a orientação a</p><p>objetos modela um sistema complexo. A classe tem atributos privados, como tabuleiro, jogadores,</p><p>baralhoDeEventos, aleatorio e roletaDeDuelo, cada um representando um aspecto distinto. Esses</p><p>atributos são instâncias de outras classes, demonstrando o conceito de composição, onde um objeto</p><p>é composto de outros objetos.</p><p>A classe também exemplifica o uso do encapsulamento: detalhes internos do funcionamento do jogo</p><p>são escondidos do mundo exterior, e a interação com a classe é feita via métodos públicos bem definidos.</p><p>Isso é evidenciado pelo método IniciarJogo(), que serve como ponto de entrada, e ExecutarJogo(), que</p><p>contém a lógica principal do seu loop.</p><p>45</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>1. class Jogo</p><p>2. {</p><p>3. private Tabuleiro tabuleiro;</p><p>4. private List jogadores;</p><p>5. private Baralho baralhoDeEventos;</p><p>6. private Random aleatorio;</p><p>7. private RoletaDeDuelo roletaDeDuelo;</p><p>8. // Construtor do Jogo</p><p>9. public Jogo(Random random)</p><p>10. {</p><p>11. aleatorio = random;</p><p>12. tabuleiro = new Tabuleiro(60);</p><p>13. jogadores = new List() {</p><p>14. new Jogador(“D’Artagnan”, random),</p><p>15. new Jogador(“Athos”, random),</p><p>16. new Jogador(“Porthos”, random),</p><p>17. new Jogador(“Aramis”, random)</p><p>18. };</p><p>19. baralhoDeEventos = new Baralho(random);</p><p>20. roletaDeDuelo = new RoletaDeDuelo(random);</p><p>21. }</p><p>22. // Demais métodos</p><p>23. }</p><p>Figura 13 – Classe Jogo: primeira parte – atributos e construtor</p><p>A classe Jogo no código-fonte da figura 13 contém uma série de atributos privados fundamentais</p><p>para sua funcionalidade e para entender como se aplica a POO, incluindo tabuleiro, jogadores,</p><p>baralhoDeEventos, aleatorio e roletaDeDuelo. Cada um representa um componente essencial do jogo, e</p><p>todos juntos formam a estrutura sobre a qual ele é construído e executado:</p><p>46</p><p>Unidade I</p><p>• tabuleiro: instância da classe Tabuleiro que representa o espaço físico onde os jogadores</p><p>movem as peças. Esse uso demonstra a composição em POO, onde um objeto (no caso, Jogo)</p><p>contém outro objeto (tabuleiro) como parte de sua estrutura.</p><p>• jogadores: lista de instâncias da classe Jogador. Cada objeto Jogador armazena informações</p><p>sobre um jogador individual, como nome, posição no tabuleiro e pontuação. A lista genérica</p><p>(List) exemplifica o uso de coleções em C# para gerenciar múltiplas entidades</p><p>do mesmo tipo.</p><p>• baralhoDeEventos: representa o conjunto de cartas de evento, sendo uma instância da classe</p><p>Baralho, atributo crucial para introduzir elementos de aleatoriedade e eventos dinâmicos no</p><p>jogo, mostrando como a classe Jogo integra diferentes componentes para poder criar uma</p><p>experiência diversificada.</p><p>• aleatorio: objeto da classe Random que gera números aleatórios. Esse atributo é essencial para as</p><p>mecânicas de aleatoriedade do jogo, como lançamento de dados ou sorteio de cartas, ilustrando</p><p>como a aleatoriedade é implementada em programas.</p><p>• roletaDeDuelo: instância da classe RoletaDeDuelo que resolve duelos. Assim como o baralho de</p><p>eventos, essa classe adiciona mais um elemento de incerteza e estratégia ao jogo.</p><p>Lembrete</p><p>O construtor da classe Jogo (linha 11) recebe um objeto Random como</p><p>parâmetro. Como vimos, essa abordagem exemplifica um importante</p><p>princípio de design de software conhecido como injeção de dependência.</p><p>Em vez de a classe Jogo criar sua própria instância de Random, ela recebe uma de fora. Isso aumenta</p><p>a flexibilidade e a testabilidade da classe, permitindo, por exemplo, que um objeto Random com uma</p><p>semente específica seja fornecido para testes ou simulações. Dentro do construtor, vários componentes</p><p>do jogo são inicializados:</p><p>• O tabuleiro é criado com um número específico de casas (linha 12).</p><p>• Uma lista de jogadores é inicializada, e cada jogador recebe um nome e o mesmo objeto Random</p><p>para manter a consistência na geração de números aleatórios em todo o jogo (linhas 15 a 20).</p><p>• O baralho de eventos e a roleta de duelos são também inicializados com o mesmo objeto</p><p>Random, garantindo que todos os elementos aleatórios sejam sincronizados (linhas 19 e 20,</p><p>respectivamente).</p><p>Essa inicialização demonstra como os construtores em C# configuram um objeto num estado inicial</p><p>válido, garantindo que todos os atributos necessários sejam configurados antes que o objeto seja usado.</p><p>47</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>Além disso, a passagem do mesmo objeto Random para diferentes componentes do jogo é um exemplo</p><p>de reutilização de objetos e manutenção da consistência.</p><p>O código da classe termina na linha 23; na linha 22 inseriremos as demais partes que virão a seguir</p><p>neste tópico.</p><p>1. // Método para rodar o jogo (linha 22 da Figura 13)</p><p>2. public void ExecutarJogo()</p><p>3. {</p><p>4. bool jogoTerminado</p><p>= false;</p><p>5.</p><p>6. while (!jogoTerminado)</p><p>7. {</p><p>8. foreach (Jogador jogador in jogadores)</p><p>9. {</p><p>10. if (jogador.Eliminado)</p><p>11. {</p><p>12. continue; // Ignora jogadores eliminados</p><p>13. }</p><p>14. Console.WriteLine($”\nÉ a vez de {jogador.Nome}.</p><p>Casa= {jogador.Posicao}, Fortuna= {jogador.FichasDeFortuna}, Honra=</p><p>{jogador.FichasDeHonra}”);</p><p>15. Console.WriteLine(“Pressione qualquer tecla para jogar.”);</p><p>16. Console.ReadKey(); // Aguarda o jogador pressionar</p><p>uma tecla</p><p>17.</p><p>18. JogarTurno(jogador);</p><p>19.</p><p>20. // Verifique o fim do jogo após cada turno</p><p>21. if (TodosJogadoresNoReconhecimentoOuDesprezo())</p><p>22. {</p><p>23. AnunciarVencedor();</p><p>48</p><p>Unidade I</p><p>24. jogoTerminado = true;</p><p>25. break;</p><p>26. }</p><p>27. }</p><p>28. }</p><p>29.</p><p>30. Console.WriteLine(“Pressione qualquer tecla para sair.”);</p><p>31. Console.ReadKey(); // Aguarda o usuário pressionar uma tecla</p><p>antes de fechar</p><p>32. }</p><p>33. // Demais métodos</p><p>Figura 14 – Classe Jogo: segunda parte – método ExecutarJogo</p><p>O método ExecutarJogo desempenha papel central e encapsula a lógica principal do jogo, ilustrando</p><p>conceitos fundamentais da programação C#, como loops, condicionais e interação com o usuário, além</p><p>de revelar o fluxo do jogo e como ele é controlado, sendo executado dentro de um loop while (linha 6),</p><p>que continua até a condição de término do jogo (jogoTerminado) ser verdadeira.</p><p>Esse uso de um loop while é típico nos jogos em que uma série de ações se repete a cada rodada</p><p>até se atingir uma condição de parada. Dentro desse loop há um loop foreach (linha 8) iterando a lista</p><p>de jogadores. Esse segundo loop é exemplo do uso de estruturas de repetição para processar coleções</p><p>(no caso, a coleção de jogadores participantes). A cada iteração, o método verifica se o jogador atual foi</p><p>eliminado (jogador.Eliminado), o que demonstra o uso de condicionais (if) para controlar o fluxo do jogo</p><p>com base no estado dos objetos (linha 10).</p><p>Participantes eliminados são ignorados no turno atual (linha 12), mostrando como a lógica do</p><p>jogo é adaptada para diferentes situações. Em seguida, informações sobre o jogador atual são exibidas</p><p>usando interpolação de strings (linha 14), e a entrada do usuário é solicitada pelo Console.ReadKey</p><p>(linha 16) – aspecto que ilustra a interação simples com o usuário, comum em aplicações de console.</p><p>O método JogarTurno(jogador) é chamado para cada jogador e encapsula a lógica de um único turno</p><p>(linha 18). Aqui a classe Jogo delega parte de sua responsabilidade para outro método, demonstrando</p><p>o princípio de responsabilidade única e como um programa pode ser organizado em partes menores e</p><p>mais gerenciáveis.</p><p>Após cada turno, o método ExecutarJogo verifica se ele terminou, utilizando</p><p>TodosJogadoresNoReconhecimentoOuDesprezo. Esse método exemplifica como os estados dos</p><p>objetos são usados para tomar decisões, integrando estados individuais de todos os participantes para</p><p>determinar o estado geral do jogo. Finalmente, se o jogo terminar, AnunciarVencedor é chamado e o loop</p><p>é encerrado, o que mostra como um programa pode sair de um loop e proceder para a próxima etapa do</p><p>49</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>fluxo de execução. O método ExecutarJogo termina solicitando que o usuário pressione qualquer tecla</p><p>para sair, concluindo a execução.</p><p>Agora, confira este código:</p><p>1. // Método para jogar um turno</p><p>2. private void JogarTurno(Jogador jogador)</p><p>3. {</p><p>4. // Verifica se o jogador está no Reconhecimento da Corte e, se sim,</p><p>pula o resto do turno</p><p>5. if (jogador.NoReconhecimentoDaCorte)</p><p>6. {</p><p>7. Console.WriteLine($”{jogador.Nome} está no Reconhecimento da</p><p>Corte e aguarda os demais jogadores.”);</p><p>8. return;</p><p>9. }</p><p>10.</p><p>11. if (jogador.DevePausar)</p><p>12. {</p><p>13. jogador.AtualizarTurno(tabuleiro.NumeroDeCasas); // Isso</p><p>cuidará da lógica de pausa</p><p>14. }</p><p>15. else</p><p>16. {</p><p>17. // Rolar o dado para movimento</p><p>18. int movimento = RolarDado();</p><p>19. Console.WriteLine($”{jogador.Nome} rolou {movimento} no</p><p>dado.”);</p><p>20. jogador.Posicao += movimento;</p><p>21.</p><p>22. if (jogador.Posicao > tabuleiro.NumeroDeCasas)</p><p>23. {</p><p>24. jogador.Posicao = tabuleiro.NumeroDeCasas; // Garante</p><p>que não ultrapasse a última casa</p><p>25. }</p><p>50</p><p>Unidade I</p><p>26.</p><p>27. // Verificar a casa no tabuleiro</p><p>28. Casa casaAtual = tabuleiro.ObterCasa(jogador.Posicao);</p><p>29. ProcessarCasa(casaAtual, jogador);</p><p>30. }</p><p>31. }</p><p>32. // Demais métodos</p><p>Figura 15 – Classe Jogo: terceira parte – método JogarTurno</p><p>Analisaremos agora o método JogarTurno, que dita o fluxo do turno de cada jogador. No início de</p><p>cada um, primeiro verifica-se se o jogador atual está no estado “reconhecimento da corte” (conforme</p><p>condição da linha 5 na figura 15). Se estiver, ele não executa mais nenhuma ação nesse turno, refletindo</p><p>uma pausa estratégica no jogo (lembre-se: o jogador na casa 60 deverá esperar os demais caso não tenha</p><p>sido eliminado). Se não estiver no reconhecimento da corte, o método então verifica se ele deve pausar</p><p>seu turno (linha 11), condição que pode ter sido imposta por eventos anteriores (cartas de evento).</p><p>Essa lógica ilustra como o estado do participante pode ser afetado por ações anteriores, aumentando a</p><p>complexidade e a imprevisibilidade do jogo.</p><p>Caso o jogador esteja ativo, o método prossegue com RolarDado (linha 18), que gera um número</p><p>aleatório e simula a rolagem de um dado físico, introduzindo o elemento de sorte. Essa rolagem determina</p><p>quantas casas o jogador avança – mecânica central em muitos jogos de tabuleiro; após o movimento,</p><p>sua posição é cuidadosamente ajustada (linhas 20 a 25) para garantir que não ultrapasse os limites do</p><p>tabuleiro, mostrando atenção aos detalhes na programação.</p><p>Em seguida, o jogo executa ações com base na casa onde o participante caiu. Se ele pousar numa</p><p>casa especial (como um duelo ou evento), ações correspondentes são executadas, cada uma com suas</p><p>próprias regras e consequências – como duelos, eventos aleatórios e encontros especiais –, que trazem</p><p>uma rica variedade e garantem que nenhum turno seja igual ao outro. Isso não apenas mantém os</p><p>jogadores engajados, mas também reflete a temática dos mosqueteiros, onde aventura e incerteza são</p><p>elementos constantes.</p><p>Métodos auxiliares – como RolarDado, ExecutarEvento e ExecutarDuelo – são componentes vitais</p><p>que enriquecem a experiência do jogo, incorporando elementos de aleatoriedade, estratégia e interatividade.</p><p>Na linha 2 da figura 16, o método RolarDado é exemplo clássico de introdução de aleatoriedade em</p><p>jogos de tabuleiro.</p><p>Lembrete</p><p>Esse método simples gera um número aleatório entre um e seis,</p><p>simulando a rolagem de um dado de seis faces.</p><p>51</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>1. // Método para rolar o dado</p><p>2. private int RolarDado()</p><p>3. {</p><p>4. return aleatorio.Next(1, 7); // Dado de 6 faces</p><p>5. }</p><p>6.</p><p>7. // Método para executar um evento</p><p>8. private void ExecutarEvento(Jogador jogador)</p><p>9. {</p><p>10. CartaDeEvento carta = baralhoDeEventos.SortearCarta();</p><p>11. Console.WriteLine($”Evento: {carta.Descricao}”);</p><p>12. carta.Efeito(jogador);</p><p>13. }</p><p>14.</p><p>15. private void ExecutarDuelo(Jogador jogador)</p><p>16. {</p><p>17. Console.WriteLine($”{jogador.Nome} está em um duelo!”);</p><p>18. ResultadoDuelo resultado = roletaDeDuelo.Girar();</p><p>19.</p><p>20. switch (resultado)</p><p>21. {</p><p>22. case ResultadoDuelo.Vitoria:</p><p>23. Console.WriteLine(“Você venceu o duelo! Avance 3 casas</p><p>e ganhe 2 fichas de Honra.”);</p><p>24. jogador.Posicao = Math.Min(jogador.Posicao + 3,</p><p>tabuleiro.NumeroDeCasas);</p><p>25. jogador.FichasDeHonra += 2;</p><p>26. break;</p><p>27. case ResultadoDuelo.Empate:</p><p>28. Console.WriteLine(“O duelo terminou em empate. Fique</p><p>onde está e ganhe 1 ficha de Honra.”);</p><p>29. jogador.FichasDeHonra += 1;</p><p>30. break;</p><p>52</p><p>Unidade I</p><p>31. case ResultadoDuelo.Derrota:</p><p>32. Console.WriteLine(“Você foi derrotado. Volte 2 casas</p><p>e perca 1 ficha de Honra.”);</p><p>33. jogador.Posicao = Math.Max(jogador.Posicao – 2, 1);</p><p>34. jogador.FichasDeHonra = Math.Max(jogador.FichasDeHonra</p><p>– 1, 0);</p><p>35. break;</p><p>36. }</p><p>37. }</p><p>38. // Demais métodos</p><p>Figura 16 – Classe Jogo: quarta parte – métodos auxiliares</p><p>ExecutarEvento (linha 8) é outro método fundamental que ativa os eventos aleatórios durante o</p><p>jogo. Ao chamá-lo, uma carta de evento é selecionada aleatoriamente do baralho de eventos, e o efeito</p><p>descrito é aplicado ao jogador. Cada carta de evento, com seu efeito único, garante que nenhum jogo</p><p>seja igual ao outro.</p><p>Já o método ExecutarDuelo (linha 15) traz à tona a temática de duelos de esgrima, recorrente na</p><p>era dos mosqueteiros, sendo chamado quando um jogador cai numa casa de duelo, e a roleta é girada</p><p>para determinar o resultado: vitória, empate ou derrota (lembre-se que criamos uma enumeração para</p><p>representar esses desfechos). Cada resultado tem diferentes implicações para a posição do jogador no</p><p>tabuleiro e seus recursos, como fichas de honra ou fortuna. Esse método não só reforça a temática do</p><p>jogo, mas também cria momentos de tensão e excitação.</p><p>Quando um jogador vence um duelo, a regra indica que ele deve avançar três casas, no entanto</p><p>deve-se respeitar uma condição fundamental: não ultrapassar o número total de casas disponíveis no</p><p>tabuleiro; e aqui a função Math.Min entra em ação como ferramenta de segurança. Ela compara dois</p><p>valores: posição potencial do jogador após avançar três casas e número total de casas no tabuleiro.</p><p>O menor dos dois valores é escolhido como sua nova posição.</p><p>O método pertence à biblioteca System.Math, e a utilizamos (assim como Math.Max) para lidar com</p><p>limites de valor, garantindo que os dados permaneçam dentro de um intervalo válido e prevenindo erros</p><p>que poderiam surgir se ele ultrapassasse o fim do tabuleiro. Esses métodos auxiliares exemplificam como</p><p>diferentes partes de um sistema de jogo podem interagir e criar uma experiência coesa, sendo chamados</p><p>em momentos apropriados durante o jogo, cada um contribuindo para progredir de maneira significativa.</p><p>Na figura 17, o método ExecutarDueloDeHonra começa com a determinação dos oponentes possíveis</p><p>para um desafiante específico, utilizando a palavra-chave var e uma expressão lambda (linha 4). Var é</p><p>uma prática comum em C# para declarar variáveis quando o tipo é óbvio ou redundante. Na declaração</p><p>da linha 4, var oponentesPossiveis é uma lista de jogadores elegíveis para um duelo, gerada pelo método</p><p>Where da biblioteca LINQ, que filtra a lista de jogadores baseando-se em critérios específicos definidos</p><p>na expressão lambda j => j!= desafiante && !j.NoReconhecimentoDaCorte && !j.Eliminado.</p><p>53</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>1. private void ExecutarDueloDeHonra(Jogador desafiante)</p><p>2. {</p><p>3.</p><p>4. var oponentesPossiveis = jogadores.Where(j => j != desafiante &&</p><p>!j.NoReconhecimentoDaCorte && !j.Eliminado).ToList();</p><p>5.</p><p>6. if (oponentesPossiveis.Count == 0)</p><p>7. {</p><p>8. Console.WriteLine(“Não há oponentes disponíveis para o duelo.”);</p><p>9. return;</p><p>10. }</p><p>11.</p><p>12. Jogador oponente;</p><p>13. if (oponentesPossiveis.Count == 1)</p><p>14. {</p><p>15. // Se houver apenas um oponente possível, escolha-o</p><p>automaticamente</p><p>16. oponente = oponentesPossiveis[0];</p><p>17. }</p><p>18. else</p><p>19. {</p><p>20. // Se houver mais de um oponente possível, permita que o</p><p>jogador escolha</p><p>21. oponente = EscolherOponente(desafiante, oponentesPossiveis);</p><p>22. }</p><p>23.</p><p>24. Console.WriteLine($”{desafiante.Nome} desafiou outro jogador</p><p>para um Duelo de Honra!”);</p><p>25.</p><p>26. ResultadoDuelo resultado = roletaDeDuelo.Girar();</p><p>27.</p><p>28. switch (resultado)</p><p>29. {</p><p>30. case ResultadoDuelo.Vitoria:</p><p>54</p><p>Unidade I</p><p>31. Console.WriteLine($”{desafiante.Nome} venceu o duelo e</p><p>ganhou 2 fichas de Honra do oponente.”);</p><p>32. TransferirFichasDeHonra(oponente, desafiante, 2);</p><p>33. break;</p><p>34. case ResultadoDuelo.Empate:</p><p>35. Console.WriteLine(“O duelo terminou em empate.</p><p>Ninguém perde ou ganha fichas.”);</p><p>36. break;</p><p>37. case ResultadoDuelo.Derrota:</p><p>38. Console.WriteLine($”{desafiante.Nome} foi derrotado e</p><p>perdeu 2 fichas de Honra para o oponente.”);</p><p>39. TransferirFichasDeHonra(desafiante, oponente, 2);</p><p>40. break;</p><p>41. }</p><p>42. }</p><p>43. // Demais métodos</p><p>Figura 17 – Classe Jogo: quinta parte – método auxiliar ExecutarDueloDeHonra</p><p>Expressão lambda é uma função anônima concisa que especifica que um jogador é um oponente</p><p>possível se ele não for o desafiante, não estiver no reconhecimento da corte e não tiver sido eliminado;</p><p>é um exemplo eficiente de como critérios múltiplos podem ser aplicados para filtrar dados em uma</p><p>coleção. Aplicado o filtro, o método ToList() converte o resultado da expressão lambda, que é um</p><p>IEnumerable, em uma lista (List). Isso é feito por várias razões, sendo a principal a necessidade</p><p>de materializar os resultados do filtro em uma coleção concreta que possa ser utilizada posteriormente.</p><p>Conversão para uma lista também permite operações adicionais, como contar o número de elementos</p><p>ou acessar elementos específicos por índice – o que seria mais complicado com um IEnumerable. Com</p><p>a lista de oponentes possíveis na mão, o código verifica se há jogadores disponíveis para duelar usando o</p><p>método Count. Se não houver nenhum (linha 6), o método retorna e o duelo não sucede; caso contrário,</p><p>se houver apenas um oponente possível (linha 13), este é automaticamente escolhido. Se houver mais de</p><p>um, a escolha é delegada ao método EscolherOponente, que permite uma seleção mais interativa.</p><p>A parte seguinte do método envolve a execução do duelo propriamente dita, utilizando a RoletaDeDuelo</p><p>para determinar o resultado, que trará diferentes ações a tomar, como ganhar ou perder fichas de</p><p>honra, que se concretizam por chamadas de métodos específicos – como TransferirFichasDeHonra –,</p><p>que manipulam o estado dos jogadores de acordo com as regras.</p><p>O método AudienciaComORei representa uma etapa crítica no jogo, na qual um jogador interage</p><p>com um cenário-chave – a audiência com o rei –, incorporando lógica de decisão, aleatoriedade</p><p>e consequências significativas para seu andamento.</p><p>55</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>1. private void AudienciaComORei(Jogador jogador)</p><p>2. {</p><p>3. Console.WriteLine($”{jogador.Nome} chegou à Audiência com o Rei!”);</p><p>4.</p><p>5. bool escolhaValida = false;</p><p>6. while (!escolhaValida)</p><p>7. {</p><p>8. Console.WriteLine(“Escolha [1] para Reconhecimento da Corte ou</p><p>[2] para Roleta do Julgamento Real:”);</p><p>9. string escolha = Console.ReadLine();</p><p>10.</p><p>11. if (escolha == “1”)</p><p>12. {</p><p>13. jogador.NoReconhecimentoDaCorte = true;</p><p>14. Console.WriteLine($”{jogador.Nome} escolheu o Reconhecimento</p><p>da Corte.”);</p><p>15. escolhaValida = true;</p><p>16. }</p><p>17. else if (escolha == “2”)</p><p>18. {</p><p>19. bool vitoria = GirarRoletaDoJulgamentoReal(jogador);</p><p>20. if (vitoria)</p><p>21. {</p><p>22. Console.WriteLine($”{jogador.Nome} foi condecorado</p><p>pelo Rei e venceu o jogo!”);</p><p>23. AnunciarVencedorImediato(jogador);</p><p>24. Environment.Exit(0); // Encerra o programa</p><p>25. }</p><p>26. else</p><p>27. {</p><p>28. Console.WriteLine($”{jogador.Nome} recebeu o Desprezo</p><p>da Corte e foi eliminado do jogo.”);</p><p>29. jogador.Eliminado = true;</p><p>56</p><p>Unidade I</p><p>30. }</p><p>31. escolhaValida = true;</p><p>32. }</p><p>33. else</p><p>34. {</p><p>35. Console.WriteLine(“Escolha inválida, tente novamente.”);</p><p>36. }</p><p>37. }</p><p>38. }</p><p>39. // Demais métodos</p><p>Figura 18 – Classe Jogo: sexta parte – método auxiliar AudienciaComORei</p><p>Inicialmente, o método informa que o jogador chegou à audiência com o rei, um momento potencialmente</p><p>decisivo. Interpola-se a string na linha 3 para personalizar a mensagem com o nome do jogador,</p><p>aumentando a imersão e a personalização da experiência. Em seguida, entra-se em um loop while</p><p>que continua até o jogador fazer uma escolha válida. Esse loop é uma técnica comum, que garante ao</p><p>usuário fornecer uma entrada esperada e válida, evitando erros ou comportamentos inesperados.</p><p>Dentro do loop, o jogador enfrenta um dilema crítico: escolher o reconhecimento da corte ou</p><p>enfrentar</p><p>a roleta do julgamento real (conforme os requisitos do quadro 1). Se o jogador escolher o</p><p>reconhecimento da corte (escolha == “1”), a variável booleana NoReconhecimentoDaCorte do objeto</p><p>jogador é definida como verdadeira. Essa escolha é então anunciada, e a variável escolhaValida é definida</p><p>como verdadeira, terminando o loop e prosseguindo o jogo. Caso ele opte pela Roleta do Julgamento</p><p>Real (escolha == “2”), o método GirarRoletaDoJulgamentoReal é chamado.</p><p>O método retorna um valor booleano que determina se ele vence ou perde nessa roleta. Se o resultado</p><p>for vitória, ele é declarado vencedor, e a função AnunciarVencedorImediato é chamada para lidar com a</p><p>situação. É um método muito simples (figura 19) e apenas gera um retorno na tela do console a respeito</p><p>do vencedor.</p><p>1. private void AnunciarVencedorImediato(Jogador vencedor)</p><p>2. {</p><p>3. Console.WriteLine($”\nO vencedor é {vencedor.Nome}!”);</p><p>4. Console.ReadKey();</p><p>5. }</p><p>6. // Demais métodos</p><p>Figura 19 – Classe Jogo: sétima parte – método auxiliar AnunciarVencedorImediato</p><p>57</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>Na sequência, o método Environment.Exit é usado em seguida para encerrar o programa, indicando</p><p>uma conclusão bem-sucedida. Disponível no namespace System, essa função encerra imediatamente um</p><p>processo em execução e pode ser muito útil em diversas situações. Ao ser chamada, Environment.Exit</p><p>requer um código de saída inteiro, usado pelo sistema operacional para entender o status de término</p><p>do processo. Ele é especialmente importante se o programa fizer parte de um pipeline maior ou se for</p><p>chamado por outros softwares, pois permite comunicar se o processo foi concluído com sucesso.</p><p>Por outro lado, se o resultado for uma derrota (linha 26 da figura 18), o jogador é eliminado, o</p><p>que é refletido pela definição da propriedade Eliminado do objeto jogador como verdadeira. Caso ele</p><p>não escolha nem 1 nem 2, uma mensagem de erro é exibida, e o loop continua, solicitando uma nova</p><p>entrada (linha 35); essa parte do código garante que ele não prossiga sem fazer uma escolha válida,</p><p>mantendo a integridade do fluxo do jogo.</p><p>Vamos analisar agora outros quatro métodos auxiliares:</p><p>• TodosJogadoresNoReconhecimentoOuDesprezo</p><p>• TransferirFichasDeHonra</p><p>• GirarRoletaDoJulgamentoReal</p><p>• TodosJogadoresTerminaram</p><p>1. private bool TodosJogadoresNoReconhecimentoOuDesprezo()</p><p>2. {</p><p>3. return jogadores.All(j => j.NoReconhecimentoDaCorte || j.Posicao</p><p>== tabuleiro.NumeroDeCasas);</p><p>4. }</p><p>5.</p><p>6.</p><p>7. private bool GirarRoletaDoJulgamentoReal(Jogador jogador)</p><p>8. {</p><p>9. // Implementação simplificada. Pode ser substituída por uma mecânica</p><p>mais complexa se necessário.</p><p>10. return roletaDeDuelo.Girar() == ResultadoDuelo.Vitoria;</p><p>11. }</p><p>12.</p><p>13. private bool TodosJogadoresTerminaram()</p><p>14. {</p><p>58</p><p>Unidade I</p><p>15. // Implementação do método para verificar se todos os jogadores</p><p>terminaram o jogo</p><p>16. foreach (var jogador in jogadores)</p><p>17. {</p><p>18. if (jogador.Posicao , e o corpo da função, que</p><p>retorna um valor booleano (true ou false), está à direita.</p><p>59</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>No contexto do método All, se a expressão lambda retornar true para todos os elementos da coleção,</p><p>então o All como um todo retorna true. Se pelo menos um elemento não satisfizer a condição (ou seja,</p><p>a expressão lambda retornar false para esse elemento), All retornará false. Por exemplo, se tivermos</p><p>uma lista de números e quisermos verificar se todos são positivos, podemos usar o All com expressão</p><p>lambda assim:</p><p>numeros.All(n => n > 0).</p><p>Aqui, n => n > 0 é a expressão lambda, onde n é o parâmetro que representa cada elemento da lista</p><p>à medida que All itera sobre ela. Para cada elemento, a expressão verifica se n é maior que zero. Se todos</p><p>os elementos da lista forem positivos, o resultado será true; caso contrário, será false.</p><p>O método GirarRoletaDoJulgamentoReal é outra parte crucial. Ele é invocado quando um jogador</p><p>chega à última casa do tabuleiro e opta por girar a roleta do julgamento real. Através de um sorteio,</p><p>decide-se seu destino – se ele vence (e potencialmente ganha o jogo) ou é eliminado. Na linha 10 da</p><p>figura 20, o método retorna o resultado da comparação: roletaDeDuelo.Girar() == ResultadoDuelo.</p><p>Vitoria. Aqui, roletaDeDuelo.Girar chama o método Girar da classe RoletaDeDuelo, que retorna um valor</p><p>do tipo ResultadoDuelo (que pode ser Vitoria, Empate ou Derrota).</p><p>O valor retornado é então comparado com ResultadoDuelo.Vitoria. Se o resultado do método Girar for</p><p>Vitoria, a expressão será verdadeira (true), caso contrário será falsa (false). É uma implementação direta</p><p>e simples que decide o resultado baseado em uma única rotação da roleta. Portanto, se cada resultado</p><p>tiver a mesma probabilidade de ser escolhido e existem três resultados possíveis, a probabilidade de</p><p>qualquer um deles (incluindo Vitoria) é de um terço, ou aproximadamente 33%.</p><p>Essa probabilidade é importante pois afeta diretamente a experiência do jogador e o equilíbrio</p><p>da partida. A taxa de sucesso de um terço pode ser considerada justa em muitos jogos por oferecer</p><p>uma mistura equilibrada de incerteza e possibilidade de sucesso. No entanto, conforme nos lembra o</p><p>comentário da linha 9, se quiséssemos alterar a dificuldade ou a dinâmica do jogo, poderíamos ajustar</p><p>essas probabilidades modificando a lógica de geração aleatória ou introduzindo outros elementos que</p><p>afetam as chances de cada resultado.</p><p>Para complexificar essa mecânica, poderíamos introduzir fatores adicionais que influenciariam o</p><p>resultado. Por exemplo:</p><p>• Probabilidades variáveis: em vez de ter chances iguais para Vitoria, Empate e Derrota, poderíamos</p><p>ajustar as probabilidades com base em certas condições ou atributos do jogador. Por exemplo, se</p><p>ele tiver um alto número de fichas de honra, as chances de vitória podem aumentar.</p><p>• Múltiplas rodadas: em vez de decidir o resultado com uma única rotação, poderíamos exigir que</p><p>o jogador ganhasse várias rodadas consecutivas para alcançar a vitória. Por exemplo, ele precisa</p><p>conseguir Vitoria duas vezes em três rotações para realmente vencer.</p><p>60</p><p>Unidade I</p><p>• Efeitos acumulativos: o resultado das rotações anteriores poderia afetar as rotações</p><p>subsequentes. Por exemplo, se um jogador perde na primeira rodada, isso poderia diminuir suas</p><p>chances de vitória nas rodadas seguintes, ou talvez ele precise de mais vitórias para compensar a</p><p>derrota inicial.</p><p>• Condições externas: o resultado poderia ser influenciado por eventos externos ou ações de</p><p>outros jogadores. Por exemplo, se um jogador recentemente venceu um duelo, isso poderia</p><p>aumentar suas chances na roleta do</p><p>julgamento real.</p><p>• Elementos de estratégia: os jogadores poderiam ter a opção de apostar fichas de honra ou</p><p>fortuna para aumentar as chances de vencer, adicionando um elemento de risco e recompensa.</p><p>São apenas algumas ideias para adicionar complexidade ao método GirarRoletaDoJulgamentoReal.</p><p>A chave aqui é: ao introduzir mais variáveis e decisões, o jogo se torna mais estratégico e menos</p><p>dependente do acaso, o que pode aumentar o envolvimento e a diversidade da experiência.</p><p>O terceiro método, TodosJogadoresTerminaram, verifica se todos os jogadores alcançaram a</p><p>última casa do tabuleiro – exemplo prático de como iterar uma lista de objetos (jogadores) e verificar</p><p>a propriedade (Posicao) de cada um deles. Já o foreach – estrutura de loop amplamente utilizada</p><p>para percorrer coleções como listas, arrays ou quaisquer outros tipos que implementam a interface</p><p>IEnumerable – proporciona uma maneira simples e eficiente de acessar cada elemento de uma coleção,</p><p>sem precisar gerenciar manualmente um contador ou índice.</p><p>No contexto do foreach, a palavra-chave var declara uma variável que representa o elemento atual</p><p>da coleção iterada (linha 16 da figura 20). Var em C# é uma forma de declarar uma variável implícita,</p><p>cujo compilador infere automaticamente o tipo da variável com base no valor que ela recebe. No caso,</p><p>como jogadores é uma coleção de objetos Jogador, o compilador infere que a variável jogador é do tipo</p><p>Jogador. A principal vantagem do var é que ele torna o código mais limpo e fácil de ler, especialmente</p><p>se o tipo da variável for óbvio ou muito longo para ser digitado.</p><p>Por outro lado, a palavra in no foreach especifica a coleção que estiver sendo percorrida. No exemplo,</p><p>in jogadores indica que o loop foreach deve iterar cada elemento dentro da coleção jogadores. Em cada</p><p>iteração do loop, a variável jogador recebe um elemento da coleção jogadores, permitindo que o código</p><p>dentro do loop acesse propriedades e métodos do objeto Jogador atual.</p><p>Por fim, na linha 27 da figura 20 temos o método TransferirFichasDeHonra, utilizado em duelos,</p><p>que permite transferir fichas de honra de um jogador para outro, demonstrando como se manipulam</p><p>atributos de objetos em C#. Ele encapsula a lógica de transferência e garante que a quantidade de</p><p>fichas transferidas não exceda a quantidade disponível do jogador que está transferindo (note o uso</p><p>de Math.Min na linha 29).</p><p>Agora vamos analisar em detalhe a lógica e os aspectos técnicos de três outros métodos:</p><p>EscolherOponente, TributoAoCardeal e VerificarFimDeJogo, conforme o código-fonte da figura 21:</p><p>61</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>1. // Método para escolher oponente para o duelo</p><p>2. private Jogador EscolherOponente(Jogador desafiante, List</p><p>possiveisOponentes)</p><p>3. {</p><p>4. int indiceEscolhido = -1;</p><p>5.</p><p>6. Console.WriteLine(“Escolha um oponente para o Duelo de Honra:”);</p><p>7. for (int i = 0; i possiveisOponentes.Count)</p><p>18. {</p><p>19. Console.WriteLine(“Escolha inválida, tente novamente.”);</p><p>20. }</p><p>21. else</p><p>22. {</p><p>23. indiceEscolhido = escolha – 1; // Ajuste para índice</p><p>baseado em zero</p><p>24. }</p><p>25. }</p><p>26.</p><p>27. return possiveisOponentes[indiceEscolhido];</p><p>28.}</p><p>29.</p><p>30.// Método para o tributo ao Cardeal</p><p>62</p><p>Unidade I</p><p>31.private void TributoAoCardeal(Jogador jogador)</p><p>32.{</p><p>33. if (jogador.FichasDeFortuna >= 2)</p><p>34. {</p><p>35. jogador.FichasDeFortuna -= 2;</p><p>36. Console.WriteLine($”{jogador.Nome} pagou 2 fichas de Fortuna</p><p>como tributo ao Cardeal.”);</p><p>37. }</p><p>38. else</p><p>39. {</p><p>40. jogador.FichasDeHonra -= 1;</p><p>41. Console.WriteLine($”{jogador.Nome} não tinha fichas de Fortuna</p><p>suficientes e perdeu 1 ficha de Honra.”);</p><p>42. }</p><p>43.}</p><p>44.</p><p>45.// Método para verificar se o jogo terminou</p><p>46.private bool VerificarFimDeJogo(Jogador jogador)</p><p>47.{</p><p>48. // Checa se o jogador alcançou a última casa</p><p>49. return jogador.Posicao == tabuleiro.NumeroDeCasas;</p><p>50.}</p><p>51. // Demais métodos</p><p>Figura 21 – Classe Jogo: nona parte – métodos auxiliares EscolherOponente,</p><p>TributoAoCardeal e VerificarFimDeJogo</p><p>Na linha 1 o método EscolherOponente permite que um jogador (identificado como desafiante)</p><p>escolha o oponente de uma lista de jogadores possíveis (possiveisOponentes) para um duelo de honra.</p><p>O processo começa listando na tela todos os jogadores elegíveis para o duelo, excluindo o próprio</p><p>desafiante e quaisquer participantes no reconhecimento da corte ou que já tenham sido eliminados.</p><p>63</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>Lembrete</p><p>É o método ExecutarDueloDeHonra que faz a seleção inicial dos</p><p>possíveis oponentes, e não diretamente dentro do método EscolherOponente</p><p>(vide linha 4 da figura 17). A lista filtrada é passada para o método</p><p>EscolherOponente, que trabalha apenas com a lista de jogadores já filtrados</p><p>no método ExecutarDueloDeHonra. Essa separação de papéis é importante:</p><p>ExecutarDueloDeHonra prepara a lista de oponentes possíveis, enquanto</p><p>EscolherOponente lida com a interação do usuário para escolher um dos</p><p>oponentes pré-filtrados.</p><p>A implementação usa um loop while (linha 12 da figura 21) para garantir que a entrada do usuário</p><p>seja válida e, uma vez que um índice válido é fornecido, ele é ajustado para corresponder à indexação</p><p>baseada em zero da lista possiveisOponentes. O método então retorna o Jogador escolhido como</p><p>oponente. A técnica de ajuste do índice (linha 23) é crucial para alinhar a escolha do usuário com a</p><p>indexação da lista em C#, que começa em zero. Além disso, o método exemplifica o uso de validação de</p><p>entrada do usuário num cenário de jogo interativo, garantindo a integridade das ações.</p><p>Convém esclarecer que o método int.TryParse (linha 17) é uma forma segura de converter uma</p><p>string em número inteiro. Diferentemente do método int.Parse, o TryParse não lança uma exceção se</p><p>a conversão falhar; em vez disso, ele retorna um valor booleano que indica o sucesso ou fracasso</p><p>da conversão. TryParse é uma escolha prudente quando a entrada do usuário pode ser imprevisível e</p><p>potencialmente não numérica.</p><p>A palavra-chave out declara uma variável local (escolha) no mesmo momento em que ela é passada</p><p>como argumento, e o valor convertido é atribuído a essa variável se a conversão for bem-sucedida.</p><p>Observe também que a segunda parte da condição (escolha possiveisOponentes.Count) garante que o número escolhido</p><p>pelo usuário não exceda o número de oponentes disponíveis. É uma medida importante para prevenir</p><p>erros de índice fora dos limites que ocorreriam se o usuário escolhesse um número maior que o de</p><p>possíveis oponentes.</p><p>Na linha 31 da figura 21 temos o método TributoAoCardeal, acionado quando um jogador cai numa</p><p>casa específica do tabuleiro que exige um tributo ao cardeal. A lógica do método é simples e eficaz</p><p>em termos de mecânica de jogo: verifica-se se o jogador tem o mínimo de duas fichas de fortuna</p><p>(linha 33). Se tiver, paga duas como tributo; caso contrário, perde uma ficha de honra. O método ilustra</p><p>a manipulação de atributos do objeto Jogador, alterando seu estado com base nas regras. A decisão</p><p>64</p><p>Unidade I</p><p>entre duas diferentes moedas (fichas de fortuna e de honra) adiciona uma camada de complexidade</p><p>estratégica, influenciando as decisões tomadas e o andamento do jogo.</p><p>Já o método VerificarFimDeJogo na linha 46 tem um propósito claro: determinar se um jogador</p><p>específico alcançou a condição de término – no caso, chegar à última casa do tabuleiro. A lógica é</p><p>direta: o método retorna um valor booleano (linha 49) que indica se a posição do jogador é igual ao</p><p>número total de casas no tabuleiro (tabuleiro.NumeroDeCasas). O método exemplifica uma verificação</p><p>de condição comum em jogos de tabuleiro, em que alcançar determinado ponto ou posição sinaliza</p><p>seu fim. Além disso, ao encapsular essa lógica num método, o código ganha em clareza e reusabilidade,</p><p>permitindo que a mesma lógica de verificação seja aplicada consistentemente em várias partes do jogo.</p><p>Estamos quase finalizando os métodos da classe Jogo. A figura 22 tem os detalhes dos métodos</p><p>AnunciarVencedor (linha 2) e IniciarJogo (linha 28). O primeiro é chamado quando o jogo identifica</p><p>que um ou mais jogadores alcançaram um estado que permite determinar um vencedor. Isso ocorre</p><p>quando todos os jogadores estão no estado de reconhecimento da corte ou quando atingem a última</p><p>casa do tabuleiro.</p><p>1. // Método para anunciar o vencedor</p><p>2. private void AnunciarVencedor()</p><p>3. {</p><p>4. var pontuacoes = new Dictionary();</p><p>5. foreach (var jogador in jogadores.Where(j =></p><p>j.NoReconhecimentoDaCorte))</p><p>6. {</p><p>7. int pontuacao = jogador.FichasDeFortuna + 2 *</p><p>jogador.FichasDeHonra;</p><p>8. pontuacoes.Add(jogador, pontuacao);</p><p>9. }</p><p>10.</p><p>11. int maiorPontuacao = pontuacoes.Values.Max();</p><p>12. var vencedores = pontuacoes.Where(p => p.Value ==</p><p>maiorPontuacao).Select(p => p.Key).ToList();</p><p>13.</p><p>14. if (vencedores.Count == 1)</p><p>15. {</p><p>16. Console.WriteLine($”\nO vencedor é {vencedores[0].Nome} com</p><p>{maiorPontuacao} pontos!”);</p><p>17. }</p><p>18. else</p><p>19. {</p><p>20. string nomesDosVencedores = string.Join(“, “,</p><p>vencedores.Select(v => v.Nome));</p><p>65</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>21. Console.WriteLine($”\nHouve um empate entre</p><p>{nomesDosVencedores}, com {maiorPontuacao} pontos cada um.”);</p><p>22. }</p><p>23.</p><p>24. Console.ReadKey();</p><p>25.}</p><p>26.</p><p>27.// Método para iniciar o jogo</p><p>28.public void IniciarJogo()</p><p>29.{</p><p>30. Console.WriteLine(“\nBem-vindo ao Jogo dos Mosqueteiros!\n”);</p><p>31. // Inicializações do jogo, se necessário</p><p>32.</p><p>33. Console.WriteLine(“Pressione qualquer tecla para começar.”);</p><p>34. Console.ReadKey(); // Aguarda o usuário pressionar uma tecla</p><p>35.</p><p>36. ExecutarJogo();</p><p>37.}</p><p>38.// Demais métodos</p><p>Figura 22 – Classe Jogo: décima parte – métodos auxiliares AnunciarVencedor e IniciarJogo</p><p>O método AnunciarVencedor implementa uma solução para determinar o vencedor num jogo de</p><p>tabuleiro, projetada para lidar não apenas com a identificação de um vencedor único, mas também com</p><p>a possibilidade de empate entre dois ou mais jogadores. A escolha de um Dictionary para armazenar</p><p>pontuações é central para a lógica implementada, pois em C# é uma coleção que armazena pares</p><p>de chave-valor; nesse caso cada objeto Jogador é usado como chave, e a respectiva pontuação é</p><p>armazenada como valor.</p><p>A principal vantagem de usar um Dictionary aqui é sua eficiência em associar chaves (jogadores)</p><p>a valores (pontuações) e na recuperação desses valores – estrutura de dados que permite calcular,</p><p>armazenar e acessar pontuações de maneira rápida e direta, facilitando o processo de determinar o</p><p>vencedor ou identificar um empate.</p><p>No início do método é criado um Dictionary vazio (linha 4) para armazenar pontuações. O método</p><p>então itera (linha 5) a lista de jogadores (note novamente o uso do Where e da expressão lambda),</p><p>calculando a pontuação de cada um que está no reconhecimento da corte.</p><p>66</p><p>Unidade I</p><p>Lembrete</p><p>Pontuação é a soma das fichas de fortuna mais o dobro das fichas</p><p>de honra. Esse cálculo foi definido nos requisitos do quadro 1 e reflete a</p><p>importância dual da fortuna e da honra no jogo, atribuindo um peso maior</p><p>à honra, conforme o fator de multiplicação.</p><p>Essas pontuações são então adicionadas ao Dictionary (linha 8). Calculadas as pontuações, o método</p><p>identifica a maior pontuação usando o Max do LINQ (linha 11), uma maneira eficiente e concisa de</p><p>encontrar o maior valor numa coleção. Em seguida, utiliza-se uma consulta LINQ para selecionar todos</p><p>os jogadores cuja pontuação corresponda a esse valor máximo (linha 12). Ao utilizar o método Where,</p><p>aplicamos um filtro sobre essa coleção; esse filtro percorre cada par chave-valor no Dictionary e seleciona</p><p>apenas aqueles cujo valor (a pontuação do jogador) se iguale à variável maiorPontuacao, previamente</p><p>determinada como maior pontuação entre todos os jogadores.</p><p>Na linha 12, a expressão lambda p => p.Value == maiorPontuacao serve como critério para esse filtro,</p><p>onde p representa cada par chave-valor e p.Value acessa a pontuação associada a esse par; aplicado</p><p>o filtro, o método Select transforma os elementos filtrados. Já o Select(p => p.Key) converte cada par</p><p>chave-valor filtrado em sua chave, que é o objeto Jogador. Em outras palavras, a transformação extrai</p><p>o jogador de cada par chave-valor que passou pelo filtro do Where. Finalmente, a chamada ao método</p><p>ToList converte a sequência resultante da filtragem e transformação numa lista concreta de objetos</p><p>Jogador, composta pelos jogadores cujas pontuações são iguais à maior pontuação encontrada.</p><p>Já a lógica para anunciar o vencedor ou um empate depende do número de jogadores que</p><p>alcançaram a maior pontuação. Se apenas um jogador tiver a maior pontuação, ele é declarado</p><p>vencedor (linha 14 à 17); caso contrário, o método reconhece um empate e lista o nome desses</p><p>jogadores (linha 19 à 22).</p><p>A expressão da linha 20 começa com a chamada do método Join da classe string – método conhecido</p><p>por sua habilidade em concatenar elementos de uma coleção inserindo um separador específico entre</p><p>cada elemento. No caso presente, o separador escolhido é uma vírgula seguida de um espaço (“, ”),</p><p>escolha que reflete o objetivo de criar uma lista legível e bem formatada do nome dos jogadores.</p><p>O primeiro argumento do método Join é o separador, enquanto o segundo é a coleção de strings a</p><p>concatenar. Para obtê-la, aplicamos o método Select sobre a lista vencedores, que contém os objetos</p><p>Jogador que empataram ou venceram. A expressão lambda v => v.Nome passada para o Select especifica</p><p>que, para cada objeto Jogador na lista vencedores, o método deve extrair a propriedade Nome do</p><p>jogador. O resultado é uma coleção de strings, cada uma representando o nome de um jogador vencedor</p><p>ou empatado.</p><p>Ao combinar string.Join com a expressão LINQ vencedores.Select(v => v.Nome), conseguimos gerar</p><p>de maneira sucinta uma única string que lista esses nomes, separados por vírgula – abordagem que não</p><p>67</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>apenas simplifica o código, mas também o torna mais legível e fácil de manter, evitando loops manuais</p><p>e concatenações complexas de strings.</p><p>A figura 22 também apresenta o método IniciarJogo na linha 28. No início uma mensagem de</p><p>boas-vindas é exibida para o jogador utilizando Console.WriteLine; a ação é simples mas essencial</p><p>para uma introdução amigável. A interação imediata com o usuário é feita pelo Console.ReadKey, que</p><p>aguarda uma entrada de teclado para prosseguir – mecanismo frequentemente utilizado em aplicações</p><p>de console para controlar o fluxo do programa, pausando a execução até o usuário fazer uma ação</p><p>específica; no caso, pressionar qualquer tecla.</p><p>Após essa interação inicial, o método ExecutarJogo é chamado; e este é o núcleo do ciclo de jogo,</p><p>no qual a lógica principal é executada.</p><p>1.// Método para processar a casa em que o jogador caiu</p><p>2. private void ProcessarCasa(Casa casa, Jogador jogador)</p><p>3. {</p><p>4. Console.WriteLine($”Jogador {jogador.Nome} caiu na casa</p><p>{jogador.Posicao}.”);</p><p>5. switch (casa.Tipo)</p><p>6. {</p><p>7. case TipoCasa.DueloDeHonra:</p><p>8. ExecutarDueloDeHonra(jogador);</p><p>9. break;</p><p>10. case TipoCasa.Duelo:</p><p>11. ExecutarDuelo(jogador);</p><p>12. break;</p><p>13. case TipoCasa.AudienciaComORei:</p><p>14. AudienciaComORei(jogador);</p><p>15. break;</p><p>16. case TipoCasa.Evento:</p><p>17. ExecutarEvento(jogador);</p><p>18. break;</p><p>19.</p><p>case TipoCasa.EscolaDeEsgrima:</p><p>20. jogador.FichasDeHonra++;</p><p>21. Console.WriteLine($”{jogador.Nome} treinou na Escola de</p><p>Esgrima e ganhou 1 ficha de Honra.”);</p><p>68</p><p>Unidade I</p><p>22. break;</p><p>23. case TipoCasa.Estalagem:</p><p>24. jogador.FichasDeFortuna = Math.Max(0,</p><p>jogador.FichasDeFortuna – 1);</p><p>25. Console.WriteLine($”{jogador.Nome} pagou pela estadia e perdeu</p><p>1 ficha de Fortuna.”);</p><p>26. break;</p><p>27. case TipoCasa.TributoAoCardeal:</p><p>28. TributoAoCardeal(jogador);</p><p>29. break;</p><p>30. case TipoCasa.Retorno:</p><p>31. if (casa.CasaDeRetorno.HasValue)</p><p>32. {</p><p>33. jogador.Posicao = casa.CasaDeRetorno.Value;</p><p>34. Console.WriteLine($”{jogador.Nome} retornou para a casa</p><p>{jogador.Posicao}.”);</p><p>35. }</p><p>36. break;</p><p>37. default:</p><p>38. Console.WriteLine(“Nenhuma ação especial nesta casa.”);</p><p>39. break;</p><p>40. }</p><p>41. }</p><p>Figura 23 – Classe Jogo: última parte – método auxiliar ProcessarCasa</p><p>Finalmente, o último método da classe Jogo. O ProcessarCasa na figura 23 define as ações que</p><p>ocorrem quando um jogador cai em determinada casa no tabuleiro, exemplificando vários conceitos</p><p>de programação em C#, como estrutura de controle switch, uso de enums e manipulação de</p><p>objetos. Ele recebe dois parâmetros: um objeto Casa, que representa a casa na qual o jogador caiu; e</p><p>um objeto Jogador, que representa o jogador atual (linha 2).</p><p>A lógica do método é baseada no tipo da casa, determinado pela enumeração TipoCasa. Dentro do</p><p>método, um switch (linha 5) é usado para tratar cada possível tipo de casa. A estrutura de controle é uma</p><p>maneira eficiente de direcionar o fluxo do programa com base no valor de uma variável – no caso, o tipo da</p><p>casa. Para cada tipo, uma série de ações é executada, afetando o estado do jogador de diferentes maneiras.</p><p>69</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>Por exemplo, se a casa for do tipo DueloDeHonra, o método ExecutarDueloDeHonra é chamado para o</p><p>jogador atual; se for do tipo Evento, o método ExecutarEvento é chamado.</p><p>A propriedade HasValue na linha 31 é um exemplo interessante da manipulação de tipos que podem</p><p>ou não ter valor definido (característica comum em C#); no caso, verifica se o objeto CasaDeRetorno</p><p>dentro da classe Casa tem valor atribuído. CasaDeRetorno é um exemplo de tipo Nullable, funcionalidade</p><p>da linguagem que permite aos tipos de valor – como inteiros e datas – serem nulos.</p><p>Em C#, tipos de valor como int, float e double não podem ser nulos por padrão. Isso significa</p><p>que sempre devem conter um valor como 0, 1,5 etc. Contudo, um valor nulo (ou ausência de um valor)</p><p>pode ser condição necessária. Para acomodar isso, C# introduziu os tipos Nullable, representados por</p><p>um ponto de interrogação após o tipo (por exemplo, int?).</p><p>Quando um tipo Nullable é declarado, ele pode conter um valor ou ser nulo; aqui a propriedade</p><p>CasaDeRetorno é do tipo int?, ou seja, pode conter um número inteiro ou ser nula. Isso é útil no</p><p>contexto do jogo, pois nem todas as casas precisam ter uma casa de retorno associada. Para algumas</p><p>casas esse conceito não se aplica, portanto seu valor pode ser nulo.</p><p>HasValue é uma forma de verificar se um objeto Nullable tem valor atribuído ou não. Se retorna</p><p>true, significa que o objeto contém um valor; se retorna false, o objeto é nulo. Portanto, no trecho if</p><p>(casa.CasaDeRetorno.HasValue), o código está verificando se a casa atual no tabuleiro tem uma casa de</p><p>retorno associada; se houver, o código subsequente (que depende desse valor) pode ser executado com</p><p>segurança, sabendo que existe um valor válido para usar.</p><p>1.1.1 Considerações sobre o código‑fonte</p><p>Chegamos ao final do código-fonte do jogo Os desafios dos mosqueteiros: duelos & destinos.</p><p>Ao todo são aproximadamente 660 linhas (a quantidade pode variar um pouco, a depender das linhas</p><p>em branco entre métodos e classes); você pode compilá-lo e executá-lo para ver o jogo funcionar.</p><p>Observação</p><p>Se tiver dificuldade nessa tarefa, leia as seções 1.2, 1.3 e 1.4.</p><p>Podem ser úteis.</p><p>Mas não chegamos ao final da revisão proposta para este tópico. Após compilar e executar o código,</p><p>analise-o atentamente e verifique que alguns conceitos básicos de POO e C# ainda não estão presentes</p><p>nesta implantação. Começaremos por herança e polimorfismo (embora este estivesse presente na</p><p>discussão sobre o delegate Action).</p><p>Podemos criar uma hierarquia de classes para diferentes tipos de casa no tabuleiro. Atualmente,</p><p>elas são diferenciadas apenas pelo enum TipoCasa, mas podemos torná-las mais versáteis e extensíveis</p><p>com herança. Vamos criar uma classe-base Casa e subclasses específicas para cada tipo de casa, como</p><p>70</p><p>Unidade I</p><p>CasaEvento, CasaDuelo etc. Cada subclasse pode ter propriedades e métodos específicos para suas ações;</p><p>por exemplo, a classe CasaEvento pode ter um método para lidar com eventos.</p><p>Alterações necessárias:</p><p>• modificar a classe Casa para ser uma classe-base abstrata (figura 24);</p><p>• criar subclasses para cada tipo de casa (figura 25);</p><p>• modificar o método InicializarCasas na classe Tabuleiro para usar as novas subclasses (figura 26);</p><p>• atualizar o método ProcessarCasa na classe Jogo para utilizar o novo método ProcessarAcao</p><p>(figura 27).</p><p>1. abstract class Casa</p><p>2. {</p><p>3. public TipoCasa Tipo { get; protected set; }</p><p>4. public int? CasaDeRetorno { get; protected set; }</p><p>5.</p><p>6. protected Casa(TipoCasa tipo, int? casaDeRetorno = null)</p><p>7. {</p><p>8. Tipo = tipo;</p><p>9. CasaDeRetorno = casaDeRetorno;</p><p>10. }</p><p>11.</p><p>12. // Método abstrato para processar a ação da casa</p><p>13. public abstract void ProcessarAcao(Jogador jogador);</p><p>14. }</p><p>Figura 24 – Herança: classe abstrata Casa</p><p>Uma classe abstrata em POO não pode ser instanciada diretamente; ela serve como base para outras</p><p>classes. A classe abstrata Casa na figura 24 serve como esqueleto ou modelo para outras classes e não</p><p>pode ser instanciada diretamente, ou seja, não se pode criar um objeto diretamente dessa classe; em vez</p><p>disso, ela é projetada para ser a base de outras classes, chamadas subclasses.</p><p>71</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>A escolha de tornar Casa uma classe abstrata é uma aplicação prática do princípio da abstração,</p><p>que é diferente de uma classe abstrata (abstração, em contexto mais amplo, é o processo de esconder a</p><p>complexidade e mostrar apenas o necessário). No código, a classe Casa abstrai a ideia de uma casa no</p><p>tabuleiro, fornecendo uma estrutura comum para todos os tipos de casa, como CasaEvento, CasaDuelo,</p><p>entre outras. Cada uma dessas subclasses pode ter características e comportamentos específicos, mas</p><p>compartilham certos traços definidos na classe Casa.</p><p>As propriedades na classe Casa são definidas como protected (linhas 3 e 4) para permitir que</p><p>somente a própria classe Casa e suas subclasses tenham acesso a elas. Isso é útil para garantir que as</p><p>propriedades essenciais da Casa, como Tipo e CasaDeRetorno, sejam facilmente acessíveis e modificáveis</p><p>pelas subclasses, mas não pelo mundo externo. Essa abordagem encapsula e protege dados, mantendo-os</p><p>seguros e expostos apenas onde necessários.</p><p>ProcessarAcao é declarado na linha 13 como método abstrato dentro da classe abstrata Casa.</p><p>Método abstrato, por definição, não tem implementação na classe em que é declarado, deixando</p><p>para as subclasses a responsabilidade de fornecer uma implementação concreta. Isso é um exemplo</p><p>de polimorfismo, ou seja, diferentes classes derivadas de uma mesma superclasse implementam</p><p>um método de maneiras diferentes. A decisão de tornar ProcessarAcao um método abstrato se baseia</p><p>na ideia de que cada tipo de casa no tabuleiro terá uma maneira única de processar a ação quando um</p><p>jogador cair nela. Assim, cada subclasse de Casa precisa definir como essa ação é processada, o que é</p><p>coerente com a natureza diversificada das ações no jogo.</p><p>Na figura 25 temos a subclasse CasaEvento, que exemplifica essa natureza diversificada.</p><p>A palavra-chave base será usada no construtor das subclasses de Casa, como em base(TipoCasa.</p><p>Evento), para chamar o construtor da</p><p>de pedidos .............................................................. 245</p><p>7.2.3 Contexto delimitado 3: administração de clientes ................................................................ 246</p><p>7.3 Práticas de programação segura em C# ...................................................................................246</p><p>7.4 Autenticação e autorização ...........................................................................................................257</p><p>8 DESENVOLVIMENTO CROSS-PLATFORM E MOBILIDADE ...............................................................263</p><p>8.1 Desenvolvimento cross-platform com Xamarin ou MAUI ................................................264</p><p>8.2 Desenvolvimento de aplicativos móveis ...................................................................................265</p><p>8.3 Gráficos e visualização de dados .................................................................................................276</p><p>8.4 Microsserviços .....................................................................................................................................282</p><p>8.5 Arquiteturas baseadas em eventos .............................................................................................286</p><p>7</p><p>APRESENTAÇÃO</p><p>O objetivo geral deste livro-texto é proporcionar ao aluno uma continuidade da disciplina</p><p>anterior, aprofundando conceitos e desenvolvendo novas habilidades, em especial o acesso a banco</p><p>de dados, com exercícios práticos. O aluno também entenderá o desenvolvimento de aplicações</p><p>com interfaces gráficas para desktop que observem e implementem os conceitos de interação</p><p>humano-computador (IHC).</p><p>Os objetivos específicos são, entre outros:</p><p>• aprofundar os princípios da programação orientada a objetos (POO) e apresentar técnicas de</p><p>persistência de dados utilizando conexões com banco de dados locais ou remotos, considerando</p><p>aspectos da computação em nuvem;</p><p>• desenvolver interfaces com C# para desktop, adotando as melhores práticas de experiência</p><p>do usuário;</p><p>• capacitar os alunos a implementar aplicações em camadas utilizando o paradigma de POO</p><p>em C#.</p><p>A disciplina foi estruturada para oferecer uma abordagem mais detalhada e aprofundada dos</p><p>conceitos e práticas associadas à POO, com ênfase em áreas de desenvolvimento avançado, como criação</p><p>de interfaces gráficas e gerenciamento de bancos de dados. A era digital evoluiu, e a programação e</p><p>o desenvolvimento de software se tornaram a espinha dorsal de muitas operações e serviços. Dentro</p><p>desse vasto domínio, esta disciplina não é apenas uma extensão, mas uma exploração mais profunda</p><p>dos intrincados caminhos da POO, focada principalmente na construção de interfaces gráficas e</p><p>interatividade com bancos de dados.</p><p>Apresentaremos o universo das interfaces gráficas, começando com o Windows Forms. Numa</p><p>época em que a IHC é mais relevante do que nunca, é imperativo que os futuros programadores</p><p>compreendam suas nuances e as de user experience (UX). Em qualquer aplicação, a maneira como</p><p>os dados são apresentados e organizados pode determinar a facilidade de uso e a eficácia da</p><p>interface. Assim, a disciplina se aprofunda em técnicas para criar layouts tanto esteticamente</p><p>agradáveis quanto funcionais.</p><p>Outro aspecto vital é o foco na arquitetura e no design. O desenvolvimento em camadas e o padrão</p><p>model-view-controller (MVC) são aprofundados, oferecendo uma visão clara de como os aplicativos</p><p>de grande escala são estruturados e organizados. Esse tema não apenas solidifica o entendimento da</p><p>estrutura de software, como também prepara futuros programadores para desafios reais.</p><p>Integração com bancos de dados é outro tópico primordial. Abordaremos a criação de conexões com</p><p>bancos de dados, sejam eles locais, sejam eles hospedados em ambientes de computação em nuvem.</p><p>A relevância de se conectar a bases de dados num cenário de computação em nuvem é de extrema</p><p>importância, dada a tendência crescente da digitalização de serviços e do armazenamento em nuvem;</p><p>8</p><p>além disso, a utilização de strings de conexão, especialmente pelo SQLClient, é discutida em detalhes.</p><p>Language Integrated Query (LINQ) é uma inovação que permite aos desenvolvedores interagir com</p><p>bancos de dados de maneira mais intuitiva e declarativa.</p><p>Também serão explorados o Windows Presentation Foundation (WPF) e a Extensible Application</p><p>Markup Language (XAML), componentes fundamentais para desenvolver interfaces gráficas ricas</p><p>em aplicações .NET. O primeiro permite aos desenvolvedores criar interfaces avançadas que podem</p><p>aproveitar ao máximo recursos do hardware; a segunda oferece uma maneira declarativa de definir</p><p>a aparência e o comportamento da interface gráfica. Essas ferramentas são vitais para desenvolver</p><p>interfaces avançadas e personalizadas para desktop, dado que ambas permitem criar interfaces gráficas</p><p>avançadas e personalizáveis, tornando-se um conhecimento valioso para qualquer desenvolvedor</p><p>focado em aplicações desktop.</p><p>Na contemporaneidade do desenvolvimento de software, dominar técnicas avançadas, manter</p><p>padrões de segurança robustos e compreender os paradigmas do desenvolvimento cross-platform</p><p>são competências indispensáveis. O alcance desses temas abrange desde práticas específicas de</p><p>linguagens de programação até conceitos arquiteturais complexos, alinhando-os às necessidades</p><p>atuais e futuras da indústria.</p><p>Ao longo da disciplina, o aluno não apenas ganha conhecimento teórico, mas também habilidades</p><p>práticas. Os assuntos serão complementados por exercícios que garantem a aplicabilidade do</p><p>conhecimento adquirido.</p><p>INTRODUÇÃO</p><p>Uma vez que a POO é o paradigma utilizado em C#, começaremos, já na unidade I, a rever seus</p><p>conceitos básicos com exercícios práticos no console. Não são apenas aquecimentos, mas fundamentos</p><p>sólidos sobre os quais se constroem os edifícios complexos do conhecimento. Cada exercício reforça o</p><p>aprendizado anterior e pavimenta o caminho para conceitos avançados em seguida. A transição dos</p><p>conceitos básicos para aplicações mais sofisticadas é guiada pela introdução ao ambiente de C#, como</p><p>veremos no tópico 1.</p><p>Aplicativos modernos contêm interfaces gráficas ricas, por isso o desenvolvedor precisa saber</p><p>desenvolvê-las adequadamente; e neste livro-texto abordaremos dois aspectos: a programação em si</p><p>e o design de interfaces. Sobre programação, veremos no tópico 2 detalhes sobre Windows Forms, um</p><p>recurso poderoso para criar aplicativos para desktop, explorando elementos gráficos desde os básicos</p><p>até os mais avançados: rótulos, caixas de texto, botões, painéis, grupos e muitos outros componentes.</p><p>Cada elemento será analisado não apenas em termos de sua funcionalidade, mas também de sua</p><p>relevância e aplicabilidade em cenários do mundo real.</p><p>Deve-se entender o peso da escolha certa de componentes para oferecer a melhor experiência</p><p>de usuário possível. Também não podemos negligenciar a complexidade e a variedade que vêm</p><p>com os elementos gráficos, por isso o tópico 3 apresenta e discute caixas de opção e de seleção,</p><p>calendários, seletores de data e muito mais; cada um desses componentes desempenha papel único</p><p>9</p><p>na criação de interfaces intuitivas e eficientes. À medida que avançamos no aprendizado desses</p><p>elementos, somos confrontados com o desafio de organizar dados e componentes; introduziremos,</p><p>então, menus, caixas de listagem, controles de abas e outras ferramentas.</p><p>No mundo da programação, interatividade é chave. Isto posto, manipular eventos – como ações de</p><p>mouse e teclado – é habilidade essencial. O tópico 4 oferece uma exploração abrangente dos eventos,</p><p>permitindo que o aluno compreenda e analise as várias interações possíveis dentro de uma interface</p><p>gráfica. A evolução do aprendizado não se limita a componentes individuais; também integra vários</p><p>componentes em uma interface coesa. Aqui entramos no domínio da multiple document interface (MDI),</p><p>característica central em muitos aplicativos modernos. Ao final do tópico 4 teremos um projeto prático</p><p>para os conceitos</p><p>classe-base (linha 3 da figura 25); isso é essencial para garantir</p><p>que a inicialização definida na classe-base seja executada, além de qualquer inicialização específica</p><p>da subclasse.</p><p>1. class CasaEvento : Casa</p><p>2. {</p><p>3. public CasaEvento() : base(TipoCasa.Evento) { }</p><p>4.</p><p>5. public override void ProcessarAcao(Jogador jogador)</p><p>6. {</p><p>7. // Lógica específica para casa de evento</p><p>8. }</p><p>9. }</p><p>10.</p><p>11. // Semelhante para CasaDuelo, CasaEscolaDeEsgrima, etc.</p><p>Figura 25 – Herança e polimorfismo: exemplo de subclasse CasaEvento</p><p>72</p><p>Unidade I</p><p>No exemplo dado isso significa que, ao criar uma instância de CasaEvento, a classe não apenas</p><p>executa sua própria inicialização, mas também a inicialização definida na classe Casa, garantindo que</p><p>todas as propriedades e comportamentos necessários sejam devidamente configurados. Note também</p><p>que o método ProcessarAcao na linha 5 da figura 25 sucede a palavra-chave override, evidenciando</p><p>claramente o conceito polimórfico.</p><p>1. private void InicializarCasas()</p><p>2. {</p><p>3. //... código anterior...</p><p>4. switch (i)</p><p>5. {</p><p>6. case 5:</p><p>7. case 20:</p><p>8. //... outros casos...</p><p>9. casas.Add(new CasaEvento());</p><p>10. break;</p><p>11. //... outros casos...</p><p>12. }</p><p>13. }</p><p>Figura 26 – Herança: atualização do método InicializarCasas na classe Tabuleiro para usar as novas subclasses</p><p>1. private void ProcessarCasa(Casa casa, Jogador jogador)</p><p>2. {</p><p>3. Console.WriteLine($”Jogador {jogador.Nome} caiu na casa</p><p>{jogador.Posicao}.”);</p><p>4. casa.ProcessarAcao(jogador);</p><p>5. }</p><p>Figura 27 – Herança: atualização do método ProcessarCasa na classe Jogo para utilizar o novo método ProcessarAcao</p><p>Essas mudanças introduzem herança e polimorfismo no código, permitindo que cada tipo de casa</p><p>tenha comportamentos específicos e facilite a adição de novos tipos de casa no futuro.</p><p>73</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>Observação</p><p>Os códigos das figuras 25 e 26, bem como os próximos códigos-fonte</p><p>deste tópico, são apenas exemplos ilustrativos e não exaustivos. Para</p><p>funcionarem adequadamente, é necessário completar alterações, conforme</p><p>os próprios comentários dos referidos códigos.</p><p>1. interface IAcaoCasa</p><p>2. {</p><p>3. void ExecutarAcao(Jogador jogador);</p><p>4. }</p><p>Figura 28 – Polimorfismo de interface: IAcaoCasa</p><p>Podemos também adaptar o código do jogo usando polimorfismo de interface (figura 28).</p><p>Uma abordagem seria criar uma interface para as diferentes ações que os jogadores podem realizar nas</p><p>casas do tabuleiro, incluindo eventos, duelos, audiências com o rei, entre outras. No caso, precisamos</p><p>criar uma interface chamada IAcaoCasa, que definirá um método ExecutarAcao a ser implementado por</p><p>diferentes classes de ação.</p><p>A seguir, precisamos criar classes específicas para cada tipo de ação possível numa casa,</p><p>implementando a interface IAcaoCasa. A figura 29 mostra um exemplo para uma ação de duelo; o</p><p>mesmo deve ser feito em outros tipos de ação, como eventos, audiências com o rei etc.</p><p>1. class AcaoDuelo : IAcaoCasa</p><p>2. {</p><p>3. public void ExecutarAcao(Jogador jogador)</p><p>4. {</p><p>5. // Lógica do duelo aqui</p><p>6. }</p><p>7. }</p><p>Figura 29 – Polimorfismo de interface: AcaoDuelo</p><p>Também seria necessário modificar a classe Casa para usar a interface IAcaoCasa, adicionando um</p><p>campo para armazenar referência à ação associada à casa. Finalmente, o método ProcessarCasa da</p><p>classe Jogo deve ser ajustado para usar polimorfismo; em vez de um grande switch-case, podemos</p><p>simplesmente chamar o método ExecutarAcao da ação associada à casa.</p><p>O código original do jogo também não apresenta polimorfismo ad hoc, que ocorre quando um</p><p>único símbolo ou operação se comporta de maneira diferente com base nos argumentos com que é</p><p>chamado ou no contexto em que é utilizado. Há dois tipos principais de polimorfismo ad hoc: sobrecarga</p><p>(overloading) e coerção (coercion).</p><p>74</p><p>Unidade I</p><p>Para introduzir esse conceito no código do jogo dos mosqueteiros, uma abordagem comum é</p><p>utilizar a sobrecarga de métodos (overloading), pois permite que vários métodos com o mesmo nome</p><p>existam, mas com diferentes assinaturas de parâmetros (tipos ou números de argumento). Isso é útil</p><p>quando desejamos que um método realize operações similares, mas com diferentes tipos de dados ou</p><p>número de argumentos.</p><p>Vamos considerar a classe Jogador e introduzir sobrecarga no método AtualizarTurno. Inicialmente,</p><p>ele atualiza o turno do jogador baseado no número total de casas no tabuleiro (linha 30 da figura 12).</p><p>Podemos sobrecarregá-lo para também poder atualizar o turno baseado em outros fatores, como um</p><p>evento específico ou uma ação de outro jogador (figura 30).</p><p>1. public void AtualizarTurno(int numeroTotalDeCasas)</p><p>2. {</p><p>3. // Lógica original...</p><p>4. }</p><p>5.</p><p>6. // Sobrecarga do método para aceitar um evento</p><p>7. public void AtualizarTurno(CartaDeEvento evento)</p><p>8. {</p><p>9. // Lógica específica para quando um evento ocorre</p><p>10. }</p><p>11.</p><p>12. // Sobrecarga do método para aceitar a ação de outro jogador</p><p>13. public void AtualizarTurno(Jogador jogadorAcao)</p><p>14. {</p><p>15. // Lógica específica para quando outro jogador influencia o turno</p><p>16. }</p><p>Figura 30 – Polimorfismo ad hoc: método AtualizarTurno</p><p>Com essas alterações, o método agora pode lidar com diferentes cenários: um é o cenário-padrão,</p><p>onde o turno é atualizado com base no número de casas no tabuleiro (linha 1); o segundo é quando um</p><p>evento específico influencia o turno do jogador (linha 7); e o terceiro é quando o turno é afetado pela</p><p>ação de outro jogador (linha 13). Essa abordagem exemplifica polimorfismo ad hoc, pois o mesmo nome</p><p>de método (AtualizarTurno) é usado em diferentes contextos com diferentes tipos de argumento. Cada</p><p>versão tem uma implementação específica e mais adequada ao tipo de dado fornecido como argumento.</p><p>Apesar de a revisão deste tópico ter discutido um pouco os métodos delegados (como na</p><p>figura 10), vale a pena exercitá-los aqui de forma independente. Para adicionar um exemplo de eventos e</p><p>delegados no código do jogo, podemos criar um sistema de notificação para quando eventos importantes</p><p>acontecem, como quando um participante ganha uma rodada, perde fichas ou é eliminado.</p><p>75</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>Vamos definir um delegado e um evento na classe Jogador e dispará-lo em situações específicas</p><p>(a figura 31 ilustra esse cenário, cuja linha 4 define um delegado chamado JogadorEventHandler).</p><p>Delegado é um tipo que representa referências a métodos com uma lista de parâmetros e tipos de</p><p>retorno específicos. No caso, o delegado pode apontar para qualquer método que aceite um objeto</p><p>Jogador e uma string como parâmetros e que não retorne nada (void).</p><p>1. class Jogador</p><p>2. {</p><p>3. // Definição do delegado</p><p>4. public delegate void JogadorEventHandler(Jogador jogador, string</p><p>mensagem);</p><p>5.</p><p>6. // Evento baseado no delegado</p><p>7. public event JogadorEventHandler JogadorMudou;</p><p>8.</p><p>9. // Restante da classe...</p><p>10.</p><p>11. // Método para disparar o evento</p><p>12. protected virtual void OnJogadorMudou(string mensagem)</p><p>13. {</p><p>14. JogadorMudou?.Invoke(this, mensagem);</p><p>15. }</p><p>16.</p><p>17. // Exemplo de uso do evento dentro da classe</p><p>18. public void GanharFichaDeHonra()</p><p>19. {</p><p>20. FichasDeHonra++;</p><p>21. OnJogadorMudou($”ganhou uma ficha de honra. Total agora:</p><p>{FichasDeHonra}”);</p><p>22. }</p><p>23.</p><p>24. //... Outros métodos da classe...</p><p>25. }</p><p>Figura 31 – Delegado e Evento na classe Jogador</p><p>76</p><p>Unidade I</p><p>Na linha 7 é declarado um evento público chamado JogadorMudou, baseado no tipo de delegado</p><p>JogadorEventHandler. Em C#, eventos são um tipo especial de delegado que pode ser invocado</p><p>apenas de dentro da classe que o declara, proporcionando um mecanismo para comunicação entre</p><p>classes. Na linha 12 criamos um método para disparar o evento, denominado OnJogadorMudou, e a</p><p>palavra-chave virtual permite que o método seja sobrescrito em uma subclasse. O operador ?. é uma</p><p>verificação de nulidade segura – o evento só será invocado se houver inscritos (não nulo).</p><p>Por fim, no método GanharFichaDeHonra (linha 18), o evento JogadorMudou é disparado</p><p>após a</p><p>atualização de um atributo fictício FichasDeHonra. Isso ilustra como eventos podem notificar outras</p><p>partes do código sobre mudanças ou ações ocorridas dentro de uma classe.</p><p>Para que a alteração tenha efeito é necessário modificar a classe Jogo para se inscrever e reagir</p><p>ao evento. A figura 32 explicita essas duas atividades (linhas 10 e 15, respectivamente). Por exemplo,</p><p>você pode exibir uma mensagem sempre que o estado de um jogador mudar. Dentro do construtor da</p><p>classe Jogo, existe um loop que se inscreve no evento JogadorMudou de cada objeto Jogador na coleção</p><p>jogadores (linha 10).</p><p>1. class Jogo</p><p>2. {</p><p>3. public Jogo(Random random)</p><p>4. {</p><p>5. //... [Restante do construtor não modificado]...</p><p>6.</p><p>7. // Inscrever-se no evento para cada jogador</p><p>8. foreach (var jogador in jogadores)</p><p>9. {</p><p>10. jogador.JogadorMudou += JogadorMudouHandler;</p><p>11. }</p><p>12. }</p><p>13.</p><p>14. // Handler do evento</p><p>15. private void JogadorMudouHandler(Jogador jogador, string mensagem)</p><p>16. {</p><p>17. Console.WriteLine($”Notificação: {jogador.Nome} {mensagem}”);</p><p>18. }</p><p>19. //... [Restante da classe não modificado]...</p><p>20. }</p><p>Figura 32 – Event Handler na classe Jogo</p><p>77</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>O método JogadorMudouHandler da linha 15 é um handler para o evento JogadorMudou e</p><p>corresponde à assinatura do delegado JogadorEventHandler. Quando o evento é disparado em um objeto</p><p>Jogador, ele é executado e imprime uma mensagem no console. No exemplo, sempre que um jogador</p><p>ganhar uma ficha de honra (usando o método GanharFichaDeHonra), o evento JogadorMudou será</p><p>disparado, e a classe Jogo (inscrita no evento) reagirá exibindo uma mensagem na tela.</p><p>Isso exemplifica o uso de delegados e eventos para criar um sistema de notificação dentro do jogo.</p><p>É possível expandir esse conceito para outros aspectos do jogo, como quando alguém é eliminado,</p><p>ganha uma rodada, ou diante de outros eventos significativos.</p><p>1. class RoletaDeDuelo</p><p>2. {</p><p>3. private static RoletaDeDuelo instancia;</p><p>4. private Random aleatorio;</p><p>5.</p><p>6. // Construtor privado</p><p>7. private RoletaDeDuelo(Random random)</p><p>8. {</p><p>9. aleatorio = random;</p><p>10. }</p><p>11.</p><p>12. // Método estático para obter a instância</p><p>13. public static RoletaDeDuelo ObterInstancia(Random random)</p><p>14. {</p><p>15. if (instancia == null)</p><p>16. {</p><p>17. instancia = new RoletaDeDuelo(random);</p><p>18. }</p><p>19. return instancia;</p><p>20. }</p><p>21. // Restante da classe...</p><p>22. }</p><p>Figura 33 – Singleton: classe RoletaDeDuelo</p><p>Além da herança, do polimorfismo e dos métodos delegados, podemos relembrar os design patterns.</p><p>Para incorporar um exemplo de padrão de projeto no código, vamos implementar o padrão Singleton</p><p>78</p><p>Unidade I</p><p>para a classe RoletaDeDuelo (figura 33), garantindo que apenas uma instância dessa classe seja criada</p><p>e compartilhada. Observe na linha 7 que o construtor é privado, para impedir a criação de instâncias</p><p>da classe RoletaDeDuelo de fora da classe. Isso é essencial para o padrão Singleton, pois garante que</p><p>apenas a própria classe crie sua instância. Também é necessário modificar a classe Jogo para usar a</p><p>instância Singleton de RoletaDeDuelo (figura 34).</p><p>1. class Jogo</p><p>2. {</p><p>3. private RoletaDeDuelo roletaDeDuelo;</p><p>4.</p><p>5. public Jogo(Random random)</p><p>6. {</p><p>7. //...</p><p>8. roletaDeDuelo = RoletaDeDuelo.ObterInstancia(random);</p><p>9. //...</p><p>10. }</p><p>11.</p><p>12. // Restante da classe...</p><p>13. }</p><p>14.</p><p>Figura 34 – Singleton: instância na classe Jogo</p><p>Alternativamente, vamos implementar um padrão Factory para criar objetos Jogador, que cria objetos</p><p>sem expor a lógica de criação ao cliente e refere-se ao objeto criado com uma interface comum. Vamos</p><p>criar uma fábrica simples para a classe Jogador, conforme a figura 35. O Factory Pattern para Jogador</p><p>centraliza a criação de objetos Jogador e oferece flexibilidade para futuras expansões ou modificações</p><p>na maneira como os jogadores são criados.</p><p>1. class JogadorFactory</p><p>2. {</p><p>3. // Método de fábrica para criar um jogador</p><p>4. public static Jogador CriarJogador(string nome, Random random)</p><p>5. {</p><p>6. return new Jogador(nome, random);</p><p>7. }</p><p>8. }</p><p>Figura 35 – Padrão Factory: classe JogadorFactory</p><p>79</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>Também é necessário modificar o construtor da classe Jogo para usar JogadorFactory (figura 36).</p><p>1. public Jogo(Random random)</p><p>2. {</p><p>3. //...</p><p>4. jogadores = new List() {</p><p>5. JogadorFactory.CriarJogador(“D’Artagnan”, random),</p><p>6. JogadorFactory.CriarJogador(“Athos”, random),</p><p>7. //...</p><p>8. };</p><p>9. //...</p><p>10. }</p><p>Figura 36 – Padrão Factory: classe Jogo</p><p>Para finalizar a revisão, podemos falar de melhorias de código. Uma área específica de melhoria</p><p>identificada é a repetição de código, em particular o método RolarDado, que aparece em várias classes. Essa</p><p>funcionalidade – crucial para a dinâmica do jogo – aparece repetidamente em diferentes classes, sobretudo</p><p>em Jogo e Jogador. Tal repetição, embora aparentemente inofensiva, pode levar a problemas de manutenção e</p><p>consistência à medida que o projeto evolui.</p><p>A solução envolve consolidar a lógica de rolagem de dados em um único ponto, proporcionando</p><p>um design de código mais limpo, fácil de manter e escalável. A estratégia ideal para abordar essa</p><p>redundância é introduzir um método centralizado que possa ser chamado por diferentes partes do</p><p>código. Considerando a natureza e o escopo do projeto, um método estático numa classe utilitária</p><p>seria a solução mais adequada, pois não só elimina a duplicação, mas também garante que qualquer</p><p>alteração futura na lógica de rolagem de dados seja implementada em um único lugar, refletindo-se em</p><p>todo o código.</p><p>Para implementar essa melhoria, cria-se uma classe chamada UtilitariosDeJogo (figura 37), que</p><p>contém um método estático RolarDado (linha 5), que por sua vez encapsula a funcionalidade de gerar</p><p>um número aleatório representando o resultado da rolagem de um dado de seis faces.</p><p>1. public static class UtilitariosDeJogo</p><p>2. {</p><p>3. private static readonly Random random = new Random();</p><p>4.</p><p>5. public static int RolarDado()</p><p>6. {</p><p>7. return random.Next(1, 7);</p><p>8. }</p><p>9. }</p><p>Figura 37 – Melhoria: classe UtilitariosDeJogo</p><p>80</p><p>Unidade I</p><p>A classe UtilitariosDeJogo também mantém sua própria instância de Random, garantindo que</p><p>a geração de números aleatórios seja centralizada e consistente. Implementada a classe utilitária, o</p><p>próximo passo é refatorar as classes existentes (Jogo e Jogador) para utilizar o novo método estático.</p><p>Isso envolve remover as implementações internas de RolarDado dessas classes e substituí-las por</p><p>chamadas ao método UtilitariosDeJogo.RolarDado.</p><p>Na linha 4 da figura 38, temos um exemplo dessa aplicação no método AtualizarTurno da</p><p>classe Jogador.</p><p>1. public void AtualizarTurno(int numeroTotalDeCasas)</p><p>2. {</p><p>3. //...</p><p>4. int movimento = UtilitariosDeJogo.RolarDado();</p><p>5. //...</p><p>6. }</p><p>Figura 38 – Refatoração: método AtualizarTurno na classe Jogador</p><p>Essa mudança não só reduz a duplicação de código, mas também melhora a clareza. Qualquer</p><p>desenvolvedor que trabalhe no código pode agora entender facilmente que a rolagem de dados</p><p>é um serviço fornecido de forma centralizada, sem necessidade de se preocupar com variações na</p><p>implementação em diferentes locais do código. Essa abordagem de refatoração, focada em reduzir</p><p>a repetição de código e centralizar funcionalidades comuns, é um exemplo clássico de aplicação de</p><p>princípios de design de software limpo e eficiente. Em um ambiente de desenvolvimento colaborativo,</p><p>essa mudança também faria outros desenvolvedores entenderem e utilizarem essa funcionalidade em</p><p>outras partes do código, se necessário.</p><p>1.2 Apresentação geral dos recursos e elementos de interface no ambiente</p><p>de programação C#</p><p>A interface de programação no ambiente C# é mais do que um simples conjunto de ferramentas;</p><p>é um ecossistema robusto e flexível que suporta e eleva o trabalho do desenvolvedor, proporcionando</p><p>um ambiente no qual criatividade e eficiência podem florescer. Ao mergulhar mais profundamente</p><p>no layout-padrão do ambiente de desenvolvimento dentro do universo de programação em C#,</p><p>descobrimos um espaço meticulosamente organizado, pensado para otimizar tanto a eficiência quanto</p><p>a experiência do usuário; uma sinfonia de elementos de interface, cada um com seu papel, mas todos</p><p>trabalhando em harmonia para criar um ambiente de desenvolvimento coeso e intuitivo.</p><p>No centro desse ambiente estão as janelas de código, nas quais a programação ganha vida. Elas</p><p>são mais do que meros recipientes para texto; são o epicentro da criação, oferecendo recursos como</p><p>realce de sintaxe e autocompletar, que não apenas aumentam a legibilidade do código, mas também</p><p>81</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>aceleram o processo de escrita. A facilidade com que se pode alternar entre diferentes arquivos de</p><p>código, visualizá-los lado a lado ou mesmo em janelas separadas exemplifica a flexibilidade oferecida</p><p>ao desenvolvedor.</p><p>O realce de sintaxe permite diferenciar visualmente elementos como palavras-chave, variáveis,</p><p>strings e comentários através de cores e estilos de fonte. Ele transforma um bloco de texto em uma</p><p>paisagem codificada, na qual cada elemento se destaca claramente. Além de reduzir o cansaço visual,</p><p>ajuda o programador a identificar rapidamente a estrutura do código, erros de sintaxe e padrões. Mais</p><p>do que isso, o realce de sintaxe facilita a leitura e compreensão do código, especialmente em projetos</p><p>complexos ou ao revisar o código de outras pessoas.</p><p>Já o autocompletar age como assistente inteligente, sugere completamentos de código enquanto</p><p>o programador digita. Isso não só acelera o processo de escrita – eliminando a necessidade de digitar</p><p>cada caractere de uma função ou nome de variável –, mas também minimiza erros de digitação</p><p>e inconsistências.</p><p>Além disso, para programadores que estiverem aprendendo ou trabalhando com bibliotecas novas,</p><p>essa funcionalidade é um recurso de aprendizado, pois apresenta funções e métodos disponíveis que</p><p>talvez não sejam de conhecimento imediato. As dicas de código, por sua vez, fornecem informações</p><p>contextuais sobre funções, métodos, parâmetros e tipos de dados diretamente no editor de texto. Ao</p><p>posicionar o cursor sobre uma parte do código, uma pequena janela de informação aparece oferecendo</p><p>descrições, documentação e até mesmo exemplos de uso. Essa funcionalidade é inestimável, pois</p><p>fornece ao programador uma rápida referência sem precisar sair do contexto de codificação para buscar</p><p>documentação externa.</p><p>Paralelamente encontramos a janela de solução, que serve como mapa do tesouro para o projeto.</p><p>Ela exibe a estrutura do projeto de maneira clara e permite que o desenvolvedor navegue rapidamente</p><p>entre diferentes componentes, como classes, bibliotecas e recursos. Essa janela é um meio de</p><p>organização e um ponto de controle no qual se pode adicionar, remover ou modificar facilmente</p><p>elementos do projeto.</p><p>A estrutura de um projeto C# é fundamental para o sucesso de qualquer aplicativo, sendo composta</p><p>por uma série de arquivos e pastas organizados de maneira lógica, facilitando tanto o desenvolvimento</p><p>quanto a manutenção. No núcleo encontramos os arquivos de código-fonte (onde reside a lógica do</p><p>programa), complementados por arquivos de recursos, como imagens e arquivos de configuração,</p><p>essenciais para funcionalidade e personalização do aplicativo.</p><p>Além disso, a estrutura do projeto inclui arquivos de metadados, como o arquivo de solução (.sln)</p><p>e os arquivos de projeto (.csproj), que contêm informações vitais para compilá-lo e executá-lo. Essa</p><p>estrutura é uma forma de organizar arquivos e reflete a modularidade e a escalabilidade do projeto,</p><p>permitindo um desenvolvimento ordenado e sistemático; porém a gestão eficaz deles é tão importante</p><p>quanto sua estrutura.</p><p>O gerenciamento de soluções e projetos no ambiente C# envolve várias atividades críticas, como</p><p>configurar dependências, definir configurações de compilação e gerir múltiplas versões</p><p>82</p><p>Unidade I</p><p>ou variantes do mesmo projeto. Um aspecto crucial desse gerenciamento é a capacidade de trabalhar</p><p>com várias soluções e projetos simultaneamente, facilitando o desenvolvimento de aplicações</p><p>complexas, que podem incluir várias bibliotecas, serviços e aplicações dependentes. O ambiente de</p><p>desenvolvimento integrado (integrated development environnment – IDE) oferece ferramentas para</p><p>visualizar e manipular a estrutura do projeto, tornando o processo de adicionar, remover ou atualizar</p><p>componentes uma tarefa simples e intuitiva.</p><p>As ferramentas comuns nesse layout são diversas e abrangem uma gama de funcionalidades.</p><p>Há o painel de propriedades – no qual se pode ajustar as configurações de elementos selecionados – e</p><p>o console de saída – que oferece feedback em tempo real do estado do programa durante a execução.</p><p>As janelas de código, de solução, de propriedades e de saída oferecem uma visão ampla e ao mesmo</p><p>tempo detalhada do projeto em desenvolvimento. Essas janelas não são estáticas, mas altamente</p><p>personalizáveis, e permitem que cada desenvolvedor ajuste o ambiente ao seu estilo de trabalho.</p><p>A habilidade de arrastar, fixar ou esconder painéis assegura que a interface possa se adaptar a diversas</p><p>necessidades e preferências.</p><p>Ferramentas de diagnóstico – como depurador e janelas de inspeção de variáveis – são indispensáveis</p><p>para identificar e resolver problemas. Elas não apenas simplificam a tarefa de depuração, mas também</p><p>proporcionam insights valiosos sobre o funcionamento interno do código. Depuração e diagnóstico</p><p>são componentes vitais no desenvolvimento de software, especialmente no contexto da programação em</p><p>C#, pois representam a arte e a ciência de identificar e resolver problemas dentro de um código – tarefa</p><p>que exige tanto habilidade técnica quanto um pensamento analítico aguçado.</p><p>Ferramentas e técnicas de depuração – como pontos de interrupção e acompanhamento de execução,</p><p>além da capacidade de visualizar e alterar variáveis em tempo real – são fundamentais. Os pontos de</p><p>interrupção são, talvez, a ferramenta mais imediatamente associada à depuração; eles permitem que</p><p>o programador pause a execução do código em pontos específicos, a fim de inspecionar o estado do</p><p>programa naquele exato momento. Essa pausa controlada é valiosa pois oferece uma janela para o</p><p>mundo interno do programa, permitindo que o desenvolvedor observe o comportamento do código e</p><p>identifique discrepâncias entre o funcionamento esperado e o real.</p><p>A flexibilidade dos pontos de interrupção também é notável; eles podem ser configurados para</p><p>ativar sob condições específicas ou quando certos tipos de dados são encontrados, tornando-os uma</p><p>ferramenta poderosa para isolar e diagnosticar problemas complexos. Além disso, o acompanhamento de</p><p>execução – geralmente atuando com os pontos de interrupção – proporciona uma visão dinâmica</p><p>de como o programa se desenrola ao longo do tempo.</p><p>Essa funcionalidade permite que o desenvolvedor percorra o código passo a passo, observando como</p><p>os valores das variáveis e o estado do sistema mudam com cada operação. Esse método granular de</p><p>examinar a execução do programa é crucial para entender o fluxo de controle e a lógica do código,</p><p>especialmente quando o comportamento do programa não é imediatamente óbvio.</p><p>A visualização e a alteração de variáveis em tempo real são outra faceta vital do processo de</p><p>depuração. Durante uma sessão de depuração, o IDE permite que o programador inspecione os valores</p><p>83</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>das variáveis, fornecendo insights imediatos sobre o estado do programa. A capacidade de visualizar o</p><p>estado interno do programa nos ajuda muito a identificar onde as coisas estão indo mal; além disso,</p><p>a habilidade de modificar os valores das variáveis em tempo real oferece uma maneira poderosa de</p><p>testar hipóteses sobre a causa dos problemas, permitindo que o programador experimente correções e</p><p>veja imediatamente os resultados de tais mudanças. No entanto, a depuração e o diagnóstico em C#</p><p>vão além da simples</p><p>correção de erros; eles representam uma oportunidade para os desenvolvedores</p><p>aprofundarem o entendimento do código e melhorarem suas habilidades.</p><p>Além disso, a integração com sistemas de controle de versão diretamente no IDE facilita o</p><p>gerenciamento de mudanças no código, permitindo que equipes colaborem de maneira eficaz, aspecto</p><p>que ressalta a natureza colaborativa da programação moderna, na qual o ambiente de desenvolvimento</p><p>serve como ponto de encontro para ideias e esforços conjuntos. O IDE foi projetado com a ideia de</p><p>centralização, em que todas as ferramentas necessárias estariam ao alcance do desenvolvedor,</p><p>permitindo-lhe se concentrar na codificação sem distrações desnecessárias.</p><p>Adicionalmente, recursos como busca integrada – que permite localizar rapidamente arquivos,</p><p>classes, métodos ou mesmo linhas de código específicas – demonstram como o IDE é aliado poderoso</p><p>na otimização do tempo de desenvolvimento. As ferramentas de navegação – que incluem a</p><p>capacidade de ir diretamente para definições e referências, e a visualização de hierarquias de</p><p>herança – são inestimáveis para a compreensão e a manutenção de códigos complexos. Importante</p><p>também é a forma como o IDE facilita o entendimento e a correção de erros de código. Através de</p><p>ferramentas de diagnóstico e de sugestões de correção automática, o ambiente de desenvolvimento</p><p>não só aponta problemas, mas muitas vezes oferece soluções práticas – representando não apenas</p><p>uma economia de tempo, mas também um recurso de aprendizado contínuo para o programador.</p><p>O layout-padrão do ambiente de desenvolvimento em C#, com suas janelas e ferramentas, prima</p><p>por design e funcionalidade, não apenas refletindo as necessidades práticas dos programadores, mas</p><p>também encorajando um fluxo de trabalho mais intuitivo e produtivo. Em última análise, esse layout</p><p>não é um mero aspecto técnico do desenvolvimento de software, mas um catalisador para criatividade</p><p>e inovação no mundo da programação em C#.</p><p>1.3 Ambiente de desenvolvimento integrado (IDE)</p><p>A escolha de um IDE para programar em C# é decisão crucial para qualquer desenvolvedor, pois é mais</p><p>do que uma simples ferramenta; é o companheiro diário do programador e influencia significativamente</p><p>a eficiência, a comodidade e até mesmo a qualidade do código produzido. Entre as opções disponíveis,</p><p>Visual Studio e Rider se destacam, embora várias alternativas também mereçam consideração.</p><p>O Visual Studio, desenvolvido pela Microsoft, é o mais escolhido para programar em C#. Sua integração</p><p>perfeita com a linguagem C# eo .NET framework fazem dele uma escolha natural, especialmente para</p><p>quem trabalha em projetos que se alinham estreitamente com o ecossistema da Microsoft. Esse IDE não</p><p>é apenas robusto em termos de recursos – como depuração avançada, gestão de pacotes e suporte a</p><p>controle de versão –, mas também é conhecido pela sua interface altamente personalizável e produtiva.</p><p>84</p><p>Unidade I</p><p>Além disso, oferece suporte para uma vasta gama de projetos, desde aplicações web até desenvolvimento</p><p>de jogos, tornando-o uma ferramenta versátil para muitos desenvolvedores.</p><p>Por outro lado, o Rider, da JetBrains, se tornou popular especialmente para quem valoriza uma</p><p>experiência de desenvolvimento ágil e eficiente. Embora seja uma opção mais recente em comparação</p><p>com o Visual Studio, ele se destaca por sua performance rápida e recursos de refatoração inteligente,</p><p>herdados de outras ferramentas da JetBrains, como o ReSharper. Sua interface é intuitiva, e sua</p><p>capacidade de trabalhar sem problemas em diferentes sistemas operacionais – incluindo Windows,</p><p>macOS e Linux – o torna atraente para equipes de desenvolvimento que operam em ambientes</p><p>heterogêneos.</p><p>Além dessas opções mais conhecidas, alternativas podem ser adequadas a depender das necessidades</p><p>do desenvolvedor ou do projeto. Ferramentas como MonoDevelop ou Visual Studio Code oferecem</p><p>ambientes mais leves e são frequentemente escolhidas para projetos menores ou desenvolvedores que</p><p>buscam simplicidade e velocidade. Cada alternativa tem suas forças e limitações, como um conjunto</p><p>de recursos diferente, suporte a extensões ou integração com determinadas tecnologias e frameworks.</p><p>Visual Studio e Visual Studio Code são duas ferramentas poderosas, mas com propósitos</p><p>e capacidades distintas. Essa diferença não é meramente nominal; ela reflete abordagens</p><p>fundamentalmente diferentes para desenvolver software. O Visual Studio – IDE completo e robusto – é</p><p>conhecido por sua ampla gama de funcionalidades e sua integração profunda com o ecossistema</p><p>.NET e C#. É uma solução abrangente, que oferece suporte extensivo para desenvolver software em</p><p>várias plataformas, incluindo desktop, mobile, web e até mesmo jogos.</p><p>O Visual Studio se destaca pela sua capacidade de gerenciar projetos complexos e oferecer</p><p>ferramentas avançadas para depuração, design de interface do usuário, gestão de banco de dados,</p><p>análise de desempenho e outras funcionalidades integradas, sendo particularmente favorável para</p><p>projetos que requerem abordagem intensiva e detalhada, como aplicações empresariais ou grandes</p><p>sistemas de software. Já o Visual Studio Code (VS Code) é um editor de código-fonte mais leve, porém</p><p>extremamente poderoso.</p><p>Embora também suporte C# e outras linguagens, o VS Code focaliza simplicidade, velocidade e</p><p>flexibilidade. Sua interface é mais enxuta, e ele opera com uma filosofia de extensões, permitindo aos</p><p>usuários adicionar apenas as funcionalidades de que precisam, o que o torna notavelmente rápido e</p><p>responsivo. É ideal para desenvolvimento web, scripts e projetos menores ou mais ágeis, nos quais a</p><p>sobrecarga de um IDE completo não é necessária nem desejável.</p><p>Um dos pontos mais evidentes de diferenciação é o consumo de recursos do sistema. O Visual</p><p>Studio, com sua ampla gama de funcionalidades embutidas, tende a ser mais exigente em termos de</p><p>recursos do sistema, o que o torna mais adequado para máquinas com especificações mais robustas.</p><p>Em contraste, sendo mais leve, o VS Code pode funcionar de maneira eficiente mesmo em hardware</p><p>menos potente.</p><p>85</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>Outra distinção importante é sua abordagem de design e usabilidade. O Visual Studio oferece uma</p><p>experiência mais integrada e coesa, com ferramentas e funcionalidades projetadas para trabalhar</p><p>harmoniosamente dentro do seu ecossistema. Por outro lado, o VS Code fornece uma experiência mais</p><p>modular e permite uma personalização mais granular através de extensões e plugins, o que pode atrair</p><p>desenvolvedores que preferem uma abordagem mais ágil e personalizada.</p><p>A escolha de uma IDE para C# depende de uma variedade de fatores, incluindo exigências específicas</p><p>do projeto, a preferência pessoal do desenvolvedor, a necessidade de integração com outras ferramentas</p><p>e plataformas, e até mesmo considerações de custo, já que há IDEs gratuitas e outras não. Portanto,</p><p>essa escolha é tanto uma questão de adequação técnica quanto de conforto pessoal e eficiência no</p><p>fluxo de trabalho; para o programador de C#, navegar por essas opções e encontrar a ferramenta certa</p><p>é fundamental para estabelecer um ambiente de desenvolvimento que não só atenda às necessidades</p><p>do projeto, mas também amplifique suas próprias habilidades e produtividade.</p><p>Observação</p><p>Neste livro-texto usaremos o Visual Studio, que contém a versão</p><p>Community, amplamente acessível e gratuita (usaremos a versão 17.8.5 com a</p><p>versão 4.8.09037 do .NET Framework). Também existem edições pagas – como</p><p>o Visual Studio Professional e o Visual Studio Enterprise –, e cada uma oferece</p><p>um conjunto de recursos e capacidades que vão além do que é disponibilizado na</p><p>versão Community. Versões pagas são projetadas para atender às necessidades</p><p>de desenvolvedores individuais e também para se alinhar aos requisitos de</p><p>equipes maiores e projetos de uma escala mais ampla e complexa.</p><p>Visual Studio Professional é um passo adiante em relação à edição Community, pois oferece</p><p>funcionalidades adicionais úteis para</p><p>pequenas e médias empresas, incluindo recursos avançados de</p><p>colaboração e integração com ferramentas de DevOps, facilitando o trabalho em equipe e a gestão</p><p>de projetos de software. Além disso, a versão oferece um suporte mais abrangente para testes automatizados</p><p>e ferramentas de diagnóstico – cruciais para desenvolver software em ambiente profissional.</p><p>Observação</p><p>DevOps – combinação das palavras development (desenvolvimento) e</p><p>operations (operações) – é uma filosofia e prática que permeia o mundo da</p><p>tecnologia da informação, enfatizando a integração e comunicação entre</p><p>desenvolvedores de software e outros profissionais de TI. A essência do</p><p>DevOps reside na busca por automatizar e monitorar todas as fases de</p><p>construção de software, desde integração, teste, liberação até a implantação</p><p>e gestão da infraestrutura – abordagem que visa cultivar uma cultura</p><p>de colaboração e eficiência, reduzir o ciclo de vida do desenvolvimento de</p><p>sistemas e garantir entrega contínua de alta qualidade.</p><p>86</p><p>Unidade I</p><p>O DevOps pressupõe que times de desenvolvimento e operações não</p><p>devem operar em silos, mas colaborar de forma estreita durante todo o</p><p>ciclo de vida do software. Essa colaboração significa que as mudanças são</p><p>mais confiáveis e os problemas podem ser resolvidos mais rapidamente,</p><p>com menos interrupções no serviço. Além disso, enfatiza a importância</p><p>de feedback contínuo, adaptabilidade e aprendizado constante, com o</p><p>objetivo de melhorar e inovar os processos de desenvolvimento e operação.</p><p>As práticas do DevOps incluem integração, entrega e implantação</p><p>contínuas, que juntas permitem às equipes de TI responder mais</p><p>rapidamente às necessidades do negócio. Integração contínua refere-se</p><p>à prática de mesclar automaticamente todas as alterações de código em</p><p>um repositório comum, seguida por testes automáticos. Entrega contínua</p><p>expande isso ao automatizar a liberação de mudanças para um ambiente</p><p>de teste ou produção, enquanto a implantação contínua leva isso ainda</p><p>mais longe, automatizando todo o processo, até a produção. DevOps</p><p>também envolve a automação de infraestrutura, que se traduz em</p><p>gerenciar e provisionar infraestruturas através de código e ferramentas,</p><p>facilitando processos escaláveis e eficientes.</p><p>Saiba mais</p><p>Para se aprofundar no tema DevOps e suas práticas, recomendamos os</p><p>dois livros a seguir. Ambos oferecem uma visão abrangente e detalhada do</p><p>DevOps, abordando tanto aspectos teóricos quanto práticos. São recursos</p><p>valiosos para quem busca entender e implementar suas práticas.</p><p>KIM, G. et al. Manual de DevOps: como obter agilidade, confiabilidade e</p><p>segurança em organizações tecnológicas. São Paulo: Alta Books, 2018.</p><p>WALLS, C. Spring boot in action. Nova York: Manning, 2016.</p><p>A versão Enterprise, por outro lado, é a mais robusta das edições do Visual Studio, destinada a grandes</p><p>organizações e projetos de alta complexidade. Ela se destaca por incluir ferramentas de desenvolvimento,</p><p>teste, entrega e diagnóstico de última geração, oferecendo funcionalidades como testes de carga</p><p>avançados, análise de código mais sofisticada e ferramentas de simulação e modelagem – essenciais</p><p>para projetos em larga escala. Além disso, fornece recursos extensivos para a segurança do aplicativo e</p><p>a gestão do ciclo de vida do software, permitindo que equipes gerenciem eficientemente todas as fases</p><p>do desenvolvimento, desde a concepção até a manutenção.</p><p>Embora a versão Community do Visual Studio seja eficiente para estudantes, desenvolvedores</p><p>individuais e pequenas equipes, as versões Professional e Enterprise oferecem um nível de suporte e</p><p>funcionalidades essenciais em ambientes empresariais. As versões pagas proporcionam ferramentas</p><p>87</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>mais avançadas e suporte técnico superior, além de opções de licenciamento mais adequadas a</p><p>necessidades comerciais e organizacionais.</p><p>Ao adentrar no mundo do Visual Studio, a experiência começa com a instalação e configuração – etapas</p><p>nas quais o Visual Studio Installer desempenha papel crucial, especialmente na versão Community.</p><p>Esses primeiros passos são fundamentais para moldar o IDE de acordo com as preferências individuais</p><p>e as demandas de cada projeto.</p><p>Visual Studio Installer é a porta de entrada para personalizar o ambiente de desenvolvimento. Ele</p><p>guia o usuário por um processo de instalação intuitivo, no qual é possível selecionar componentes</p><p>específicos, como ferramentas para desenvolvimento web, aplicações de desktop, programação mobile,</p><p>entre outros. Essa abordagem modular permite que os desenvolvedores configurem o Visual Studio</p><p>para atender exatamente às suas necessidades, evitando a instalação de recursos desnecessários</p><p>que poderiam sobrecarregar o sistema. Para usuários da versão Community (gratuita), isso significa</p><p>acessar um conjunto robusto de ferramentas de desenvolvimento sem custo associado, uma vantagem</p><p>significativa para estudantes, desenvolvedores independentes e pequenas equipes.</p><p>Após a instalação, a configuração básica no Visual Studio Community não difere muito das edições</p><p>pagas e oferece uma gama de opções de personalização. Desenvolvedores podem ajustar desde as</p><p>configurações de linguagem e editor até preferências de depuração e desempenho. Essa flexibilidade</p><p>é vital para garantir que o ambiente de desenvolvimento esteja alinhado com as preferências</p><p>individuais e as exigências do projeto, estabelecendo uma base sólida para uma programação eficiente</p><p>e confortável.</p><p>A personalização do layout e das preferências é uma das características mais apreciadas do Visual</p><p>Studio, incluindo a versão Community. Desenvolvedores têm liberdade de moldar a interface do usuário</p><p>de acordo com seus hábitos e estilos de trabalho, o que abrange reorganização de painéis e janelas,</p><p>seleção de temas de cores e fontes e configuração de barras de ferramentas e menus para acesso</p><p>rápido e eficiente. Essa personalização vai além da estética e influencia diretamente a produtividade e</p><p>o conforto do desenvolvedor.</p><p>Adicionalmente, tanto no Visual Studio Community quanto nas versões pagas, a capacidade</p><p>de expandir funcionalidades do IDE por extensões e complementos é uma vantagem significativa.</p><p>Essas extensões – disponíveis pelo gerenciador de extensões integrado – permitem adicionar desde</p><p>utilitários de produtividade simples até ferramentas avançadas para analisar códigos e gerenciar</p><p>projetos. Essa flexibilidade faz do Visual Studio, independentemente da edição, um ambiente</p><p>altamente adaptável, capaz de atender às necessidades em evolução de desenvolvedores e projetos.</p><p>Uma extensão popular costuma oferecer suporte aprimorado para linguagens de programação</p><p>específicas ou frameworks; por exemplo, extensões para linguagens como JavaScript, Python ou para</p><p>frameworks como Angular e React podem oferecer funcionalidades como realce de sintaxe melhorado,</p><p>autocompletar inteligente e ferramentas de depuração específicas para essas tecnologias. Essas extensões</p><p>tornam o Visual Studio mais adaptável a diferentes stacks tecnológicas, ampliando sua utilidade para</p><p>além de .NET e C#.</p><p>88</p><p>Unidade I</p><p>Outra categoria de extensões foca a melhoria da produtividade e eficiência do código. Ferramentas</p><p>como analisadores de código e refatoradores automáticos ajudam a identificar problemas potenciais,</p><p>sugerem melhorias e até automatizam a refatoração. Extensões como ReSharper ou CodeMaid</p><p>são exemplos disso e oferecem uma série de funcionalidades para limpeza de código, otimização e</p><p>reestruturação, o que ajuda a mantê-lo limpo, legível e eficiente.</p><p>Extensões relacionadas ao controle de versão e colaboração também são úteis, pois podem oferecer</p><p>integrações mais profundas com sistemas como Git e facilitar operações como commit, merge, push e pull</p><p>diretamente do IDE. Ferramentas como GitLens, por exemplo, ampliam as capacidades do Visual Studio</p><p>em termos de visualização de histórico de commits, comparação de branches e outras funcionalidades</p><p>que tornam o trabalho colaborativo mais gerenciável e transparente</p><p>(abordaremos o Git com mais</p><p>detalhes neste tópico).</p><p>Além disso, há extensões voltadas para a interface do usuário e experiência de desenvolvimento,</p><p>como temas personalizados (por exemplo o Color Theme Editor), gerenciadores de janelas e ferramentas</p><p>de navegação de código (como o Visual Assist). Essas extensões permitem personalizar a aparência do IDE,</p><p>organizar o layout de trabalho de forma mais eficiente e navegar rapidamente entre arquivos e projetos.</p><p>Por fim, extensões de integração contínua e entrega contínua (CI/CD) e gerenciamento de projetos</p><p>também são populares, como Azure DevOps Extensions for Visual Studio, Jenkins Tools for Visual Studio</p><p>e GitHub Actions for Visual Studio. Elas permitem que os desenvolvedores configurem pipelines de CI/CD</p><p>dentro do Visual Studio, integrem ferramentas de rastreamento de bugs e projetos e gerenciem o ciclo</p><p>de vida do desenvolvimento de software de maneira mais eficaz.</p><p>Dentro do ambiente do Visual Studio, uma tarefa essencial e frequente é a gestão de dependências</p><p>e pacotes, processo que garante a disponibilidade e atualização de todos os componentes necessários</p><p>para um projeto. Essa gestão é crucial para a integridade e o sucesso de qualquer projeto de software, e</p><p>no Visual Studio isso é feito predominantemente pelo NuGet, gerenciador de pacotes para .NET.</p><p>Integrado ao Visual Studio, o NuGet serve como plataforma eficiente para gerenciar pacotes</p><p>e permite que desenvolvedores busquem, instalem, atualizem e removam bibliotecas e ferramentas</p><p>dentro de seus projetos de software de forma controlada e consistente. Essa funcionalidade é vital para</p><p>manter a compatibilidade e a funcionalidade dos projetos, especialmente num ambiente onde múltiplas</p><p>dependências e versões específicas podem determinar o sucesso ou falha de uma aplicação.</p><p>Através do NuGet, desenvolvedores têm acesso a um vasto repositório de pacotes, o que facilita a</p><p>descoberta e a incorporação de bibliotecas e ferramentas de terceiros em seus projetos. Esses pacotes</p><p>podem variar desde frameworks e bibliotecas de user interface (UI) a pacotes para operações de rede</p><p>ou manipulação de dados. A integração do NuGet no Visual Studio simplifica o processo de adicionar</p><p>essas dependências, permitindo fazer isso diretamente através da interface do IDE, sem precisar baixar e</p><p>configurar bibliotecas manualmente. Além disso, um aspecto crucial do gerenciamento de pacotes no</p><p>Visual Studio é a capacidade de referenciar bibliotecas externas – funcionalidade que permite incluir</p><p>e utilizar código que não tenha sido criado dentro do ambiente do seu projeto atual.</p><p>89</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>Referenciar bibliotecas externas é uma prática comum, especialmente em projetos grandes e</p><p>complexos, em que é necessário usar funcionalidades já desenvolvidas e testadas, economizando</p><p>tempo e recursos. Bibliotecas externas são coleções de código desenvolvidas por terceiros que oferecem</p><p>funcionalidades específicas que podem ser incorporadas em diversos projetos de software. Esse conceito</p><p>é central para reutilizar código e permite incorporar soluções já existentes e testadas, em vez de criar</p><p>novas funcionalidades do zero.</p><p>Biblioteca externa, em sua essência, é um conjunto de funções, métodos ou classes que executam</p><p>tarefas específicas e podem variar em complexidade, abrangendo desde simples algoritmos de ordenação</p><p>até complexos frameworks que facilitam o desenvolvimento de aplicações web ou móveis; e seu uso</p><p>traz uma série de vantagens. Primeiramente, economiza tempo e recursos, já que os desenvolvedores</p><p>podem se concentrar nas partes únicas de seus projetos em vez de reinventar soluções para problemas</p><p>comuns. Além disso, como geralmente são bem testadas pela comunidade, elas tendem a ser confiáveis</p><p>e estáveis, aumentando a qualidade do software desenvolvido.</p><p>No entanto, bibliotecas externas também requerem uma gestão cuidadosa. É fundamental que os</p><p>desenvolvedores estejam atentos às versões das bibliotecas usadas, para garantir compatibilidade com o</p><p>projeto em desenvolvimento; outrossim, segurança e manutenção de bibliotecas são aspectos importantes</p><p>a considerar. Dependendo da fonte da biblioteca, podem surgir preocupações quanto à segurança do</p><p>código e à frequência com que a biblioteca é atualizada para corrigir bugs ou vulnerabilidades.</p><p>Nesse contexto, como o NuGet permite procurar, instalar e gerenciar as dependências de forma</p><p>eficiente, ele também garante que os desenvolvedores incorporem facilmente funcionalidades externas</p><p>em seus projetos, mantendo ao mesmo tempo o controle das versões e a compatibilidade dessas</p><p>bibliotecas com o restante do código. Entre os pacotes mais populares e amplamente utilizados está o</p><p>Newtonsoft.Json, uma biblioteca para trabalhar com JSON e oferecer funcionalidades como serialização</p><p>e desserialização de objetos JSON de maneira eficiente.</p><p>Observação</p><p>JSON – que significa JavaScript Object Notation – é um formato leve</p><p>para troca de dados. Amplamente utilizado para transmitir informações</p><p>entre um servidor e um aplicativo web ou móvel, se tornou um dos formatos</p><p>mais populares para isso, principalmente devido à sua simplicidade</p><p>e facilidade de leitura tanto por humanos quanto por máquinas. Sua</p><p>popularidade decorre da facilidade de uso e da eficácia na manipulação</p><p>de dados JSON – tarefa comum na maioria dos projetos modernos de</p><p>desenvolvimento web e móvel.</p><p>Outro exemplo é o Entity Framework, um object-relational mapper (ORM) que permite trabalhar com</p><p>bancos de dados usando objetos .NET, simplificando o acesso e a manipulação de dados. A capacidade</p><p>de mapear classes .NET para tabelas de banco de dados e vice-versa sem precisar escrever consultas</p><p>90</p><p>Unidade I</p><p>SQL detalhadas torna o Entity Framework uma ferramenta poderosa para quem trabalha com aplicações</p><p>orientadas a dados.</p><p>Para projetos de interface gráfica, bibliotecas como MahApps.Metro ou MaterialDesignInXamlToolkit</p><p>são populares, pois oferecem conjuntos de controles e estilos que permitem criar interfaces</p><p>de usuário modernas e atraentes com estilos Metro e Material Design, respectivamente. Elas são</p><p>essenciais para quem deseja criar aplicações com aparência sofisticada e uma experiência de usuário</p><p>rica. No âmbito do desenvolvimento web, pacotes como AspNet.Mvc ou AspNetCore.Mvc são cruciais</p><p>para criar aplicações web, pois fornecem a estrutura para construir aplicações escaláveis e de alto</p><p>desempenho com o padrão model-view-controller (MVC), facilitando a separação de lógica, interface</p><p>do usuário e controle.</p><p>Além disso, para testes e garantia de qualidade, pacotes como xUnit, NUnit ou Moq são amplamente</p><p>utilizados, porque oferecem frameworks para escrever e executar testes unitários e de integração, bem</p><p>como ferramentas para simular (mocking) objetos e serviços, garantindo que o software desenvolvido</p><p>seja testado de maneira abrangente e eficaz.</p><p>Não é raro confundir gestão de extensões com gestão de dependências e pacotes;</p><p>ambos desempenham papéis fundamentais e são cruciais para a eficiência e funcionalidade do ambiente</p><p>de desenvolvimento, mas atendem a necessidades diferentes. O gerenciador de extensões no Visual</p><p>Studio é projetado para expandir e personalizar o ambiente de desenvolvimento adicionando novas</p><p>funcionalidades ou melhorias às já existentes.</p><p>Esse gerenciador permite procurar, instalar e gerenciar extensões, que são essencialmente</p><p>complementos ou plugins que aprimoram o IDE e podem variar amplamente em sua função,</p><p>abrangendo desde simples melhorias na interface do usuário até ferramentas complexas para análise de</p><p>código, suporte a novas linguagens de programação ou integração com outros serviços e ferramentas.</p><p>A principal função do gerenciador de extensões é, portanto, personalizar e melhorar a experiência de</p><p>desenvolvimento, oferecendo flexibilidade e adaptabilidade ao ambiente do Visual Studio.</p><p>Por outro lado, a gestão de dependências e pacotes – particularmente através do NuGet no</p><p>Visual Studio – lida com a manutenção e o gerenciamento</p><p>de bibliotecas e componentes de software</p><p>necessários para um projeto específico funcionar corretamente. Esse processo envolve identificação,</p><p>instalação, atualização e remoção de pacotes de software – que podem incluir bibliotecas, frameworks</p><p>e outras ferramentas – necessários para construir e executar um projeto.</p><p>A gestão eficaz dessas dependências é crucial para garantir integridade, compatibilidade e bom</p><p>funcionamento do software desenvolvido. Diferente do gerenciador de extensões (que aprimora o</p><p>IDE em si), a gestão de dependências e pacotes foca a composição e o funcionamento do projeto</p><p>de software em desenvolvimento. Assim, enquanto o gerenciador de extensões atua mais como</p><p>personalizador e ampliador do ambiente de desenvolvimento, a gestão de dependências e pacotes</p><p>é atividade essencial dentro do próprio desenvolvimento de software, garantindo que todos os</p><p>componentes necessários estejam disponíveis e funcionando harmoniosamente.</p><p>91</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>No ambiente do Visual Studio, o controle de versão integrado, sobretudo o Git, representa uma</p><p>dimensão vital do desenvolvimento de software. Essa integração não é meramente uma funcionalidade</p><p>adicional; é fundamental para a gestão eficaz do código-fonte, especialmente num contexto de</p><p>trabalho colaborativo. O Visual Studio abraça essa necessidade ao incorporá-lo diretamente no IDE,</p><p>oferecendo uma experiência de controle de versão fluida e acessível, essencial para projetos modernos</p><p>de software.</p><p>Git é um sistema de controle de versão distribuído, amplamente utilizado no desenvolvimento de</p><p>software para rastrear mudanças no código-fonte ao longo do tempo. Desenvolvido por Linus Torvalds,</p><p>criador do Linux, ele facilita a colaboração entre desenvolvedores, permitindo que múltiplos usuários</p><p>trabalhem em diferentes aspectos de um projeto simultaneamente, sem interferir uns nos outros. Esse</p><p>sistema é caracterizado por sua eficiência, velocidade e suporte para desenvolvimento não linear, graças</p><p>a características como branches e merges.</p><p>Uma branch no Git é basicamente uma linha independente de desenvolvimento. Os desenvolvedores</p><p>podem criar branches para trabalhar em novas funcionalidades ou correções, sem afetar a branch</p><p>principal (geralmente chamada master ou main). Completo e testado o trabalho na branch, ele pode ser</p><p>integrado de volta à branch principal através de um merge ou rebase.</p><p>A integração do Git no Visual Studio facilita significativamente o trabalho dos desenvolvedores.</p><p>Com o Git embutido, os usuários podem realizar a maioria das operações de controle de versão sem</p><p>sair do IDE, incluindo tarefas básicas – como commit, push, pull e fetch –, assim como operações mais</p><p>avançadas – como merge e rebase. Essa integração significa que desenvolvedores podem manter o</p><p>foco no ambiente de desenvolvimento, sem a necessidade de alternar para uma ferramenta de linha de</p><p>comando ou cliente Git separado. A abordagem unificada economiza tempo e reduz a complexidade</p><p>do gerenciamento de código.</p><p>Agora, um pouco sobre os conceitos mencionados:</p><p>• Um commit no Git é como uma fotografia do estado atual de um projeto. Ele registra o estado</p><p>atual dos arquivos no repositório, servindo como ponto de referência que pode ser revertido ou</p><p>comparado com outros estados. Cada commit contém uma mensagem que descreve as mudanças</p><p>realizadas, facilitando o entendimento do histórico do projeto.</p><p>• O comando push envia commits locais a um repositório remoto, como GitHub ou Bitbucket.</p><p>Essa ação atualiza o repositório remoto com mudanças locais, tornando-as acessíveis a outros</p><p>colaboradores do projeto.</p><p>• Pull é o processo de atualizar o repositório local com mudanças feitas por outros desenvolvedores</p><p>no repositório remoto. Quando se faz um pull, o Git automaticamente tenta mesclar as mudanças</p><p>do repositório remoto com o estado local do código.</p><p>• Fetch é semelhante ao pull, mas em vez de mesclar automaticamente ele apenas busca as mudanças</p><p>do repositório remoto e as armazena em uma branch local. Isso permite ao desenvolvedor revisar</p><p>as mudanças antes de integrá-las ao trabalho atual.</p><p>92</p><p>Unidade I</p><p>• Rebase é uma técnica que integra mudanças de uma branch para outra. Diferentemente do</p><p>merge, que cria um novo commit para unificar duas histórias de commit, o rebase reescreve a</p><p>história ao aplicar os commits de uma branch sobre outra, resultando em um histórico de projeto</p><p>mais linear.</p><p>Uma analogia pode ser feita com o mundo da escrita e publicação de um jornal colaborativo.</p><p>Imagine que um grupo de jornalistas esteja trabalhando em diferentes artigos para a próxima edição de</p><p>um jornal. Cada um trabalha em seu próprio artigo, mas todos os artigos precisam ser combinados para</p><p>formar a edição completa. O ato de commit é semelhante a um jornalista finalizando um rascunho do</p><p>seu artigo. Esse rascunho é um registro completo do artigo naquele momento, assim como commit é o</p><p>registro de estado do código-fonte.</p><p>O comando push é como enviar o rascunho finalizado do artigo para a redação do jornal, onde</p><p>ele será revisado e eventualmente publicado. Similarmente, no Git, push envia as mudanças locais</p><p>para um repositório remoto. Por outro lado, pull é como um jornalista recebendo as últimas versões</p><p>dos artigos de seus colegas para revisar ou referenciar. No Git, pull atualiza o repositório local com</p><p>as mudanças feitas no repositório remoto. Fetch é como receber as últimas versões dos artigos sem</p><p>imediatamente começar a trabalhar nelas; é uma maneira de estar ciente das contribuições dos</p><p>outros sem mesclá-las com o próprio trabalho.</p><p>Rebase pode ser visto como um jornalista reescrevendo seu artigo para incorporar informações</p><p>atualizadas ou para se alinhar melhor com os outros artigos; no Git, rebase reestrutura a série de</p><p>commits para tornar o histórico mais limpo e organizado. Branch é similar a um jornalista trabalhando</p><p>em um artigo especial ou uma série separada que não faz parte da edição principal; ele pode trabalhar</p><p>de forma independente sem afetar o conteúdo principal. Finalmente, merge é como combinar os</p><p>artigos de vários jornalistas em uma única edição. No Git, é o processo de combinar as mudanças de</p><p>diferentes branches em uma única linha de desenvolvimento.</p><p>A analogia do jornal colaborativo ajuda a entender como o Git facilita a colaboração e o</p><p>gerenciamento de várias linhas de trabalho em desenvolvimento de software, garantindo que todas</p><p>as contribuições sejam integradas de maneira organizada e eficiente para o produto final. Além disso,</p><p>o Visual Studio oferece ferramentas visuais e intuitivas para a colaboração e o gerenciamento de</p><p>branches. Essas ferramentas permitem que os desenvolvedores visualizem a estrutura de branches</p><p>de seu projeto, facilitando a compreensão do fluxo de trabalho e a gestão de múltiplas linhas de</p><p>desenvolvimento.</p><p>Por exemplo, desenvolvedores podem facilmente alternar entre diferentes branches, criar modelos</p><p>novos para experimentações ou funcionalidades específicas e mesclar mudanças de um branch para</p><p>outro. Essa capacidade de gerenciá-los de forma eficiente é crucial em ambientes de desenvolvimento</p><p>ágil, onde o trabalho em várias funcionalidades ou correções pode ocorrer simultaneamente.</p><p>Também podemos destacar, no ambiente do Visual Studio, as ferramentas de análise de código</p><p>e refatoração, que desempenham papel crucial ao contribuir significativamente com a qualidade e</p><p>93</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>manutenção do software. Elas não são apenas complementos úteis; são essenciais para identificar</p><p>problemas em potencial no código e torná-lo mais limpo, eficiente e sustentável ao longo do tempo.</p><p>Refatoração é o processo de modificar o código-fonte de um software sem alterar seu comportamento</p><p>funcional externo, com o objetivo de melhorar estrutura interna, legibilidade, eficiência ou manutenção</p><p>do código. Essa prática é parte fundamental do desenvolvimento de software, pois ajuda a manter o</p><p>código limpo, organizado e fácil de entender, o que é crucial</p><p>para sua manutenção e expansão.</p><p>Na figura 39, um exemplo de refatoração manual no nosso jogo.</p><p>1. public void ExecutarJogo()</p><p>2. {</p><p>3. //... código existente...</p><p>4. foreach (Jogador jogador in jogadores)</p><p>5. {</p><p>6. // Lógica existente para jogar turno</p><p>7.</p><p>8. if (VerificarEAnunciarVencedor())</p><p>9. {</p><p>10. jogoTerminado = true;</p><p>11. break;</p><p>12. }</p><p>13. }</p><p>14. //... código existente...</p><p>15. }</p><p>16. private bool VerificarEAnunciarVencedor()</p><p>17. {</p><p>18. // Implementação do método para verificar se o jogo terminou e</p><p>anunciar o vencedor</p><p>19. //... código existente...</p><p>20. }</p><p>Figura 39 – Refatoração do método ExecutarJogo da classe Jogo</p><p>Inicialmente, a classe Jogo parece sobrecarregada de responsabilidades, desde a gestão de jogadores</p><p>até o controle do fluxo do jogo. Podemos começar refatorando essa classe para melhor organizar</p><p>suas responsabilidades e melhorar a clareza do código. Primeiramente, a lógica dentro do método</p><p>ExecutarJogo pode ser simplificada e dividida em métodos menores; por exemplo, o código que verifica</p><p>se o jogo terminou e anuncia o vencedor pode ser movido para um método separado chamado</p><p>VerificarEAnunciarVencedor. Isso não só torna o método ExecutarJogo mais conciso, mas também torna</p><p>o código mais fácil de entender e manter.</p><p>94</p><p>Unidade I</p><p>Em seguida, na figura 40, a lógica de processamento de cada casa no método ProcessarCasa pode ser</p><p>extraída para métodos separados. Por exemplo, o processamento do duelo de honra pode ser extraído</p><p>para um novo método ProcessarDueloDeHonra, simplificando o ProcessarCasa e tornando o código</p><p>mais modular.</p><p>1. private void ProcessarCasa(Casa casa, Jogador jogador)</p><p>2. {</p><p>3. //... código existente...</p><p>4.</p><p>5. switch (casa.Tipo)</p><p>6. {</p><p>7. case TipoCasa.DueloDeHonra:</p><p>8. ProcessarDueloDeHonra(jogador);</p><p>9. break;</p><p>10. //... outros casos...</p><p>11. }</p><p>12. }</p><p>13.</p><p>14. private void ProcessarDueloDeHonra(Jogador jogador)</p><p>15. {</p><p>16. // Lógica para processar um duelo de honra</p><p>17. //... código existente...</p><p>18. }</p><p>Figura 40 – Refatoração do método ProcessarCasa</p><p>1. public void AtualizarTurno(int numeroTotalDeCasas)</p><p>2. {</p><p>3. //... código existente para verificar pausa...</p><p>4. int movimento = RolarDado();</p><p>5. MoverJogador(movimento, numeroTotalDeCasas);</p><p>6. }</p><p>7. private void MoverJogador(int movimento, int numeroTotalDeCasas)</p><p>8. {</p><p>9. // Lógica para mover o jogador</p><p>10. //... código existente...</p><p>11. }</p><p>Figura 41 – Refatoração do método AtualizarTurno</p><p>95</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>Além disso, a classe Jogador pode ser refatorada para encapsular melhor suas responsabilidades.</p><p>Na figura 41, por exemplo, o método AtualizarTurno pode ser dividido em métodos menores, como</p><p>RolarDado e MoverJogador, melhorando a clareza e a manutenção do código – essas são apenas algumas</p><p>das muitas maneiras possíveis de refatorar o código. O objetivo é sempre torná-lo mais limpo e mais</p><p>fácil de entender e de manter, sem alterar o comportamento funcional do programa.</p><p>As ferramentas de refatoração automática no Visual Studio são instrumentos poderosos para</p><p>melhorar a estrutura do código sem alterar seu comportamento funcional, envolvendo mudanças</p><p>como renomear variáveis, métodos e classes para nomes mais descritivos, dividir métodos ou classes</p><p>grandes em unidades menores e mais gerenciáveis, e remover código duplicado. Essas ações, embora</p><p>pareçam pequenas, têm impacto significativo na legibilidade e manutenção do código. A refatoração</p><p>automática no Visual Studio torna o processo mais eficiente e permite que desenvolvedores apliquem</p><p>essas mudanças com poucos cliques, garantindo ao mesmo tempo que as alterações sejam consistentes</p><p>em todo o projeto.</p><p>Já a análise estática de código no Visual Studio é um processo automatizado que examina o</p><p>código-fonte antes de ser executado. Essa análise busca padrões comuns que podem indicar erros, como</p><p>uso incorreto de sintaxe, potenciais vazamentos de memória, referências nulas e padrões de codificação</p><p>inseguros ou ineficientes.</p><p>O que torna a análise estática tão valiosa é sua capacidade de identificar problemas que podem não</p><p>ser óbvios durante testes ou mesmo na execução normal do programa. Ao destacar essas questões cedo</p><p>no ciclo de desenvolvimento, ela ajuda a prevenir bugs que poderiam ser custosos ou problemáticos</p><p>para corrigir mais tarde.</p><p>1.4 Introdução ao .NET Framework e .NET Core</p><p>Plataforma de desenvolvimento de software criada pela Microsoft, .NET Framework representa um</p><p>marco significativo na história da programação. Desde seu lançamento no início dos anos 2000, evoluiu</p><p>de um sistema apenas para Windows para uma plataforma versátil, capaz de suportar uma ampla</p><p>gama de aplicações em diferentes sistemas operacionais.</p><p>Framework, no desenvolvimento de software, é uma estrutura ou plataforma pré-concebida</p><p>que serve como base para desenvolvê-lo e organizá-lo, fornecendo uma fundação e um conjunto de</p><p>diretrizes para os desenvolvedores poderem construir e gerenciar aplicações de software de forma mais</p><p>eficiente e padronizada. Pode incluir bibliotecas de códigos, compiladores, interfaces de programação</p><p>de aplicações (APIs) e outras ferramentas que, juntas, facilitam o processo.</p><p>Seu principal propósito é fornecer uma estrutura genérica e reutilizável, que permita aos</p><p>desenvolvedores focar partes específicas de suas aplicações em vez de recriar do zero componentes e</p><p>estruturas comuns. Isso economiza tempo e esforço e ajuda a garantir que a aplicação seja construída</p><p>com práticas de codificação consistentes e eficientes. Além disso, como muitos frameworks são</p><p>amplamente usados e testados, eles tendem a ser confiáveis e robustos.</p><p>96</p><p>Unidade I</p><p>Frameworks existem para uma variedade de linguagens de programação e tipos de aplicação. Por</p><p>exemplo, no desenvolvimento web, frameworks como Ruby on Rails, Django (Python) ou Angular</p><p>(JavaScript) oferecem estruturas predefinidas para criar aplicações web. Em desenvolvimento móvel,</p><p>frameworks como React Native ou Flutter permitem desenvolver aplicações para iOS e Android usando</p><p>uma base de código comum.</p><p>Para o desenvolvimento de software geral, .NET Framework e Java Framework são exemplos</p><p>de sistemas abrangentes que fornecem uma ampla gama de funcionalidades. A escolha de um framework</p><p>apropriado depende de vários fatores, incluindo requisitos específicos do projeto, linguagem de</p><p>programação preferida, familiaridade da equipe de desenvolvimento com o framework, e comunidade e</p><p>suporte disponíveis para ele. Utilizá-lo é uma maneira de garantir que as aplicações sejam desenvolvidas</p><p>de forma mais rápida, segura e com um padrão de qualidade mais elevado.</p><p>A jornada do .NET Framework começou como resposta da Microsoft à crescente necessidade de uma</p><p>plataforma unificada, que simplificasse a criação de aplicações para internet e desktop. Inicialmente,</p><p>o .NET Framework foi projetado para oferecer um ambiente gerenciado, conhecido como Common</p><p>Language Runtime (CLR), que executa o código de forma segura e eficiente. Ele permitiu a execução</p><p>de um código intermediário chamado Microsoft Intermediate Language (MSIL), que é independente</p><p>da plataforma e da linguagem, e possibilitou o desenvolvimento em várias linguagens, como C#,</p><p>VB.NET e F#.</p><p>Outro componente essencial do .NET Framework é a extensa biblioteca de classes, conhecida como</p><p>Framework Class Library (FCL). Ela fornece uma vasta gama de funcionalidades prontas para uso,</p><p>abrangendo desde operações básicas de entrada e saída até interfaces gráficas complexas de usuário</p><p>e acesso a bancos de dados. Também ajudou a acelerar significativamente o processo, permitindo</p><p>que programadores se concentrassem na lógica específica da aplicação sem se preocupar com a</p><p>implementação de baixo nível de muitas funcionalidades comuns.</p><p>Ao longo dos anos, o .NET Framework foi se expandindo e se adaptando às mudanças nas demandas</p><p>e tecnologias do mercado. Uma das evoluções mais notáveis foi a introdução do ASP.NET, plataforma</p><p>para desenvolver aplicações web que permitiu criar</p><p>sites dinâmicos e aplicações robustas, trazendo</p><p>consigo tecnologias como Web Forms, MVC e Web API, cada uma atendendo a diferentes estilos</p><p>e necessidades.</p><p>A arquitetura do .NET Framework também se destacou pela sua interoperabilidade, com diferentes</p><p>linguagens de programação e sistemas. A Microsoft promoveu uma estratégia de inclusão e permitiu que</p><p>o .NET interagisse com outras plataformas e linguagens – passo significativo para adotar a plataforma</p><p>numa variedade mais ampla de ambientes de desenvolvimento.</p><p>Apesar do sucesso, o .NET Framework começou a enfrentar limitações, especialmente no que diz</p><p>respeito à compatibilidade com sistemas que não eram Windows, levando ao desenvolvimento do .NET</p><p>Core, uma versão mais moderna e multiplataforma do .NET, que oferece maior flexibilidade, desempenho</p><p>aprimorado e suporte a containeres; uma evolução do .NET num mundo cada vez mais diversificado do</p><p>desenvolvimento de software.</p><p>97</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>A introdução do .NET Core representou um momento significativo na evolução da plataforma</p><p>.NET, marcando uma mudança na forma como a Microsoft abordava o desenvolvimento de software.</p><p>O .NET Core é uma versão reformulada do .NET Framework, projetada para atender às demandas</p><p>emergentes de um ambiente de desenvolvimento moderno. Com o .NET Core, a Microsoft buscou criar</p><p>uma plataforma mais modular, eficiente e, acima de tudo, multiplataforma, capaz de rodar em Windows,</p><p>Linux e macOS.</p><p>Uma das principais diferenças entre o .NET Core e o .NET Framework é a capacidade multiplataforma.</p><p>Enquanto o .NET Framework foi construído primariamente para aplicações Windows, o .NET Core</p><p>foi projetado desde o início para ser executado em várias plataformas –característica que abriu</p><p>novas possibilidades para desenvolvedores que queriam ou precisavam executar suas aplicações em</p><p>ambientes diferentes de Windows, uma necessidade cada vez mais comum no cenário tecnológico</p><p>diversificado de hoje.</p><p>Além disso, o .NET Core apresenta uma arquitetura modular, que permite aos desenvolvedores incluir</p><p>apenas as partes do .NET de que eles precisam em suas aplicações. Isso se traduz em aplicações mais</p><p>leves e com tempos de carregamento mais rápidos – vantagem significativa especialmente para</p><p>aplicações web e serviços em nuvem. Essa modularidade é possível graças à introdução do NuGet como</p><p>meio de entregar bibliotecas e componentes, permitindo que desenvolvedores personalizem projetos</p><p>com maior precisão e flexibilidade.</p><p>Desempenho também é um ponto de diferenciação. O .NET Core foi otimizado para oferecer maior</p><p>desempenho em várias métricas, incluindo velocidade de inicialização da aplicação e uso de recursos.</p><p>Essa melhoria no desempenho é consequência tanto da arquitetura modular quanto de várias outras</p><p>otimizações no núcleo da plataforma. O .NET Core também elevou a plataforma .NET a um novo patamar</p><p>de versatilidade.</p><p>O suporte ao Linux e macOS, além do Windows, abriu caminho para desenvolver e hospedar</p><p>aplicações .NET numa variedade mais ampla de ambientes. Isso é particularmente relevante no contexto</p><p>de containeres e microsserviços, em que o .NET Core se encaixa perfeitamente com tecnologias como</p><p>Docker e Kubernetes, o que facilita a criação de aplicações escaláveis e eficientes na nuvem.</p><p>CLR é um componente fundamental do ecossistema .NET da Microsoft e desempenha papel crítico na</p><p>execução de aplicativos desenvolvidos nele. Em resumo, é a máquina virtual que gerencia a execução de</p><p>programas .NET e proporciona uma série de serviços importantes, incluindo gerenciamento de memória,</p><p>segurança e manipulação de exceções. Uma das principais características do CLR é a execução de código</p><p>gerenciado, que oferece vários benefícios em termos de produtividade do desenvolvedor, segurança e</p><p>estabilidade da aplicação.</p><p>O conceito de código gerenciado é central para o CLR; trata-se do código executado sob a supervisão</p><p>do CLR, em oposição ao código não gerenciado, executado diretamente pelo sistema operacional. Quando</p><p>um programa .NET é compilado, ele é convertido em MSIL. Ao executar um aplicativo .NET, o CLR converte</p><p>o MSIL em código de máquina nativo usando um compilador just in time (JIT) – processo que não</p><p>98</p><p>Unidade I</p><p>apenas garante ao código ser otimizado para a máquina específica em que está sendo executado, mas</p><p>também permite que o CLR imponha políticas de segurança e lide com exceções de maneira mais eficaz.</p><p>Um dos aspectos mais notáveis do CLR é o gerenciamento de memória, particularmente a</p><p>coleta de lixo (ou garbage collection). Garbage collection automatizada é uma característica-chave</p><p>do código gerenciado, pois libera os desenvolvedores da tarefa de gerenciar manualmente a memória.</p><p>No modelo de garbage collection do CLR, o alocador de memória mantém o controle das referências de</p><p>objeto e periodicamente percorre a memória para identificar objetos não mais utilizados pelo programa.</p><p>Esses objetos são então coletados, e a memória que eles ocupavam é liberada, num processo que reduz</p><p>significativamente a probabilidade de vazamentos de memória e erros a ela relacionados, comuns em</p><p>ambientes onde ela deve ser gerenciada manualmente.</p><p>Além disso, o CLR oferece outros serviços de gerenciamento de memória, como a compactação de</p><p>heap – que ajuda a evitar a fragmentação da memória – e a geração de coletas de lixo – que categoriza</p><p>objetos com base na sua longevidade na memória. Objetos usados por pouco tempo são coletados com</p><p>mais frequência que os mais longevos, otimizando assim o desempenho do garbage collector.</p><p>Trabalhar com bibliotecas de classe é uma prática essencial no desenvolvimento de software,</p><p>especialmente em ambientes .NET, onde modularidade e reutilização são fortemente encorajadas.</p><p>Uma biblioteca de classes em .NET é uma coleção de classes, interfaces e outros tipos que fornecem uma</p><p>variedade de funcionalidades, desde operações básicas de entrada/saída até complexas interações com</p><p>sistemas de banco de dados.</p><p>O uso eficaz dessas bibliotecas, com a organização adequada através de namespaces, é fundamental</p><p>para criar aplicações robustas e manuteníveis. Namespaces-padrão em .NET organizam e agrupam</p><p>classes relacionadas, proporcionando uma maneira de controlar o escopo dos nomes em grandes projetos</p><p>de software. Por exemplo, o namespace System é um dos principais namespaces do .NET Framework,</p><p>contendo classes essenciais para o funcionamento de qualquer aplicação .NET.</p><p>Dentro do System há sub-namespaces como o System.Collections – que fornece classes para</p><p>gerenciar coleções de objetos – e o System.IO – que contém classes para leitura e escrita de arquivos</p><p>e fluxos de dados. Esses namespaces ajudam os desenvolvedores a encontrar rapidamente classes</p><p>necessárias e entendê-las dentro do contexto maior da biblioteca. Além de utilizar namespaces-padrão e</p><p>bibliotecas de classes fornecidas pelo .NET Framework ou .NET Core, os desenvolvedores frequentemente</p><p>criam suas próprias bibliotecas de classes reutilizáveis. Essas bibliotecas personalizadas permitem aos</p><p>desenvolvedores encapsular funcionalidades específicas que podem ser reutilizadas em diferentes</p><p>projetos, aumentando a eficiência e a consistência em todo o desenvolvimento de software.</p><p>Para criar uma biblioteca de classes reutilizável, os desenvolvedores devem se concentrar na criação</p><p>de código que seja genérico o suficiente para ser aplicado em diferentes contextos, mas também</p><p>suficientemente robusto e bem documentado para garantir sua funcionalidade e facilidade de uso. Um</p><p>aspecto importante na criação de bibliotecas de classes reutilizáveis é o cuidado com a definição clara</p><p>das interfaces e com a abstração apropriada. Isso significa que classes e métodos devem ter interfaces</p><p>bem definidas que ocultem detalhes internos e complexidades, o que torna a biblioteca mais fácil de</p><p>99</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>usar e facilita a manutenção e atualizações futuras, já que mudanças internas podem ocorrer sem afetar</p><p>os usuários da biblioteca.</p><p>Também</p><p>é crucial considerar as dependências. Bibliotecas eficazes devem minimizar suas</p><p>dependências externas para reduzir o risco de conflitos e problemas de compatibilidade em projetos</p><p>que as utilizem. Quando as dependências são necessárias, é importante gerenciá-las cuidadosamente</p><p>e documentá-las claramente para os usuários. Trabalhar com bibliotecas de classes no .NET, seja com</p><p>bibliotecas_padrão, seja criando outras personalizadas, é parte integral do desenvolvimento de software</p><p>eficiente e sustentável. O uso de namespaces para organizar e gerenciar classes, com a criação de</p><p>bibliotecas reutilizáveis, ajuda a construir um código mais limpo, mais modular e mais fácil de manter,</p><p>contribuindo para a qualidade geral e a longevidade das aplicações de software.</p><p>Desenvolvimento multiplataforma é uma abordagem crucial na era atual da tecnologia, com</p><p>aplicações e serviços que precisam ser eficientes numa variedade de dispositivos e sistemas operacionais.</p><p>Essa necessidade decorre da diversidade de dispositivos utilizados, incluindo smartphones, tablets,</p><p>desktops e até mesmo wearables, cada um operando em diferentes plataformas, como Windows, macOS,</p><p>iOS, Android e Linux.</p><p>Para atender a essa demanda, desenvolvedores adotam estratégias e ferramentas específicas que</p><p>permitem criar aplicações executáveis em múltiplas plataformas com pouco ou nenhum ajuste no código.</p><p>Uma estratégia comum para o desenvolvimento multiplataforma são os frameworks, que suportam a escrita</p><p>de código uma vez e sua execução em várias plataformas. Eles oferecem bibliotecas e APIs que abstraem</p><p>as diferenças entre plataformas subjacentes, permitindo que desenvolvedores escrevam código que é,</p><p>em grande parte, independente da plataforma. Isso economiza tempo e recursos e garante consistência na</p><p>funcionalidade e na experiência do usuário em diferentes dispositivos.</p><p>Dentre as ferramentas e frameworks que suportam o desenvolvimento multiplataforma, o Xamarin</p><p>é um exemplo notável. Integrado ao ecossistema .NET da Microsoft, ele permite que os desenvolvedores</p><p>usem C# para criar aplicações para iOS, Android e Windows, oferecendo acesso completo às APIs</p><p>nativas de cada plataforma e permitindo que aplicações pareçam e funcionem como nativas em cada</p><p>dispositivo. O Xamarin.Forms (extensão do Xamarin) vai além e permite que desenvolvedores criem</p><p>interfaces de usuário compartilhadas em todas as plataformas, reduzindo ainda mais a necessidade de</p><p>código específico de plataforma.</p><p>Outra abordagem popular é o desenvolvimento de aplicações web progressivas (progressive web</p><p>apps – PWAs), que usam tecnologias web modernas para oferecer uma experiência próxima à de</p><p>um aplicativo nativo em navegadores de desktop e móveis. PWAs são responsivas, funcionam offline, são</p><p>acessíveis através de um URL e podem ser adicionadas à tela inicial de dispositivos móveis, oferecendo</p><p>uma experiência de usuário aprimorada em comparação com sites tradicionais. Além disso, plataformas</p><p>como React Native, desenvolvida pelo Facebook, permitem que desenvolvedores usem JavaScript e React</p><p>para construir aplicações nativas para iOS e Android. O React Native se concentra em oferecer uma</p><p>experiência de usuário nativa, permitindo aos desenvolvedores acessar funcionalidades específicas do</p><p>dispositivo, como câmera e localização, enquanto compartilham a lógica de negócios entre plataformas.</p><p>100</p><p>Unidade I</p><p>Para aplicações que exigem alto desempenho – como jogos e softwares gráficos intensivos –,</p><p>tecnologias como Unity são frequentemente utilizadas. Unity é um motor de jogo poderoso que suporta</p><p>desenvolvimento multiplataforma e permite que desenvolvedores criem jogos e experiências interativas</p><p>executáveis em quase qualquer dispositivo. O desenvolvimento multiplataforma, portanto, é facilitado</p><p>por uma variedade de estratégias e ferramentas, cada uma adequada para diferentes tipos de projetos</p><p>e requisitos.</p><p>Seja através de frameworks como Xamarin para aplicações que se assemelham a nativas, PWAs</p><p>para aplicações web acessíveis, React Native para uma abordagem baseada em JavaScript, ou</p><p>Unity para projetos de jogos, os desenvolvedores têm à disposição um leque de opções para criar</p><p>soluções eficazes que atendam a usuários em diversas plataformas. Esse ambiente diversificado de</p><p>desenvolvimento multiplataforma é um testemunho da contínua inovação e adaptação no campo</p><p>do desenvolvimento de software, respondendo às demandas de um mundo digital cada vez mais</p><p>conectado e diversificado.</p><p>2 DESENVOLVIMENTO DA PRIMEIRA INTERFACE GRÁFICA</p><p>O desenvolvimento de interfaces gráficas é uma área fundamental na criação de software e foca</p><p>a construção de elementos visuais com os quais os usuários finais podem interagir diretamente. No</p><p>contexto do Windows, uma das plataformas de sistemas operacionais mais utilizadas, desenvolver</p><p>interfaces gráficas envolve compreender e aplicar tecnologias e ferramentas específicas para criar</p><p>aplicativos não apenas funcionalmente ricos, mas também intuitivos e agradáveis.</p><p>No cerne do desenvolvimento de interfaces gráficas está a ideia de que os usuários devem ser</p><p>capazes de interagir com o software de maneira natural e fácil. Isso geralmente envolve a criação de</p><p>janelas, menus, caixas de diálogo, botões, caixas de texto e outros elementos gráficos familiares aos</p><p>usuários, que lhes permitam ações como inserir dados, selecionar opções, navegar por diferentes seções</p><p>do software e receber feedback visual do sistema. A Microsoft fornece várias tecnologias e frameworks</p><p>para apoiar o desenvolvimento de interfaces gráficas no Windows. Por exemplo, Windows Forms,</p><p>parte do .NET Framework, é uma das ferramentas mais antigas e testadas para criar interfaces gráficas</p><p>do usuário (GUIs) tradicionais, sendo conhecido por sua simplicidade e eficiência, e por permitir aos</p><p>desenvolvedores arrastar e soltar elementos de interface gráfica em um ambiente de design visual.</p><p>Um aspecto crucial no desenvolvimento de interfaces gráficas é a experiência do usuário (user</p><p>experience – UX). Isso implica não apenas a aparência estética da interface, mas também a facilidade</p><p>de uso, acessibilidade e intuitividade. Desenvolvedores devem considerar cuidadosamente como os</p><p>usuários interagem com a aplicação, antecipar necessidades e comportamentos e projetar interfaces que</p><p>facilitem essas interações. Usabilidade torna-se prioridade para garantir que o software seja acessível</p><p>a uma ampla gama de usuários, incluindo aqueles com deficiências. Complementarmente, a interação</p><p>humano-computador (IHC) envolve aspectos como estudo, planejamento e design de interação entre</p><p>usuários e computadores.</p><p>Neste tópico abordaremos o desenvolvimento de interfaces com Windows Forms e aspectos</p><p>de IHC e UX.</p><p>101</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>2.1 Windows Forms</p><p>Windows Forms, parte integral da plataforma .NET fornecida pela Microsoft, é uma tecnologia usada</p><p>para criar GUIs baseadas em Windows. Desenvolvida especificamente para o ambiente do Windows,</p><p>Windows Forms oferece uma ampla gama de controles, como botões, caixas de texto e grades de</p><p>dados – essenciais para construir interfaces de usuário interativas e amigáveis.</p><p>Esses controles são objetos que podem ser arrastados e soltos em um formulário (basicamente</p><p>uma janela ou diálogo), fornecendo uma maneira intuitiva de projetar GUIs. O desenvolvimento com</p><p>Windows Forms em C# é facilitado pelo uso do Visual Studio. Esse IDE fornece ferramentas e recursos</p><p>como um designer de formulários, que permite aos desenvolvedores arrastar e soltar controles em um</p><p>formulário, configurar suas propriedades e escrever código que define o comportamento do aplicativo.</p><p>A abordagem visual do Visual Studio torna o design de uma GUI mais simples e mais eficiente,</p><p>mesmo para aqueles com conhecimento limitado de programação. No coração do Windows Forms está</p><p>o mecanismo de manipulação de eventos. Cada controle em um formulário Windows Forms é capaz</p><p>de gerar eventos; por exemplo, um botão gerará um evento de clique quando um usuário clicar</p><p>da unidade II serem efetivamente empregados.</p><p>Desenvolvimento em camadas é uma abordagem que separa responsabilidades dentro de uma</p><p>aplicação, tornando o código mais modular, fácil de gerenciar e escalável. O MVC, por outro lado, é uma</p><p>abordagem de design arquitetônico que separa a aplicação em três componentes principais – modelo,</p><p>visualização e controlador –, facilitando a manutenção do código, a escalabilidade da aplicação e</p><p>promovendo seu reúso.</p><p>No tópico 5 detalharemos o desenvolvimento em camadas e do MVC, além de trabalhar com o WPF,</p><p>que é similar ao Windows Forms mas oferece recursos avançados, como gráficos 2D/3D e animações, com</p><p>marcação XAML para design mais declarativo. O WinForms é indicado para aplicativos simples, enquanto</p><p>o WPF é preferível para interfaces altamente personalizadas e escaláveis. Pelo mesmo motivo, falaremos</p><p>do padrão model-view-viewmodel (MVVM) e finalizaremos com um projeto de aplicação prática.</p><p>A realidade cada vez mais interconectada faz a computação em nuvem apresentar novos paradigmas.</p><p>A habilidade de conectar-se com bancos de dados num ambiente de nuvem permite que os sistemas</p><p>sejam escaláveis, resilientes e disponíveis globalmente. Desenvolvedores devem compreender esses</p><p>conceitos e a complexidade subjacente das operações de nuvem, garantindo que desenvolvam sistemas</p><p>que aproveitem ao máximo suas capacidades.</p><p>No tópico 6 detalharemos o uso de strings de conexão, especialmente com SQLClient; também</p><p>abordaremos o .NET Framework com seus diversos provedores de dados, como OLE DB e ODBC – variedade</p><p>que oferece uma perspectiva ampla das ferramentas à disposição e nos prepara para selecionar a</p><p>tecnologia mais apropriada para diferentes cenários de desenvolvimento.</p><p>Parte do conteúdo é dedicada à arquitetura do ADO.NET, parte central da plataforma .NET.</p><p>Elementos como DataSet, DataTable, DataView, DataRow e DataColumn são explorados em detalhe.</p><p>Compreendendo-os em profundidade, futuros desenvolvedores serão capacitados para realizar operações</p><p>complexas de bancos de dados com confiança e eficiência. Nos dias atuais é praticamente impossível</p><p>pensar uma aplicação sem conexão com algum banco de dados, o que demonstra a importância de</p><p>dominar a técnica de conectar artefatos de software com sistemas de gerenciamento de bancos</p><p>de dados (SGBDs). Assim, faremos um projeto prático de conexão de um aplicativo desktop com um</p><p>servidor SQL Server da Microsoft.</p><p>10</p><p>Indo além dos sistemas convencionais, o desenvolvimento cross-platform e a mobilidade – particularmente</p><p>com ferramentas como Xamarin ou Microsoft’s Multi-platform App UI (MAUI) – revolucionaram a</p><p>construção de softwares (a unidade IV trata disso). Essas ferramentas permitem aos desenvolvedores</p><p>escrever um código-base que pode ser executado em várias plataformas, como Android, iOS e Windows,</p><p>otimizando tempo e recursos.</p><p>O tópico 7 aborda recursos assíncronos, como async e await, vitais para construir aplicações</p><p>responsivas, permitindo que operações demoradas – como leitura de arquivos ou consultas a bancos de</p><p>dados – sejam realizadas em segundo plano, mantendo a interface do usuário ágil e responsiva. Além disso,</p><p>a emergência do domain-driven design (DDD) trouxe uma perspectiva renovada ao desenvolvimento de</p><p>software, enfatizando a modelagem baseada no domínio do problema e buscando estabelecer uma</p><p>linguagem ubíqua e bem definida entre desenvolvedores e stakeholders. O tópico também explica o</p><p>desenvolvimento de aplicações seguras.</p><p>Na vanguarda do desenvolvimento moderno, microsserviços em C# são uma abordagem</p><p>arquitetônica que decompõe aplicações em pequenos serviços que funcionam de forma independente,</p><p>o que resulta em sistemas mais modulares e escaláveis. Complementarmente, arquiteturas baseadas</p><p>em eventos com C# oferecem uma maneira de criar sistemas reativos que respondem em tempo</p><p>real a estímulos, tornando as aplicações mais dinâmicas e resilientes (abordaremos esses assuntos,</p><p>bem como o desenvolvimento cross-plataform e mobilidade, no tópico 8). Apresentaremos também</p><p>o Xamarin, plataforma baseada em C# que permite criar aplicativos nativos para Android, iOS e</p><p>Windows com uma única base de código. Se usarmos C# junto com Xamarin, aproveitamos o poder</p><p>da linguagem e as facilidades da plataforma.</p><p>De modo resumido, a unidade I trata das bases para a programação e do ambiente à disposição</p><p>do programador para seu trabalho. Já a unidade II aborda o desenvolvimento de interfaces gráficas</p><p>para desktop usando Windows Forms, e há um projeto prático para que o conhecimento técnico de</p><p>programação seja aplicado à luz das melhores práticas de IHC e UX.</p><p>A unidade III aprofunda conceitos fundamentais de arquitetura no desenvolvimento de aplicações</p><p>de maior porte, por isso o projeto da unidade anterior será reformulado e expandido. Também analisa</p><p>a integração da aplicação com o banco de dados, tarefa imprescindível em qualquer sistema (dada a</p><p>relevância do tema, desenvolveremos um projeto prático de aplicação conectado a um servidor de</p><p>banco de dados). Finalmente, a unidade IV trata de aspectos do desenvolvimento de aplicações seguras</p><p>(incluindo autenticação e autorização) e de cross-plataform e mobile.</p><p>11</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>Unidade I</p><p>1 REVISÃO DE CONCEITOS DE POO E INTRODUÇÃO AO AMBIENTE C#</p><p>Ao adentrar no mundo da POO em C#, especialmente no desenvolvimento de aplicativos para desktop</p><p>e mobile que se conectam a bancos de dados integrados, nos deparamos com uma série de conceitos</p><p>fundamentais que se entrelaçam e tornam essa abordagem muito poderosa; portanto dominar os</p><p>conceitos de classes e objetos é imprescindível.</p><p>Classe é como um blueprint que define um tipo de objeto, determinando quais operações podem ser</p><p>realizadas com ele. Quando criamos uma instância dessa classe, temos o que chamamos de objeto. Essa</p><p>distinção é vital pois, enquanto a classe é como um molde, o objeto é a concretização desse molde; daí</p><p>surge a ideia de encapsulamento, que se refere a como escondemos detalhes internos de uma classe</p><p>e expomos apenas o que é estritamente necessário para o mundo exterior. Isso não apenas protege a</p><p>integridade dos dados, mas também torna nosso código mais modular e reutilizável.</p><p>Não podemos deixar de mencionar a herança, conceito que permite construir uma nova classe</p><p>com base em uma já existente. Isso significa que podemos herdar propriedades e comportamentos,</p><p>permitindo reutilizar o código. Ao lado da herança temos o polimorfismo, uma maneira de tratar</p><p>diferentes objetos como se fossem do mesmo tipo; seria como ter vários formatos de peça, mas todas</p><p>se encaixam na mesma moldura.</p><p>Abstração é outro pilar fundamental da POO. Criando classes abstratas e interfaces, podemos</p><p>definir uma espécie de contrato, o que nos dá uma estrutura para trabalhar, sem necessariamente entrar</p><p>em detalhes de implementação específicos de cada classe. Conforme exploramos as relações entre</p><p>classes, encontramos conceitos como associação, agregação e composição. Imagine duas classes,</p><p>uma representando uma conexão com o banco de dados e outra representando uma consulta. O</p><p>relacionamento entre essas duas classes pode ser definido usando esses conceitos, dependendo da</p><p>natureza da relação.</p><p>Além das relações entre classes, dentro de uma mesma classe temos métodos – ou seja, ações que</p><p>um objeto pode executar – e propriedades – que permitem acessar e definir valores. Atributos são</p><p>outra característica interessante, pois fornecem uma maneira de adicionar metadados (ou informações</p><p>adicionais) ao código. Isso pode influenciar como o código é executado ou fornecer informações valiosas</p><p>em tempo de execução.</p><p>Além disso, propriedades automáticas, inicializadores de objetos e inicializadores de coleção</p><p>simplificam a criação e inicialização de objetos e coleções. Por exemplo, em vez de escrever várias linhas</p><p>de código para configurar um novo objeto, podemos fazer tudo isso numa única linha. No que diz</p><p>respeito à manipulação de dados, coleções genéricas e classes</p><p>nele.</p><p>Os desenvolvedores escrevem código em C# para responder a esses eventos, definindo a lógica do</p><p>aplicativo, o que permite criar aplicativos interativos nos quais as ações do usuário no frontend</p><p>desencadeiam processos específicos de backend.</p><p>Outra característica notável do Windows Forms é sua flexibilidade e extensibilidade. Desenvolvedores</p><p>podem não apenas usar os controles existentes, mas também criar seus próprios controles personalizados.</p><p>Isso é útil quando os requisitos de um aplicativo são únicos e os controles-padrão podem não ser</p><p>suficientes. Além disso, o Windows Forms suporta o uso de recursos gráficos avançados, como gráficos</p><p>e animações, que podem ser integrados para melhorar a experiência do usuário.</p><p>Apesar de suas muitas vantagens, é importante notar que o Windows Forms é adequado</p><p>principalmente para aplicativos que serão executados em ambientes Windows. Com o surgimento</p><p>de tecnologias como Windows Presentation Foundation (WPF) e Universal Windows Platform</p><p>(UWP), que oferecem mais recursos e uma melhor integração com os sistemas Windows modernos,</p><p>o Windows Forms pode parecer menos atraente para projetos que exigem interfaces mais ricas</p><p>e dinâmicas. No entanto, para projetos que visam um desenvolvimento rápido e uma curva de</p><p>aprendizado menos íngreme (especialmente para quem está familiarizado com o paradigma</p><p>tradicional do Windows), o Windows Forms continua a ser uma escolha viável e robusta.</p><p>Com ele os desenvolvedores podem criar aplicativos eficientes para Windows com interfaces ricas</p><p>e personalizáveis enquanto aproveitam o suporte e os recursos fornecidos pela plataforma .NET e pelo</p><p>ambiente de desenvolvimento integrado Visual Studio. A capacidade de lidar facilmente com eventos, a</p><p>integração com outras tecnologias do .NET Framework e a possibilidade de criar controles personalizados</p><p>tornam o Windows Forms uma ferramenta poderosa para desenvolver aplicativos Windows tradicionais.</p><p>Criar um formulário no Windows Forms do C# usando o Visual Studio Community é um processo</p><p>que envolve várias etapas inter-relacionadas, começando com a configuração do projeto e avançando</p><p>102</p><p>Unidade I</p><p>para o design e a codificação da interface do usuário. Primeiramente, o desenvolvedor deve iniciar</p><p>o Visual Studio Community e criar um novo projeto. Ao fazer isso, ele seleciona a opção “Windows</p><p>Forms App” na lista de modelos de projeto, o que lhe dá uma base para começar a desenvolver uma</p><p>aplicação GUI em C#.</p><p>Ao se deparar com as opções “Windows Forms App” e “Windows Forms App (.NET Framework)” no</p><p>Visual Studio, a escolha entre elas depende das necessidades específicas do seu projeto e do ambiente</p><p>de destino para o aplicativo. A primeira opção geralmente se refere a um projeto Windows Forms que</p><p>utiliza o .NET Core ou o .NET 5/6 e é recomendada se estivermos iniciando um novo projeto e desejamos</p><p>aproveitar os benefícios das atualizações mais recentes, melhor suporte a longo prazo e capacidade de</p><p>executar o aplicativo em diferentes sistemas operacionais (embora a GUI do Windows Forms seja</p><p>específica para Windows, o núcleo do aplicativo pode ser compartilhado entre plataformas).</p><p>Já a segunda opção é a que usaremos nos exemplos deste tópico e é indicada para projetos que</p><p>utilizam o .NET Framework tradicional, uma plataforma que tem sido a base para muitos aplicativos</p><p>Windows ao longo dos anos. Podemos escolhê-la se estivermos trabalhando em um projeto que precisa</p><p>ser compatível com versões mais antigas do Windows, ou se estivermos mantendo ou atualizando</p><p>um aplicativo existente já construído com o .NET Framework. Embora o .NET Framework ainda seja</p><p>suportado, ele não recebe mais novos recursos, e a Microsoft está direcionando os desenvolvedores ao</p><p>.NET Core e suas versões subsequentes para novos desenvolvimentos.</p><p>Criado o projeto, o Visual Studio abre um formulário em branco, conhecido como Form1 por padrão.</p><p>É a tela principal do aplicativo, e é aqui que os elementos da interface do usuário – como botões, caixas</p><p>de texto e etiquetas – serão adicionados. O desenvolvedor pode alterar o tamanho e as propriedades do</p><p>formulário ajustando-as no devido painel, que oferece opções para modificar aspectos como título</p><p>da janela, dimensões e comportamento de minimização/máxima.</p><p>O próximo passo é arrastar e soltar os controles desejados do Toolbox para o formulário,</p><p>que contém uma variedade de controles como botões, caixas de texto, rótulos (labels), caixas de</p><p>combinação (comboboxes) e muitos outros. Cada controle pode ser colocado e redimensionado</p><p>no formulário conforme necessário. Colocado um controle, o desenvolvedor pode ajustar suas</p><p>propriedades usando novamente o painel de propriedades.</p><p>Por exemplo, ele pode alterar o texto de um botão, ajustar a fonte de um rótulo ou configurar a</p><p>lista de itens em uma caixa de combinação. Com os controles no lugar, o passo seguinte é escrever o</p><p>código que define o comportamento do aplicativo. O desenvolvedor pode adicionar funcionalidades</p><p>aos controles programando manipuladores de eventos; por exemplo, ao dar um duplo clique em um</p><p>botão no designer, o Visual Studio automaticamente cria um manipulador de eventos de clique para</p><p>esse botão. No editor de código que se abre, o desenvolvedor pode então escrever o código C# que será</p><p>executado quando o botão for clicado.</p><p>O processo de adicionar controles ao formulário, ajustar suas propriedades e programar seus eventos</p><p>se repete até que o desenvolvedor tenha construído a interface do usuário conforme desejado. O Visual</p><p>Studio Community oferece também ferramentas para depurar e testar o aplicativo, e o desenvolvedor</p><p>103</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>pode executá-lo dentro do ambiente do Visual Studio para ver como a interface do usuário se comporta</p><p>e interagir com ela em tempo real. Isso permite que ele identifique e corrija quaisquer problemas ou</p><p>bugs antes de finalizá-lo.</p><p>Ao longo do processo, o desenvolvedor tem flexibilidade para retornar ao designer a qualquer</p><p>momento para fazer ajustes na interface do usuário, adicionar novos controles ou modificar os existentes.</p><p>Esse ciclo iterativo de design, codificação e teste continua até que o aplicativo atenda às necessidades</p><p>e expectativas estabelecidas. Ao final, o desenvolvedor tem um aplicativo Windows Forms totalmente</p><p>funcional, construído em C# com o Visual Studio Community, pronto para ser distribuído ou utilizado.</p><p>2.2 Formulários e caixas de mensagem</p><p>No desenvolvimento de interfaces gráficas utilizando Windows Forms com C#, é essencial criar</p><p>formulários e caixas de mensagem para interagir com o usuário. Este subtópico ensina a criar formulários</p><p>customizados fundamentais para coletar informações do usuário, além de apresentar informações e</p><p>perguntas por caixas de mensagem. Na customização desses formulários, os desenvolvedores são livres</p><p>para definir layout, estilos, cores e outros elementos de design que façam o formulário não apenas</p><p>funcionar bem, mas também parecer atraente e se alinhar com a identidade visual do aplicativo.</p><p>No núcleo da criação de um formulário está a classe Form, parte integral do namespace System.</p><p>Windows.Forms do .NET Framework. Ao instanciar um objeto dessa classe, o desenvolvedor cria uma janela</p><p>de aplicativo padrão, que pode ser modificada e estilizada. A personalização de um formulário envolve</p><p>a definição de várias propriedades, como seu título, ícone, cor de fundo, tamanho e estilo de borda.</p><p>Essas propriedades podem ser definidas programaticamente no código C# ou visualmente usando</p><p>o Designer de Formulários do Visual Studio, que proporciona uma interface de arrastar e soltar para</p><p>construir interfaces de usuário. Além da aparência, os formulários gerenciam o comportamento. Por</p><p>exemplo, eventos são aspectos fundamentais dos formulários: eventos como Load, Click e Resize podem</p><p>ser manipulados para executar ações específicas quando ocorrem interações do usuário ou outras</p><p>atividades. Um evento Load pode inicializar certos controles ou carregar dados quando o formulário</p><p>é</p><p>aberto pela primeira vez.</p><p>Um aspecto importante é a gestão de layout, e o Windows Forms oferece várias opções para gerenciar</p><p>a disposição dos controles num formulário. Layouts podem ser fixos, e o desenvolvedor define posições</p><p>e tamanhos exatos para cada controle, ou podem ser dinâmicos, utilizando gerenciadores de layout</p><p>como FlowLayout ou TableLayoutPanel. Esses gerenciadores permitem que os controles se ajustem</p><p>automaticamente ao redimensionar o formulário, melhorando a responsividade e a flexibilidade da</p><p>interface do usuário.</p><p>Para criar um form:</p><p>1) Inicie o Visual Studio.</p><p>2) Selecione “Criar um novo projeto”.</p><p>104</p><p>Unidade I</p><p>3) Escolha “Aplicativo Windows Forms (.NET Framework)” na lista de modelos de projeto.</p><p>4) Dê um nome ao seu projeto e clique em Criar.</p><p>5) O Form1 será automaticamente criado.</p><p>Ao clicar com o botão direito no formulário e, a seguir, em “Propriedades” no menu suspenso, é</p><p>possível alterar as propriedades do formulário. A título de exemplo, vamos alterar algumas propriedades:</p><p>• em Aparência, mudaremos a propriedade Text para “Os três mosqueteiros”;</p><p>• em Design, mudaremos a propriedade (Name) para TelaPrincipal;</p><p>• em Estilo da Janela, mudaremos a propriedade Icon para false.</p><p>Em Aparência, vamos alterar também a propriedade BackgroundImage. No caso, o clique abrirá</p><p>uma tela com o título “Selecionar recurso”. Basta clicar no botão Importar, escolher um arquivo de</p><p>imagem (extensões jpg, png, bmp etc.) e clicar em Ok. A imagem escolhida virará o pano de fundo</p><p>do formulário.</p><p>Figura 42 – Formulário original (à esquerda) e formulário com as propriedades alteradas (à direita).</p><p>A imagem usada como pano de fundo do formulário foi produzida pelo próprio autor com</p><p>tecnologia DALL-E, uma ferramenta de inteligência artificial desenvolvida pela OpenAI</p><p>Para finalizar, também vamos alterar para Stretch a propriedade BackgroundImageLayout. Para ver</p><p>o formulário em ação, clique no botão Iniciar na barra de ferramentas. A figura 42 mostra o formulário</p><p>original e o formulário após as alterações; note que as mudanças de algumas propriedades já produzem</p><p>um efeito visual importante. O quadro 5 mostra algumas propriedades úteis de um formulário. A lista</p><p>não é exaustiva, mas sim uma pequena amostra para um primeiro contato. Essas propriedades são</p><p>fundamentais para controlar a aparência dos formulários.</p><p>105</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>Quadro 5 – Propriedades úteis de um formulário</p><p>Propriedade Descrição</p><p>BackColor Define a cor de fundo do formulário</p><p>FormBorderStyle Especifica o estilo da borda do formulário, como None, FixedSingle, Fixed3D, FixedDialog etc.</p><p>Text Texto que aparece na barra de título do formulário</p><p>Size Define o tamanho do formulário. Geralmente é um objeto do tipo Size que inclui largura e altura</p><p>StartPosition Define a posição inicial do formulário quando ele é exibido pela primeira vez. Pode ser</p><p>CenterScreen ou outro</p><p>Icon Define o ícone a ser exibido na barra de títulos do formulário e na barra de tarefas do Windows</p><p>WindowState Define o estado inicial do formulário, como Maximized, Minimized ou Normal</p><p>TopMost Valor booleano que define se o formulário deve ser exibido acima de todos os outros formulários</p><p>Opacity Define o nível de opacidade do formulário; um valor entre 0 (totalmente transparente) e 1</p><p>(totalmente opaco)</p><p>ShowInTaskbar Valor booleano que determina se o formulário aparece na barra de tarefas do Windows</p><p>No botão Iniciar – ícone de triângulo verde – o Visual Studio inicia a compilação. O código-fonte</p><p>escrito em C# é compilado em um executável ou uma biblioteca, dependendo da natureza do projeto.</p><p>Essa etapa é crucial, pois verifica a correção sintática e lógica do código, garantindo que não haja erros</p><p>que impeçam a execução do programa. Após a compilação bem-sucedida, o Visual Studio procede para</p><p>executar a aplicação; nesse ponto o ambiente de desenvolvimento inicia a aplicação dentro de um</p><p>contexto que permite a depuração e o teste.</p><p>O formulário principal da aplicação – ou seja, a interface gráfica desenhada e programada</p><p>pelo desenvolvedor – é exibido. Nesse momento o desenvolvedor pode interagir com o formulário,</p><p>exatamente como um usuário final faria. Essa interação é vital para o processo de desenvolvimento, e</p><p>o desenvolvedor pode testar a funcionalidade dos elementos da interface, como botões, caixas de texto,</p><p>menus e outros controles. Erros lógicos, problemas de interface ou falhas na experiência do usuário</p><p>podem ser identificados e corrigidos.</p><p>Além disso, é possível verificar como o aplicativo responde a diferentes ações, como cliques do</p><p>mouse, entradas de teclado e redimensionamento de janelas. Um aspecto importante desse processo</p><p>é a depuração. Se o desenvolvedor inseriu pontos de interrupção no código, a execução do programa</p><p>será pausada nesses pontos, permitindo a inspeção de variáveis, o rastreamento de execução e a</p><p>análise do comportamento do programa; é uma ferramenta poderosa para entender e resolver</p><p>problemas complexos que podem não ser imediatamente aparentes.</p><p>Concluídos os testes e a depuração, o desenvolvedor pode parar de executar a aplicação</p><p>selecionando Parar no Visual Studio. Esse ciclo de iniciar, testar e parar se repete inúmeras vezes</p><p>durante o desenvolvimento de uma aplicação, evidenciando a natureza iterativa e incremental do</p><p>desenvolvimento de software. Se dermos um clique duplo no formulário, ele abrirá o código-fonte</p><p>(arquivo Form1.cs), que pode ser editado. A figura 43 mostra o código-fonte aberto na tela.</p><p>106</p><p>Unidade I</p><p>Figura 43 – Form1.cs aberto para edição na tela</p><p>Note que há uma aba Form1.cs e ao lado a aba Form1.cs [Design], que serve para retornar ao</p><p>modo gráfico (Design). Observe ainda que o código-fonte foi aberto automaticamente com o cursor</p><p>posicionado no método Form1_Load para editar um código executável no evento Load. Eventos são</p><p>fundamentais para gerenciar o ciclo de vida do formulário e para interagir com o usuário, e permitem</p><p>que os desenvolvedores implementem comportamentos específicos em resposta às ações do usuário ou</p><p>a mudanças no estado do formulário.</p><p>O quadro 6 mostra uma lista não exaustiva de eventos. Cada um pode ser manipulado escrevendo</p><p>código no método de evento correspondente, que pode ser gerado automaticamente no Visual Studio</p><p>quando criamos um manipulador de eventos para um caso específico.</p><p>Quadro 6 – Eventos mais comuns associados a um Form em Windows Forms com C#</p><p>Evento Aplicação</p><p>Load Antes da primeira exibição do formulário. É comumente usado para inicializar variáveis ou</p><p>estado</p><p>Shown Depois que o formulário é exibido pela primeira vez</p><p>Closing Quando o formulário está sendo fechado, permitindo interrupção ou alteração do</p><p>fechamento</p><p>Closed Depois que o formulário fecha. É utilizado para limpar ou liberar recursos</p><p>Resize Quando o formulário é redimensionado. Útil para ajustar o layout dos controles</p><p>MouseMove Quando o mouse é movido sobre o formulário</p><p>Click Quando o formulário é clicado</p><p>DoubleClick Quando o formulário é clicado duas vezes rapidamente</p><p>KeyPress Quando uma tecla é pressionada enquanto o formulário tem foco</p><p>GotFocus Quando o formulário recebe o foco</p><p>LostFocus Quando o formulário perde o foco</p><p>107</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>Evento Aplicação</p><p>Paint Quando o formulário precisa ser redesenhado. Útil para desenho personalizado</p><p>KeyDown Quando uma tecla é pressionada enquanto o formulário tem foco</p><p>KeyUp Quando uma tecla é liberada enquanto o formulário tem foco</p><p>Activated Quando o formulário é ativado e se torna o principal</p><p>Deactivate Quando o formulário perde o status de ativo para outro formulário</p><p>Além dos formulários, as caixas de mensagem desempenham papel vital na comunicação com o</p><p>usuário. No universo do C#, existe uma variedade de caixas de mensagem que podem ser utilizadas</p><p>conforme a necessidade. Por exemplo, há caixas de mensagem usadas apenas para fornecer informações</p><p>ao usuário, como avisos ou confirmações de ações realizadas. Essas caixas são geralmente simples e</p><p>possuem</p><p>um botão para fechar a mensagem.</p><p>A figura 44 mostra a execução de uma caixa de mensagem, iniciada pelo evento Load do Form,</p><p>conforme o código-fonte da mesma figura.</p><p>Figura 44 – Caixa de mensagem simples</p><p>Outro tipo importante de caixa de mensagem é a que solicita uma confirmação do usuário.</p><p>É frequentemente utilizada quando é necessário garantir que o usuário deseja prosseguir com uma ação</p><p>específica, como excluir um arquivo ou fechar um aplicativo. Normalmente apresenta opções como Sim,</p><p>Não e às vezes Cancelar, permitindo-lhe tomar uma decisão informada.</p><p>A figura 45 mostra essa caixa, e novamente utilizamos o evento Load do Form. Note também que o</p><p>ícone de interrogação foi colocado pelo parâmetro MessageBoxIcon.Question.</p><p>108</p><p>Unidade I</p><p>Figura 45 – Caixa de mensagem com opções para o usuário</p><p>Caixas de mensagem que apresentam erros são cruciais para informar usuários sobre problemas</p><p>durante a execução do aplicativo. Elas fornecem feedbacks essenciais quando algo não funciona</p><p>conforme o esperado, ajudando-os a entender o que deu errado. Essas caixas geralmente têm um</p><p>ícone de erro e fornecem detalhes suficientes para o usuário ou desenvolvedor identificar e corrigir</p><p>o problema. A figura 46 exemplifica um código que faz uma divisão ilegal (por zero), e portanto uma</p><p>exceção é gerada e colocada na tela pela caixa de mensagem.</p><p>Figura 46 – Exceção capturada e mostrada numa caixa de mensagem</p><p>Um aspecto importante das caixas de mensagem é a capacidade de capturar a resposta do usuário</p><p>pelo DialogResult.</p><p>109</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>1. private void Form1_Load(object sender, EventArgs e)</p><p>2. {</p><p>3. string mensagem = “Você deseja executar esta ação?”;</p><p>4. string titulo = “Confirmação Necessária”;</p><p>5. // Exibe a caixa de mensagem com botões ‘Sim’, ‘Não’ e</p><p>‘Cancelar’</p><p>6. DialogResult result = MessageBox.Show(mensagem, titulo,</p><p>MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);</p><p>7. // Verifica qual botão foi pressionado</p><p>8. if (result == DialogResult.Yes)</p><p>9. {</p><p>10. // Código para o caso de ‘Sim’</p><p>11. MessageBox.Show(“Você escolheu ‘Sim’”);</p><p>12. }</p><p>13. else if (result == DialogResult.No)</p><p>14. {</p><p>15. // Código para o caso de ‘Não’</p><p>16. MessageBox.Show(“Você escolheu ‘Não’”);</p><p>17. }</p><p>18. else</p><p>19. {</p><p>20. // Código para o caso de ‘Cancelar’</p><p>21. MessageBox.Show(“Você escolheu ‘Cancelar’”);</p><p>22. }</p><p>23. }</p><p>Figura 47 – DialogResult</p><p>As linhas 7, 10 e 15 ilustram o DialogResult. A funcionalidade permite que o aplicativo tome decisões</p><p>baseadas nas escolhas do usuário. Por exemplo, se um usuário selecionar Sim numa caixa de mensagem</p><p>de confirmação, o aplicativo pode prosseguir com a ação correspondente. Por outro lado, se o usuário</p><p>escolher Não, pode abortar a ação ou oferecer alternativas.</p><p>2.3 Elementos gráficos básicos: rótulos, caixas de texto e botões</p><p>Um formulário, no contexto do WindowsForms, é essencialmente uma janela ou tela na qual os</p><p>controles são colocados para interagir com o usuário. A maneira como esses controles são integrados e</p><p>gerenciados dentro do formulário determina a funcionalidade, a estética e a usabilidade da aplicação, e</p><p>ele atua como container cujos controles são alocados e organizados. Essa relação de container e conteúdo</p><p>é crucial para definir como a interface do usuário será apresentada e como responderá às interações.</p><p>Nesse contexto, alguns controles básicos – como Label (rótulo), TextBox (caixa de texto) e Button (botão)</p><p>– desempenham papel essencial e proporcionam a base para a interação entre usuário e software.</p><p>110</p><p>Unidade I</p><p>O Label é primordial em qualquer interface gráfica, pois serve como meio de apresentar texto de</p><p>forma não editável. As propriedades de texto do Label permitem que o desenvolvedor especifique o</p><p>conteúdo a ser exibido; além disso, o alinhamento do texto dentro do Label contribui para a estética e</p><p>a legibilidade da interface. Estilos variados podem ser aplicados ao Label, incluindo diferentes fontes,</p><p>cores e tamanhos, o que possibilita uma customização avançada para se adequar ao design da aplicação.</p><p>TextBox, por sua vez, é um controle interativo que permite a entrada de texto pelo usuário. Uma</p><p>de suas características mais notáveis é o modo multilinha, que o torna ideal para inputs de texto mais</p><p>extensos. Outro aspecto importante são os eventos associados, especialmente o TextChanged – evento</p><p>disparado toda vez que o texto dentro do TextBox é alterado, seja por digitação, seja por programação,</p><p>permitindo que o software responda dinamicamente a essas mudanças. Por exemplo, pode-se implementar</p><p>uma lógica de validação de dados em tempo real, melhorando a interação do usuário com a aplicação.</p><p>Finalmente, Button é um elemento crucial em quase todas as interfaces gráficas e age como gatilho</p><p>para executar ações. O evento Click do Button é talvez o mais utilizado, acionado quando o usuário</p><p>clica no botão – funcionalidade central para iniciar processos como enviar formulários, abrir novas</p><p>janelas ou iniciar cálculos. Além disso, a associação de atalhos de teclado com botões é uma prática que</p><p>realça a usabilidade e permite que os usuários realizem ações rapidamente, sem precisar do mouse. Tal</p><p>característica é útil em aplicações que demandam rapidez ou para usuários que preferem ou necessitam</p><p>de interações no teclado.</p><p>Quadro 7 – Exemplos de propriedades e eventos</p><p>dos controles Label, Texbox e Button</p><p>Controle Tipo Nome Descrição</p><p>Label</p><p>Propriedade</p><p>Text Define o texto do Label</p><p>TextAlign Alinha o texto dentro do Label</p><p>Font Estilo da fonte do texto</p><p>ForeColor Cor da fonte do texto</p><p>BackColor Cor de fundo do Label</p><p>Evento</p><p>Click Evento ao clicar</p><p>DoubleClick Evento ao clicar duas vezes</p><p>MouseEnter Evento ao entrar com o mouse</p><p>MouseLeave Evento ao sair com o mouse</p><p>MouseMove Evento ao mover o mouse sobre o Label</p><p>TextBox</p><p>Propriedade</p><p>Text Texto inserido pelo usuário</p><p>Multiline Permite texto em várias linhas</p><p>ReadOnly Impede modificação do texto</p><p>MaxLength Máximo de caracteres permitidos</p><p>ForeColor Cor da fonte do texto</p><p>Evento</p><p>TextChanged Altera quando o texto muda</p><p>KeyDown Evento ao pressionar uma tecla</p><p>KeyUp Evento ao soltar uma tecla</p><p>Enter Evento ao entrar no TextBox</p><p>Leave Evento ao sair do TextBox</p><p>111</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>Controle Tipo Nome Descrição</p><p>Button</p><p>Propriedade</p><p>Text Texto do botão</p><p>BackColor Cor de fundo do botão</p><p>ForeColor Cor da fonte do botão</p><p>Image Imagem no botão</p><p>FlatStyle Estilo visual do botão</p><p>Evento</p><p>Click Evento ao clicar no botão</p><p>DoubleClick Evento ao clicar duas vezes no botão</p><p>MouseEnter Evento ao entrar com o mouse no botão</p><p>MouseLeave Evento ao sair com o mouse do botão</p><p>MouseMove Evento ao mover o mouse sobre o botão</p><p>O quadro mostra exemplos úteis de eventos e propriedades. A compreensão e o uso eficiente</p><p>desses controles – Label, TextBox e Button – são fundamentais para desenvolver interfaces gráficas em</p><p>WindowsForms com C#. Cada controle oferece funcionalidades específicas que, quando bem usadas,</p><p>podem enriquecer a experiência do usuário e a eficácia da aplicação. A hierarquia e o posicionamento</p><p>também são fundamentais na relação dos controles com o formulário.</p><p>Controles são posicionados dentro do formulário e seguem uma ordem hierárquica que determina</p><p>como são renderizados e como interagem entre si. Por exemplo, um TextBox para entrada de dados</p><p>é usualmente acompanhado por um Label para sua identificação e um Button para submeter as</p><p>informações inseridas. Essa disposição não é apenas estética, mas também funcional, e determina a</p><p>sequência de navegação e interação do usuário com a aplicação. Outrossim, a interação entre controles</p><p>e formulário é gerenciada através de eventos.</p><p>Num formulário WindowsForms, eventos como cliques do mouse ou pressionamentos de tecla</p><p>são capturados e tratados. O formulário, portanto, não apenas hospeda controles, mas também serve</p><p>como ponto central para gerenciar eventos e garantir que as ações do usuário sejam interpretadas e</p><p>respondidas</p><p>adequadamente.</p><p>2.4 Elementos gráficos de apoio: painéis, grupos, rótulos com vínculo e dicas</p><p>de ferramenta</p><p>Além de controles básicos como Label, TextBox e Button, outros elementos enriquecem a experiência</p><p>do usuário, como Panel, GroupBox, LinkLabel e ToolTip – cada um com funcionalidades que contribuem</p><p>para uma interface mais organizada, intuitiva e informativa.</p><p>Panel é um controle versátil no contexto de WindowsForms. Sua principal função é servir como</p><p>container para outros controles. Essa característica é particularmente útil quando se deseja agrupar</p><p>elementos de interface de forma lógica. Por exemplo, um formulário com vários campos de entrada</p><p>e etiquetas pode ser organizado de maneira mais clara e coerente dentro de um Panel. Além disso,</p><p>oferece capacidades de rolagem, indispensáveis se o espaço na tela for limitado. Com isso, mesmo</p><p>uma grande quantidade de controles pode ser acomodada em uma área confinada e melhorar a</p><p>usabilidade sem comprometer a estética.</p><p>112</p><p>Unidade I</p><p>Um dos principais atributos do Panel é sua flexibilidade em termos de layout e design. Desenvolvedores</p><p>podem utilizar Panels para segmentar visualmente a interface do usuário, organizando-a em seções</p><p>lógicas e coerentes. Por exemplo, em uma aplicação com múltiplas funções, cada conjunto de controles</p><p>relacionados a uma função específica pode ser agrupado num Panel separado. Isso não apenas melhora</p><p>a estética da aplicação, mas também facilita a navegação e a compreensão por parte do usuário, que</p><p>pode identificar e interagir com diferentes funcionalidades de forma mais intuitiva.</p><p>Outro aspecto relevante do Panel é sua capacidade de aninhamento. Panels podem conter outros</p><p>Panels e permitir a criação de estruturas hierárquicas complexas. Essa capacidade de aninhamento é</p><p>útil para criar interfaces ainda mais organizadas e segmentadas, onde cada seção pode ter seu próprio</p><p>layout e conjunto de controles, gerenciados de forma independente.</p><p>Do ponto de vista técnico, o Panel é facilmente personalizável. Os desenvolvedores controlam várias</p><p>propriedades, como cor de fundo, bordas e tamanho, permitindo que o Panel se integre harmoniosamente</p><p>ao design geral da aplicação. Além disso, a interação do Panel com outros controles é gerenciada de</p><p>maneira fluida e eficiente pelo WindowsForms, garantindo que os eventos de usuário sejam tratados</p><p>corretamente, mesmo quando envolvem controles aninhados dentro de Panels.</p><p>GroupBox é outro controle que facilita a organização de interfaces gráficas. Como o nome sugere,</p><p>seu propósito é agrupar controles relacionados. Diferente do Panel, ele não apenas contém os controles,</p><p>mas também os apresenta visualmente como conjunto distinto, usualmente com uma borda e um</p><p>título opcional. Isso é particularmente benéfico em formulários complexos ou em aplicações com</p><p>múltiplas opções e configurações, em que é crucial a clareza na segmentação de diferentes seções.</p><p>Seu uso contribui para uma interface mais intuitiva, permitindo que os usuários identifiquem</p><p>rapidamente conjuntos de controles relacionados e entendam como interagir com cada parte da</p><p>aplicação. Ele é essencialmente um container, mas com a característica distintiva de apresentar borda</p><p>e título opcionais. Essa estrutura visual é benéfica em interfaces complexas, nas quais diferentes</p><p>conjuntos de controle precisam ser claramente delineados; por exemplo, num formulário com várias</p><p>seções de entrada de dados, cada seção pode ser encapsulada num GroupBox separado, com um título</p><p>descritivo. Isso não só ajuda os usuários a navegar pela interface mais eficientemente, mas também</p><p>proporciona uma experiência de usuário mais intuitiva, facilitando a identificação de grupos de controle</p><p>que trabalham juntos para cumprir uma função específica.</p><p>Embora sua função primária seja agrupar controles visuais, ele também oferece uma variedade</p><p>de propriedades que podem ser ajustadas para se adequar ao design geral da aplicação, incluindo</p><p>personalização da fonte do título, a cor da borda e até mesmo a opção de habilitar ou desabilitar</p><p>o GroupBox, o que pode alterar a disponibilidade dos controles contidos nele. Este último aspecto é útil</p><p>quando determinados grupos de controle só devem ser acessíveis em certas condições.</p><p>Outro aspecto técnico importante do GroupBox é sua capacidade de influenciar o comportamento</p><p>dos controles que contém. Por exemplo, desabilitar um GroupBox automaticamente desabilita todos os</p><p>controles dentro dele – funcionalidade que simplifica a lógica de programação quando um conjunto</p><p>inteiro de controles precisa ser ativado ou desativado em resposta a certas condições da aplicação.</p><p>113</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>Por fim, o GroupBox se integra perfeitamente ao mecanismo de layout do WindowsForms, permitindo</p><p>que os desenvolvedores posicionem e redimensionem os controles internos com facilidade. Isso é</p><p>essencial para criar interfaces responsivas que se adaptem a diferentes tamanhos de tela e resoluções.</p><p>Rótulos com vínculo, conhecidos como Linked Labels, são uma adição significativa à gama de</p><p>recursos disponíveis para desenvolvedores. Esses rótulos – que combinam as características de um</p><p>rótulo tradicional com a funcionalidade de um hiperlink – oferecem uma maneira interativa e intuitiva</p><p>de proporcionar uma experiência de usuário mais rica e conectada. São uma variação do controle Label</p><p>comum, mas com a capacidade adicional de responder a interações do usuário, como cliques do mouse.</p><p>Essa capacidade permite que os desenvolvedores os utilizem não apenas para exibir texto, mas</p><p>também para funcionar como pontos de navegação dentro da aplicação ou para abrir páginas externas</p><p>da web. Por exemplo, um rótulo com vínculo pode direcionar o usuário para uma seção diferente da</p><p>aplicação, abrir um formulário de ajuda ou um site com informações relevantes.</p><p>Visualmente, costumam diferir de rótulos normais pela cor e pelo sublinhado, indicando que são</p><p>interativos e funcionam como o hiperlink de um navegador. Além disso, propriedades do texto – como</p><p>fonte, cor e alinhamento – podem ser ajustadas para se adequar ao design geral da interface do</p><p>usuário. Essa customização visual melhora a estética da aplicação e ajuda na identificação intuitiva</p><p>desses rótulos como elementos interativos pelo usuário, que podem ser usados para fornecer atalhos</p><p>rápidos e intuitivos, melhorando a experiência do usuário para pessoas com diferentes habilidades de</p><p>navegação. Isso é valioso em interfaces complexas, onde a navegação rápida e direta pode melhorar</p><p>significativamente a usabilidade.</p><p>Já os tooltips proporcionam um meio simples mas poderoso de melhorar a experiência, fornecendo</p><p>informações contextuais de forma discreta e acessível. Bem implementados, tooltips podem aumentar</p><p>significativamente a usabilidade, a compreensão e a acessibilidade de uma aplicação, tornando-a</p><p>mais intuitiva e amigável. Esses pequenos textos informativos emergem quando o usuário posiciona</p><p>o cursor do mouse sobre determinado elemento da interface, oferecendo esclarecimentos, dicas ou</p><p>instruções adicionais.</p><p>O uso eficiente de tooltips é sinal de uma interface bem pensada, que busca orientar e informar o</p><p>usuário de maneira intuitiva e discreta. São fáceis de implementar, mas oferecem um grande impacto</p><p>na usabilidade e na acessibilidade da aplicação. Geralmente se associam a um controle específico, como</p><p>um botão, ícone ou campo de texto. A mensagem que aparece é breve mas direta, destinada a fornecer</p><p>informações úteis que podem não ser imediatamente óbvias a partir do layout ou do rótulo do controle.</p><p>Por exemplo, um tooltip pode explicar a função de um botão com um ícone ambíguo ou oferecer</p><p>orientações sobre como preencher um campo de formulário específico.</p><p>114</p><p>Unidade I</p><p>Quadro 8 – Propriedades e eventos dos controles Panel,</p><p>GroupBox, LinkLabel e tooltip</p><p>Controle Tipo Nome Descrição</p><p>Panel</p><p>Propriedade</p><p>AutoScroll Habilita rolagem automática</p><p>BackColor Cor de fundo do painel</p><p>BorderStyle Estilo da borda</p><p>Dock Como o painel é</p><p>acoplado</p><p>Evento</p><p>Click Evento ao clicar</p><p>DoubleClick Evento ao clicar duas vezes</p><p>Enter Evento ao entrar no painel</p><p>Leave Evento ao sair do painel</p><p>GroupBox</p><p>Propriedade</p><p>Text Texto do GroupBox</p><p>Font Estilo da fonte do texto</p><p>Enabled Habilita ou desabilita o GroupBox</p><p>Dock Como o GroupBox é acoplado</p><p>Evento</p><p>Click Evento ao clicar</p><p>DoubleClick Evento ao clicar duas vezes</p><p>Enter Evento ao entrar no GroupBox</p><p>Leave Evento ao sair do GroupBox</p><p>LinkLabel</p><p>Propriedade</p><p>Text Texto do LinkLabel</p><p>LinkColor Cor do link</p><p>VisitedLinkColor Cor do link visitado</p><p>LinkBehavior Comportamento do link</p><p>Evento</p><p>LinkClicked Evento ao clicar no link</p><p>MouseHover Evento ao passar o mouse sobre o link</p><p>MouseMove Evento ao mover o mouse sobre o link</p><p>Tooltip</p><p>Propriedade</p><p>Active Ativa ou desativa o tooltip</p><p>AutomaticDelay Atraso automático</p><p>AutoPopDelay Atraso de desaparecimento automático</p><p>InitialDelay Atraso inicial</p><p>Evento</p><p>Popup Evento quando o tooltip aparece</p><p>Draw Evento de desenho</p><p>Closed Evento quando o tooltip fecha</p><p>Desenvolvedores são livres para modificar vários aspectos dos tooltips, incluindo o tempo de atraso</p><p>antes de aparecerem, o tempo de permanência na tela e até mesmo sua aparência, como fonte e cor.</p><p>Essa capacidade de personalização permite que os tooltips se integrem de forma coesa ao design geral da</p><p>aplicação, mantendo a consistência visual e reforçando a identidade da marca ou do estilo da interface.</p><p>Além de seu valor estético e informativo, são extremamente valiosos do ponto de vista da acessibilidade.</p><p>Eles podem ajudar usuários que podem ter dificuldades em entender o propósito de determinados</p><p>controles ou que precisam de lembretes adicionais sobre a funcionalidade de certos elementos da</p><p>interface. Para usuários com deficiências visuais que dependem de leitores de tela, tooltips podem ser</p><p>configurados para ser lidos por essas tecnologias, proporcionando uma camada adicional de acessibilidade.</p><p>115</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>Outra característica dos tooltips é a gestão do equilíbrio entre informação e sobrecarga. Embora</p><p>seja tentador usar tooltips para fornecer grandes quantidades de informação, são mais bem utilizados</p><p>em dicas curtas e diretas, pois isso mantém a interface limpa e focada, evitando confusão ou distração</p><p>do usuário com informações excessivas ou desnecessárias.</p><p>Para usar um tooltip no Windows Forms do .NET, siga estes passos:</p><p>1) Arraste o tooltip para o formulário. Será criado o tooltip1;</p><p>2) Defina o texto do tooltip para cada controle no qual quisermos exibir uma dica de ferramenta.</p><p>Para isso:</p><p>a) Selecione o controle (por exemplo, button1).</p><p>b) Na janela de propriedades, procure em Diversos uma propriedade chamada ToolTip em tooltip1.</p><p>c) Defina o valor dessa propriedade para o texto desejado.</p><p>3) Configure propriedades adicionais, caso queira, para personalizar o comportamento do tooltip,</p><p>como AutoPopDelay, InitialDelay, ReshowDelay etc.</p><p>4) Habilite o tooltip para ele funcionar (a propriedade Enabled deve estar definida como true, que</p><p>é o padrão).</p><p>5) Execute e teste o aplicativo. Ao passar o mouse sobre o controle para o qual definimos o tooltip,</p><p>a dica de ferramenta configurada deve aparecer.</p><p>Figura 48 – Resumo: elementos gráficos básicos e de apoio</p><p>Este tópico apresentou diversos recursos básicos para o desenvolvedor começar a desenvolver</p><p>interfaces para o usuário. A figura 48 apresenta visualmente todos os controles aqui mencionados.</p><p>116</p><p>Unidade I</p><p>Resumo</p><p>Nesta unidade introduzimos um projeto de programação inspirado</p><p>no romance Os três mosqueteiros. O jogo, denominado Os desafios dos</p><p>mosqueteiros: duelos & destinos, é uma aplicação prática para console</p><p>e tem como objetivo pedagógico apoiar a revisão de conceitos de POO</p><p>utilizando a linguagem C#. Mergulhamos em aspectos técnicos da</p><p>programação do jogo, dentre os quais:</p><p>• Criar e utilizar classes, que são estruturas centrais na POO.</p><p>• Implementar propriedades e métodos nas classes, essenciais para</p><p>definir comportamentos e estados dos objetos.</p><p>• Usar enums (enumerações), que ajudam a organizar conjuntos de</p><p>constantes relacionadas.</p><p>• Implementar delegados, em particular Action, para manipular</p><p>métodos como parâmetros.</p><p>• Usar encapsulamento, princípio-chave da POO, para proteger o</p><p>estado interno de um objeto.</p><p>• Gerar números aleatórios utilizando a classe Random, crucial para</p><p>elementos de sorte no jogo.</p><p>• Usar listas e coleções genéricas (List), fundamentais para</p><p>armazenar e manipular conjuntos de dados.</p><p>• Adotar conceitos de exceções e programação defensiva para</p><p>aumentar a robustez do código.</p><p>• Manipular eventos e callbacks, particularmente no contexto de</p><p>interações no jogo.</p><p>Esses e outros assuntos foram explicados com exemplos práticos</p><p>relacionados ao desenvolvimento do jogo. Foi proposto que o leitor</p><p>compilasse e executasse a primeira versão do jogo e, na sequência,</p><p>percebesse alguns conceitos adicionais não implementados, como herança,</p><p>polimorfismos e design patterns. Foram apresentadas as alterações no</p><p>código relacionadas a esses temas, incluindo exemplos dos padrões</p><p>Singleton e Factory.</p><p>117</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>Finalizada a revisão conceitual e prática, oferecemos uma visão</p><p>abrangente sobre o ambiente de desenvolvimento integrado (IDE) para</p><p>programação em C#, destacando o Visual Studio como ferramenta</p><p>primordial. Discutimos amplamente suas funcionalidades, estrutura</p><p>e flexibilidade, enfatizando a importância de suas capacidades de</p><p>personalização, gestão de projetos, depuração e integração com sistemas</p><p>de controle de versão.</p><p>Além disso, abordamos as diferenças entre as edições do Visual Studio,</p><p>incluindo as versões Community, Professional e Enterprise, e ressaltamos</p><p>a relevância do .NET Framework e .NET Core no desenvolvimento de</p><p>aplicações multiplataforma. Também evidenciamos a importância das</p><p>práticas de refatoração, análise de código e gestão de dependências e</p><p>pacotes, utilizando o NuGet para a manutenção da integridade e sucesso</p><p>de projetos de software.</p><p>Também abordamos o desenvolvimento de interfaces gráficas de</p><p>usuário (GUIs) no contexto do Windows, com foco em Windows Forms,</p><p>uma tecnologia do .NET Framework. Introduzimos alguns aspectos cruciais</p><p>da experiência do usuário (UX) e da interação humano-computador (IHC)</p><p>usando controles específicos para criar interfaces ricas e funcionais.</p><p>Foi enfatizada a simplicidade e eficiência do Windows Forms para</p><p>desenvolvedores, e mencionamos a capacidade de arrastar e soltar</p><p>elementos gráficos, além de discutir suas características, como manipulação</p><p>de eventos e flexibilidade na criação de controles personalizados.</p><p>Apresentamos um guia para criar um formulário no Windows</p><p>Forms usando C# no Visual Studio Community, detalhando desde a</p><p>configuração do projeto até a adição de controles e programação de</p><p>eventos. Discutimos a importância de elementos como formulários e</p><p>caixas de mensagem na interação com o usuário, abordando temas como</p><p>personalização, gerenciamento de layout e manipulação de eventos.</p><p>Também foram explorados controles básicos e adicionais, como rótulos,</p><p>caixas de texto, botões, painéis e grupos, detalhando suas propriedades</p><p>e eventos associados para otimizar a funcionalidade e a estética da</p><p>interface do usuário.</p><p>Vimos que Form é a janela ou container principal que hospeda outros</p><p>elementos de interface. É o espaço básico de interação entre o aplicativo</p><p>e o usuário. MessageBox apresenta informações, alertas ou solicita</p><p>confirmações dos usuários, desempenhando papel crucial na comunicação</p><p>interativa. Labels identificam outros elementos de interface ou fornecem</p><p>breves instruções, melhorando a compreensão do usuário sobre a</p><p>funcionalidade da interface.</p><p>118</p><p>Unidade I</p><p>GroupBox e Panel são containers que ajudam a agrupar elementos</p><p>relacionados, contribuindo para uma organização lógica e uma</p><p>apresentação visual clara. TextBox é um campo fundamental para entrada</p><p>de texto, permitindo aos usuários inserir informações como nome,</p><p>endereço ou feedback.</p><p>Por fim, tooltips melhoram o entendimento e a</p><p>acessibilidade das aplicações.</p><p>119</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>Exercícios</p><p>Questão 1. (UFV 2022, adaptada) Em relação aos conceitos envolvidos na POO, avalie as afirmativas.</p><p>I – Abstração, encapsulamento, herança e polimorfismo são pilares do paradigma de orientação</p><p>a objetos.</p><p>II – Classe abstrata é uma classe não instanciada.</p><p>III – Generalização é a técnica para esconder detalhes internos (atributos/métodos) de uma classe.</p><p>IV – Herança múltipla ocorre quando mais de um método é herdado.</p><p>É correto o que se afirma em:</p><p>A) I, apenas.</p><p>B) III, apenas.</p><p>C) I e II, apenas.</p><p>D) II e IV, apenas.</p><p>E) I, II, III e IV.</p><p>Resposta correta: alternativa C.</p><p>Análise das afirmativas</p><p>I – Afirmativa correta.</p><p>Justificativa: o paradigma de orientação a objetos tem quatro pilares: abstração, encapsulamento,</p><p>herança e polimorfismo. A abstração permite simplificar a complexidade do mundo real, representando</p><p>elementos por objetos, de forma a ocultar detalhes desnecessários e focar apenas as informações</p><p>relevantes. O encapsulamento é um recurso que oculta os detalhes internos do funcionamento dos</p><p>métodos de uma classe, pois conhecer a implementação interna da classe é desnecessário do ponto de</p><p>vista do objeto.</p><p>A herança permite que objetos herdem características e comportamentos de outros objetos, o</p><p>que ajuda reutilizar o código. Por fim, polimorfismo refere-se à capacidade de um mesmo método ser</p><p>aplicado de maneiras distintas em classes ou objetos diferentes.</p><p>120</p><p>Unidade I</p><p>II – Afirmativa correta.</p><p>Justificativa: classe abstrata não pode ser instanciada, ou seja, não pode dar origem a um</p><p>objeto diretamente. Ela apenas fornece um modelo para gerar outras classes, que podem herdar</p><p>suas características.</p><p>III – Afirmativa incorreta.</p><p>Justificativa: a técnica utilizada para esconder detalhes internos de uma classe é o encapsulamento.</p><p>IV – Afirmativa incorreta.</p><p>Justificativa: no contexto de orientação a objetos, a herança múltipla se dá quando uma classe herda</p><p>características e comportamentos de mais de uma classe. A linguagem C#, no entanto, não suporta</p><p>herança múltipla de classes. Essa limitação pode ser contornada com a implementação de interfaces.</p><p>Questão 2. (Fundatec 2023, adaptada) O .NET é uma plataforma de desenvolvimento de software</p><p>que suporta diversas linguagens de programação, incluindo C#. Nesse contexto, assinale a alternativa que</p><p>indica um benefício de utilizarmos a plataforma .NET para desenvolver aplicações.</p><p>A) Permite desenvolver aplicações somente para desktop.</p><p>B) Possui um conjunto limitado de bibliotecas para criar interfaces gráficas de usuário.</p><p>C) É uma plataforma dedicada ao desenvolvimento em linguagem C#.</p><p>D) Possui uma grande variedade de ferramentas e recursos que permitem desenvolver aplicações</p><p>para diferentes plataformas e dispositivos.</p><p>E) É uma plataforma exclusiva para desenvolver aplicações em nuvem.</p><p>Resposta correta: alternativa D.</p><p>Análise das alternativas</p><p>A) Alternativa incorreta.</p><p>Justificativa: a plataforma .NET permite o desenvolvimento de diversos tipos de aplicações, não</p><p>apenas para desktop.</p><p>B) Alternativa incorreta.</p><p>Justificativa: o .NET tem um rico conjunto de bibliotecas para a criação de interfaces gráficas de</p><p>usuário, não um conjunto limitado.</p><p>121</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>C) Alternativa incorreta.</p><p>Justificativa: o .NET suporta várias linguagens de programação, como C#, F# e VB.NET, entre outras.</p><p>Portanto, a plataforma não é dedicada apenas ao desenvolvimento em C#.</p><p>D) Alternativa correta.</p><p>Justificativa: a plataforma detém uma grande variedade de ferramentas e recursos que permitem</p><p>desenvolver aplicações para diferentes plataformas e dispositivos, incluindo aplicações para desktop,</p><p>web, mobile, jogos e internet das coisas, entre outras.</p><p>E) Alternativa incorreta.</p><p>Justificativa: o .NET não é exclusivo para desenvolver aplicações em nuvem, embora também possa</p><p>ser utilizado para isso.</p><p>genéricas no C# oferecem maneiras</p><p>12</p><p>Unidade I</p><p>flexíveis e eficientes de armazenar e manipular dados, permitindo escrever um código que seja tanto</p><p>seguro quanto reutilizável.</p><p>Em aplicativos para desktop e mobile, frequentemente usamos eventos e delegados para responder</p><p>a interações do usuário, seja um clique do mouse, seja uma entrada do teclado. No C#, delegado é um</p><p>tipo que pode fazer referência a um método. Essa referência pode ser alterada em tempo de execução,</p><p>tornando os delegados instrumentos poderosos para criar sistemas responsivos e dinâmicos.</p><p>Associados a delegados temos eventos, essencialmente mecanismos que permitem a uma classe</p><p>notificar outras classes se ocorrer algo de interesse; isso é particularmente relevante em aplicações de</p><p>interface gráfica, nas quais ações como cliques ou entradas do usuário podem desencadear respostas</p><p>específicas no software. Também não podemos esquecer dos padrões de projeto – como Singleton,</p><p>Factory e Repository –, que, embora não sejam exclusivos da POO, muitas vezes se alinham com seus</p><p>conceitos para ajudar a resolver problemas comuns de design.</p><p>Por fim, ao lidar com aplicativos que se conectam a bancos de dados, é essencial um bom tratamento</p><p>de exceções para gerenciar situações inesperadas, como falhas de conexão. Além disso, a integração</p><p>com o .NET Framework ou o .NET Core/.NET 5+ proporciona acesso a uma vasta biblioteca de classes e</p><p>métodos prontos para uso, desde a manipulação de strings e matemática complexa até funcionalidades</p><p>de rede e entrada/saída de arquivos.</p><p>Neste tópico passaremos por revisão e aprofundamento, com exercícios práticos de vários</p><p>aspectos-chave da POO e do C#, que são importantes para desenvolver interfaces desktop e mobile.</p><p>Também será apresentado o ambiente de desenvolvimento integrado (IDE) e alguns detalhes do .NET</p><p>Framework e .Net Core.</p><p>1.1 Revisando conceitos de POO: exercícios práticos (console)</p><p>Para revisar conceitos, vamos criar um jogo inspirado no romance Les Trois Mousquetaires, traduzido</p><p>no Brasil como Os três mosqueteiros, escrito por Alexandre Dumas e publicado pela primeira vez em 1844.</p><p>O jogo criado para este livro-texto terá como nome Os desafios dos mosqueteiros: duelos & destinos.</p><p>Antes de detalhar o jogo, um breve resumo do livro: D’Artagnan, um jovem das regiões rurais da</p><p>Gasconha, viaja para Paris com esperanças de se juntar aos Mosqueteiros do Rei, a guarda de elite do rei</p><p>Luís XIII. No entanto, no início de sua jornada, ele se envolve em duelos individuais com três homens</p><p>diferentes devido a mal-entendidos: Athos, Porthos e Aramis. Em vez de lutar contra eles, os quatro são</p><p>interrompidos por guardas do cardeal Richelieu, o ministro-chefe do rei, e juntos eles enfrentam os</p><p>guardas e solidificam uma amizade duradoura.</p><p>O quarteto se envolve em diversas aventuras, e muitas delas os colocam em oposição ao cardeal</p><p>Richelieu e sua agente, a bela e perigosa Milady de Winter. Enquanto D’Artagnan é apaixonado pela</p><p>jovem Constance Bonacieux, a trama se desenrola em um fundo de intrigas reais e políticas, incluindo</p><p>os esforços de Richelieu para frustrar os planos da rainha Ana da Áustria e seu suposto amante, o Duque</p><p>de Buckingham.</p><p>13</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>Figura 1 – Os mosqueteiros do nosso jogo; imagem produzida com tecnologia DALL-E,</p><p>ferramenta de inteligência artificial desenvolvida pela OpenAI</p><p>Observação</p><p>Neste tópico ainda não será abordada a interface gráfica (falaremos</p><p>disso em detalhe depois). Por ora faremos os exercícios usando o console do</p><p>C#, cuja característica é fornecer uma simples saída de caracteres na tela.</p><p>Em virtude desse fato, a imagem de nossos mosqueteiros neste primeiro</p><p>momento seria um pouco diferente:</p><p>Figura 2 – Os mosqueteiros do nosso jogo como arte ASCII; imagem produzida também com tecnologia DALL-E</p><p>14</p><p>Unidade I</p><p>No ambiente empresarial contemporâneo, uma série de etapas é cumprida por diferentes</p><p>profissionais, fundamentais para o sucesso do projeto muito antes de um programador iniciar a</p><p>codificação de um sistema em C#. Esse processo começa com a fase de concepção, na qual especialistas</p><p>em negócios e analistas de sistemas trabalham juntos para compreender e definir os objetivos do</p><p>projeto, fazendo uma análise minuciosa das necessidades do usuário e do mercado, identificando</p><p>requisitos críticos que o sistema deve atender.</p><p>Após essa fase inicial, entra em cena a equipe de análise e design de sistemas, cujos profissionais</p><p>são responsáveis por traduzir os requisitos coletados em um projeto de software detalhado, utilizando</p><p>metodologias e ferramentas de modelagem, como unified modeling language (UML), para criar uma</p><p>representação visual e técnica do sistema proposto. Essa etapa é crucial pois guia os desenvolvedores</p><p>durante a fase de programação.</p><p>Além disso, a arquitetura do sistema é cuidadosamente planejada. Arquitetos de software determinam</p><p>sua estrutura geral, incluindo a seleção de tecnologias, definição de padrões de design e garantia de</p><p>que o sistema será escalável e seguro – planejamento essencial para assegurar que o sistema atenda</p><p>tanto às necessidades atuais quanto às futuras demandas. Paralelamente, a equipe de gerenciamento</p><p>de projetos desempenha papel vital na coordenação de todas essas atividades, estabelecendo</p><p>cronogramas, alocando recursos e garantindo que todas as equipes estejam alinhadas com os</p><p>objetivos do projeto.</p><p>Uma gestão eficaz é fundamental para manter o projeto dentro do prazo e do orçamento. Finalmente,</p><p>antes de o programador começar a escrever o código em C#, a equipe de qualidade de software entra</p><p>em ação. Essa equipe é responsável por definir os padrões e processos de qualidade que serão seguidos</p><p>durante a fase de desenvolvimento. Eles também planejam estratégias de teste que serão implementadas</p><p>para garantir que o software seja robusto, confiável e livre de erros. Portanto, quando um programador</p><p>inicia a programação em C#, ele se baseia num extenso trabalho preparatório realizado por uma equipe</p><p>multidisciplinar. Colaboração e planejamento meticulosos são essenciais para criar um sistema eficaz e</p><p>de alta qualidade.</p><p>Para nosso propósito, considere que as equipes já fizeram seu trabalho e obtiveram as seguintes</p><p>informações:</p><p>15</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>Quadro 1 – Detalhes do jogo</p><p>Objetivo</p><p>Avançar pela vida de um mosqueteiro, desde sua iniciação até alcançar o reconhecimento máximo,</p><p>seja por ser condecorado pelo rei, seja por alcançar um posto de grande honra e responsabilidade. Os</p><p>jogadores enfrentarão eventos aleatórios que afetarão sua fortuna e honra</p><p>Componentes</p><p>Tabuleiro com 60 casas: é linear e estilizado como um mapa da França do século XVII, começando na</p><p>província e terminando em Paris. Ao longo do caminho, há espaços coloridos: alguns têm instruções fixas</p><p>e outros indicam que uma carta de evento deve ser retirada</p><p>Cartas de evento: detalham eventos aleatórios, positivos ou negativos, que afetam o jogador</p><p>Dado: para determinar o número de espaços a mover</p><p>Roleta de duelo: para definir o desfecho (vitória, empate ou derrota) de um duelo entre dois</p><p>mosqueteiros</p><p>Fichas de fortuna e honra: moedas douradas (para fortuna) e emblemas de espada cruzada (para</p><p>honra)</p><p>Peças: representam jogadores (cada uma representa um mosqueteiro: D’Artagnan, Athos, Porthos e</p><p>Aramis)</p><p>Roleta de duelo</p><p>Vitória (verde): “Você venceu o duelo! Avance três casas e ganhe duas fichas de honra”</p><p>Empate (amarelo): “O duelo terminou empatado. Fique onde está e ganhe uma ficha de honra”</p><p>Derrota (vermelho): “Você foi derrotado. Volte duas casas e perca uma ficha de honra”</p><p>Movimento</p><p>Em sua vez, cada participante gira a roda ou joga o dado e move sua peça pelo número indicado de</p><p>espaços. Não é permitido dividir a casa. Quando isso acontecer, o jogador da vez deve ocupar o próximo</p><p>espaço livre</p><p>Espaços de</p><p>evento</p><p>Se um jogador pousar num espaço colorido sem instrução fixa, ele deve retirar uma carta de evento e</p><p>seguir as instruções</p><p>Espaços fixos Alguns espaços</p><p>têm instruções permanentes</p><p>Configuração</p><p>Cada jogador escolhe um mosqueteiro e coloca sua peça no início do tabuleiro</p><p>As cartas de evento são embaralhadas e colocadas em uma pilha ao lado do tabuleiro</p><p>Cada jogador começa com cinco fichas de fortuna e cinco de honra</p><p>Duelo de honra</p><p>Quando um jogador cai no espaço do duelo de honra, ele pode desafiar qualquer outro jogador usando a</p><p>roleta de duelo</p><p>O vencedor do duelo ganha duas fichas de honra do perdedor. Em caso de empate, nenhum jogador</p><p>perde nem ganha fichas</p><p>Tributo ao</p><p>cardeal</p><p>Ao cair nesse espaço, o jogador deve pagar duas fichas de fortuna como tributo ao cardeal Richelieu. Se</p><p>o jogador não tiver fortuna suficiente, perde uma ficha de honra</p><p>Audiência com</p><p>o rei</p><p>É a finalização da jornada dos mosqueteiros, momento em que eles são chamados para uma audiência</p><p>final com o rei para receber seu julgamento</p><p>Ao chegar a essa casa, o jogador decide se quer o “reconhecimento da corte” (e nesse caso fica sem</p><p>jogar, aguardando que todos os demais cheguem à audiência) ou se quer apostar todas as suas fichas de</p><p>fortuna e honra na “roleta do julgamento real”. Essa roleta terá apenas dois marcadores: “condecoração</p><p>real” e “desprezo da corte”</p><p>Condecoração real: o jogador vence o jogo</p><p>Desprezo da corte: o jogador perde todas as suas fichas de honra e fortuna</p><p>Vencedor</p><p>O jogo termina quando algum dos participantes recebe condecoração real ou quando todos chegam ao</p><p>final do tabuleiro. Neste caso, eles contam suas fichas de fortuna e honra</p><p>O vencedor é determinado pela combinação mais alta de fichas: um ponto para cada ficha de fortuna e</p><p>dois pontos para cada ficha de honra. O jogador com o maior total vence</p><p>16</p><p>Unidade I</p><p>Considere também que a equipe de projeto já criou um diagrama de classes, que facilita o</p><p>entendimento do jogo:</p><p>Figura 3 – Diagrama de classes do jogo</p><p>Por enquanto não é necessário se preocupar com cada detalhe do jogo. As informações apresentadas</p><p>até aqui são úteis para que o programador tenha uma visão exemplificada do processo anterior à</p><p>programação em si. Lembre-se que a POO surgiu como paradigma que ajuda a equipe a quebrar</p><p>problemas complexos em problemas menores. Ao longo deste tópico, desenvolveremos gradativamente o</p><p>código-fonte do jogo, exercitando a POO em C#. Também revisitaremos alguns conceitos e adicionaremos</p><p>alguns detalhes complementares.</p><p>O quadro 1 e o diagrama da figura 3 também servirão como referência de consulta em outras etapas</p><p>do desenvolvimento do jogo. O quadro 2 fornece uma descrição resumida da figura 3:</p><p>17</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>Quadro 2 – Resumo das classes do jogo, seus métodos e atributos</p><p>Classe Descrição Atributos e métodos principais</p><p>Programa Classe principal do programa,</p><p>responsável por iniciar o jogo</p><p>– globalRandom: campo estático do tipo Random para gerar</p><p>números aleatórios</p><p>– Main: método estático que é o ponto de entrada do programa.</p><p>Cria uma instância do jogo e o inicia</p><p>Jogo</p><p>Representa o jogo gerenciando</p><p>os elementos principais, como</p><p>jogadores, tabuleiro e baralho</p><p>– tabuleiro: objeto da classe Tabuleiro</p><p>– jogadores: lista de objetos Jogador</p><p>– baralhoDeEventos: objeto da classe Baralho para eventos do jogo</p><p>– aleatorio: objeto Random para gerar números aleatórios</p><p>– roletaDeDuelo: objeto da classe RoletaDeDuelo para duelos</p><p>– Métodos para gerenciar o jogo, como ExecutarJogo, IniciarJogo,</p><p>JogarTurno e outros</p><p>Jogador Representa um jogador, com suas</p><p>propriedades e ações</p><p>– Propriedades como Nome, Posicao, FichasDeFortuna,</p><p>FichasDeHonra, DevePausar, NoReconhecimentoDaCorte, Eliminado</p><p>– Métodos como PausarProximoTurno, AtualizarTurno e RolarDado</p><p>Tabuleiro Representa o jogo com suas casas e</p><p>configurações</p><p>– numeroDeCasas: número total de casas no tabuleiro</p><p>– casas: lista de objetos Casa que compõem o tabuleiro</p><p>– Métodos para inicializar e obter informações das casas do</p><p>tabuleiro</p><p>Casa</p><p>Representa uma casa individual no</p><p>tabuleiro, com um tipo específico e</p><p>ações associadas</p><p>– Tipo: enumeração TipoCasa para indicar o tipo da casa</p><p>– CasaDeRetorno: valor opcional que indica para qual casa o</p><p>jogador deve retornar, se aplicável</p><p>– Construtor para inicializar a casa com um tipo específico e casa</p><p>de retorno</p><p>Baralho Representa as várias cartas de evento</p><p>do jogo</p><p>– cartas: lista de objetos CartaDeEvento</p><p>– random: objeto Random para escolher cartas aleatoriamente</p><p>– Método SortearCarta para sortear uma carta do baralho</p><p>CartaDeEvento Representa um evento individual</p><p>com descrição e efeito no jogador</p><p>– Descricao: texto que apresenta o evento da carta</p><p>– Efeito: ação (delegate Action) que define o efeito da</p><p>carta no jogador</p><p>– Construtor para inicializar a carta com descrição e efeito</p><p>RoletaDeDuelo Determina o resultado de duelos no</p><p>jogo</p><p>– aleatorio: objeto Random para gerar o resultado do duelo</p><p>– Método Girar para obter um resultado de duelo (ResultadoDuelo)</p><p>ResultadoDuelo Enumeração dos possíveis resultados</p><p>de um duelo Enumeração com valores Vitoria, Empate, Derrota</p><p>TipoCasa Enumeração dos tipos de casa no</p><p>tabuleiro</p><p>Enumeração com valores como Normal, Evento, EscolaDeEsgrima,</p><p>DueloDeHonra, Duelo, Estalagem, TributoAoCardeal, Retorno,</p><p>AudienciaComORei</p><p>18</p><p>Unidade I</p><p>Vamos iniciar a programação desenvolvendo a classe principal do jogo, chamada Programa:</p><p>1. using System;</p><p>2. using System.Collections.Generic;</p><p>3. using System.Linq;</p><p>4. class Programa</p><p>5. {</p><p>6. private static readonly Random globalRandom = new Random();</p><p>7. static void Main(string[] args)</p><p>8. {</p><p>9. Jogo jogo = new Jogo(globalRandom);</p><p>10. jogo.IniciarJogo();</p><p>11. }</p><p>12. }</p><p>Figura 4 – Classe Programa: revisão de conceitos básicos</p><p>Podemos analisar em detalhe as linhas do código-fonte da figura 4. As três primeiras linhas contêm</p><p>uma diretiva using, que informa ao compilador C# os namespaces que o programa irá utilizar. System</p><p>é um namespace fundamental na biblioteca de classes .NET, que fornece funcionalidades básicas</p><p>como entrada e saída de dados, manipulação de strings, entre outras. System.Collections.Generic é um</p><p>namespace que contém definições para classes genéricas de coleção – como listas e dicionários –, usadas</p><p>para armazenar e manipular conjuntos de dados (veremos adiante que ela será usada na criação de listas</p><p>de jogadores, cartas de baralho, entre outras ferramentas). Já System.Linq fornece funcionalidades para</p><p>consultar e manipular dados em coleções e outras fontes de dados usando LINQ. Também veremos neste</p><p>tópico que ele será usado nos métodos ExecutarDueloDeHonra e AnunciarVencedor da classe Jogo.</p><p>Na linha 4, definimos uma classe chamada Programa, cujo conteúdo interno e funcionamento</p><p>se estendem da linha 5 até a 12. Em C#, classe é uma estrutura que pode conter dados e métodos</p><p>para trabalhar com esses dados (por questões didáticas, deixaremos a revisão da linha 6 para depois).</p><p>As linhas 7 a 11 referem-se ao método Main, que é o ponto de entrada de qualquer aplicativo C#.</p><p>Quando compilamos e executamos um programa C#, o ambiente de execução .NET (como o .NET Core</p><p>ou .NET Framework) carrega o assembly (o arquivo compilado) e procura pelo método Main como</p><p>ponto de entrada. Uma vez encontrado, ele começa a execução a partir desse método.</p><p>Observação</p><p>O método Main não precisa estar especificamente dentro de uma</p><p>classe chamada Program (ou Programa). O nome Program para a classe</p><p>que contém o método Main é uma convenção muitas vezes utilizada</p><p>nos templates-padrão do Visual Studio para aplicativos de console, mas</p><p>não é obrigatório.</p><p>19</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>Na linha 9 criamos uma instância da classe Jogo, passando globalRandom como argumento</p><p>para o construtor; e jogo é o nome da variável que referencia essa instância. Em breve iniciaremos a</p><p>programação da classe Jogo (ainda não foi criada). Já a linha 10 demonstra uma chamada ao método</p><p>IniciarJogo do objeto jogo – responsável por iniciar a lógica dele (e teremos que programá-lo também).</p><p>Agora, algumas características da linha 6, que declara na classe Programa</p><p>um campo batizado de</p><p>globalRandom com a seguinte sintaxe:</p><p>• private: modificador de acesso que torna o campo acessível apenas dentro da própria classe</p><p>Programa. Ou seja, estamos rememorando o encapsulamento.</p><p>• static: significa que o campo pertence à classe Programa em si, e não a uma instância</p><p>específica dela.</p><p>• readonly: indica que o valor do campo só pode ser atribuído à declaração ou ao construtor</p><p>da classe.</p><p>• Random: tipo de campo que é uma classe do .NET Framework usada para gerar números</p><p>aleatórios. Para usá-la, você normalmente cria uma instância dessa classe, que é um objeto.</p><p>• globalRandom: nome dado ao campo.</p><p>• = new Random();: o campo é inicializado com uma nova instância da classe Random.</p><p>Random representa o fator sorte no jogo. Por exemplo, mosqueteiros avançam as casas no</p><p>tabuleiro conforme o número sorteado no lançamento de um dado; no caso, usando Random o</p><p>número que será sorteado a cada rodada do jogo é aproximadamente aleatório. O mesmo vale para</p><p>duelos: a decisão sobre quem ganha, perde, ou sobre o empate vem de um sorteio. Na audiência real</p><p>(final do jogo) também há uma aposta na qual a sorte é novamente representada.</p><p>Observação</p><p>Em programação, a expressão aproximadamente aleatório se refere</p><p>ao conceito de pseudoaleatoriedade. O termo aproximadamente é</p><p>usado porque, embora os números gerados por tais métodos pareçam</p><p>aleatórios e tenham propriedades estatísticas de números aleatórios, eles</p><p>são gerados por um processo determinístico, ou seja, podem ser previstos</p><p>se você conhecer o estado inicial do gerador de números aleatórios.</p><p>Em C#, como em muitas outras linguagens de programação, Random</p><p>é uma classe que implementa um gerador de números pseudoaleatórios</p><p>(PRNG) e usa um algoritmo que toma um valor inicial, conhecido como</p><p>semente (seed), e a partir dela gera uma série de números que têm</p><p>20</p><p>Unidade I</p><p>aparência de aleatoriedade. Na prática, isso significa que, se você iniciar</p><p>duas instâncias de Random com a mesma semente, elas produzirão a</p><p>mesma sequência de números: não há aleatoriedade real. Além disso, no</p><p>longo prazo, a sequência de números vai começar a se repetir. O período de</p><p>repetição depende do algoritmo usado pelo PRNG.</p><p>Em resumo, aproximadamente aleatório significa que, embora os</p><p>números produzidos se comportem como aleatórios para muitas aplicações</p><p>práticas, são gerados de maneira previsível e replicável. Por exemplo, os</p><p>números gerados por Random não devem ser usados para aplicações que</p><p>requeiram alta segurança, como criptografia ou geração de senhas seguras.</p><p>Para esses fins, é melhor usar classes como RNGCryptoServiceProvider no</p><p>.NET, que são projetadas para ser mais imprevisíveis.</p><p>Colocamos o Random no Program, uma vez que pode não ser ideal criar novas instâncias da</p><p>classe Random em métodos diferentes, especialmente se esses métodos forem chamados em rápida</p><p>sucessão, o que pode levar a resultados repetidos e, portanto, degradar o fator sorte. Isso ocorre</p><p>porque Random é inicializado com um valor de semente baseado no relógio do sistema, e criar</p><p>várias instâncias rapidamente pode fazer com que elas tenham a mesma semente. Idealmente, uma</p><p>única instância de Random deve ser compartilhada ou reutilizada, o que justifica a linha 6 ter sido</p><p>arbitrariamente colocada em Program.</p><p>Saiba mais</p><p>A aleatoriedade gerada pela classe Random é suficiente para muitos</p><p>usos gerais (como neste jogo), mas não é apropriada para aplicações</p><p>que exigem alto nível de segurança, como criptografia. Para esses casos,</p><p>são necessárias abordagens mais sofisticadas; uma boa referência para</p><p>aprofundar o tema é este livro:</p><p>TEILHET, S.; HILYARD, J. C# Cookbook. Cebastopol: O’Reilly Media, 2004. v. 1</p><p>Introduzido o assunto aleatoriedade, vamos desenvolver a classe RoletaDeDuelo, parte fundamental</p><p>do jogo para determinar seus resultados. Ela utiliza um gerador de números aleatórios para simular</p><p>uma roleta, e com base no número gerado retorna se o duelo resultou em vitória, empate ou derrota.</p><p>Esses resultados são usados em outras partes do jogo para definir as consequências dos duelos para</p><p>os jogadores.</p><p>21</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>1. class RoletaDeDuelo</p><p>2. {</p><p>3. private Random aleatorio;</p><p>4.</p><p>5. // Construtor da RoletaDeDuelo</p><p>6. public RoletaDeDuelo(Random random)</p><p>7. {</p><p>8. aleatorio = random;</p><p>9. }</p><p>10.</p><p>11. // Método para girar a roleta</p><p>12. public ResultadoDuelo Girar()</p><p>13. {</p><p>14. int resultado = aleatorio.Next(3); // 0, 1 ou 2</p><p>15. switch (resultado)</p><p>16. {</p><p>17. case 0: return ResultadoDuelo.Vitoria;</p><p>18. case 1: return ResultadoDuelo.Empate;</p><p>19. default: return ResultadoDuelo.Derrota;</p><p>20. }</p><p>21. }</p><p>22. }</p><p>Figura 5 – Classe RoletaDeDuelo</p><p>Novamente analisaremos cada linha, para revisar o conteúdo de programação C#. A classe é declarada</p><p>na linha 1 e finaliza na 22, contendo um campo privado (batizado de aleatório) e dois métodos (um</p><p>construtor e um que gira a roleta). Na linha 3, o campo privado do tipo Random é usado para gerar</p><p>números aleatórios, cruciais para a roleta funcionar. Na linha 6, o construtor da classe recebe um objeto</p><p>Random como argumento e o atribui ao campo privado aleatório; isso permite que a roleta use um</p><p>gerador de números aleatórios fornecido externamente, o que é útil para garantir a aleatoriedade e</p><p>facilitar testes (lembremos também que o método construtor tem o mesmo nome da classe).</p><p>Girar (linha 12) é o método principal da classe: quando chamado, ele simula o giro da roleta para</p><p>determinar o resultado de um duelo. Para fazer isso ele gera um número inteiro aleatório entre 0 e 2</p><p>(linha 14) com o auxílio do método Next (membro da classe Random), que tem várias sobrecargas, ou</p><p>seja, pode ser chamado de diferentes maneiras, a depender dos argumentos fornecidos.</p><p>22</p><p>Unidade I</p><p>As sobrecargas mais comuns são:</p><p>• Next(): retorna um número inteiro positivo aleatório de 32 bits. O valor retornado está no</p><p>intervalo de 0 a Int32.MaxValue (exclusivo), ou seja, entre 0 e 2147483647.</p><p>• Next(int maxValue): retorna um número inteiro aleatório que está dentro de um intervalo</p><p>especificado. O valor retornado está no intervalo de 0 a maxValue (exclusivo). Em nosso caso,</p><p>Next(3) retorna um número entre 0 e 2.</p><p>• Next(int minValue, int maxValue): retorna um número inteiro aleatório dentro de um intervalo</p><p>especificado. O valor retornado está no intervalo de minValue a maxValue (exclusivo). Por exemplo,</p><p>Next(5, 10) retorna um número entre 5 e 9.</p><p>A linha 15 inicia uma estrutura switch, que usa o número gerado para selecionar um resultado</p><p>do duelo:</p><p>• Caso 0: retorna ResultadoDuelo.Vitoria, indicando que o jogador venceu.</p><p>• Caso 1: retorna ResultadoDuelo.Empate, indicando empate.</p><p>• Caso-padrão (default): retorna ResultadoDuelo.Derrota, indicando que o jogador perdeu.</p><p>Portanto, a probabilidade esperada de vitória, derrota e empate em cada rodada é de um terço,</p><p>garantindo o equilíbrio na distribuição dos duelos. Note que ainda não desenvolvemos ResultadoDuelo;</p><p>faremos isso usando uma enumeração, conforme a figura 6 (é um tipo distinto, que engloba um conjunto</p><p>de constantes nomeadas):</p><p>1. enum ResultadoDuelo</p><p>2. {</p><p>3. Vitoria,</p><p>4. Empate,</p><p>5. Derrota</p><p>6. }</p><p>Figura 6 – Enumeração do resultado dos duelos</p><p>Aproveitando o assunto, podemos também criar uma enumeração para representar os diferentes</p><p>tipos de casa do tabuleiro (figura a seguir):</p><p>23</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>1. enum TipoCasa</p><p>2. {</p><p>3. Normal,</p><p>4. Evento,</p><p>5. EscolaDeEsgrima,</p><p>6. DueloDeHonra,</p><p>7. Duelo,</p><p>8. Estalagem,</p><p>9. TributoAoCardeal,</p><p>10. Retorno,</p><p>11. AudienciaComORei</p><p>12. }</p><p>Figura 7 – Tipos de casa do tabuleiro</p><p>O tabuleiro tem 60 casas; destas, 38 sem nenhuma descrição, ou seja, caso o jogador caia nela, nada</p><p>acontece. As 22 casas restantes têm descrições especiais (quadro 3), as quais podem ser agrupadas em</p><p>9 categorias, inseridas na enumeração TipoCasa (figura 7).</p><p>Quadro 3 – Casas do tabuleiro com descrições especiais</p><p>Casa Descrição</p><p>1 Início – província</p><p>5 Evento</p><p>(pegue uma carta de evento)</p><p>6 Escola de esgrima: “Treine suas habilidades. Ganhe uma ficha de honra”</p><p>8 Duelo de honra</p><p>10 Duelo (gire a roleta de duelo para determinar seu destino)</p><p>11 Estalagem: “Pague pela sua estadia. Perca uma ficha de fortuna”</p><p>12 Tributo ao cardeal</p><p>15 Retorno (seta apontando para a casa 5: “Desafio inesperado! Retorne à casa 5”)</p><p>20 Evento (pegue uma carta de evento)</p><p>25 Duelo (gire a roleta de duelo)</p><p>26 Duelo de honra</p><p>29 Retorno (seta apontando para a casa 20: “Reveses na jornada! Retorne à casa 20”)</p><p>30 Tributo ao cardeal</p><p>35 Evento (pegue uma carta de evento)</p><p>24</p><p>Unidade I</p><p>Casa Descrição</p><p>40 Duelo (gire a roleta de duelo)</p><p>45 Duelo de honra</p><p>46 Evento (pegue uma carta de evento)</p><p>50 Retorno (seta apontando para a casa 35: “Missão falhou! Retorne à casa 35”)</p><p>51 Tributo ao cardeal</p><p>55 Evento (pegue uma carta de evento)</p><p>56 Duelo (gire a roleta de duelo)</p><p>Enums são úteis porque melhoram a legibilidade do código e reduzem a possibilidade de erro ao</p><p>restringir valores a um conjunto finito de opções. São essencialmente grupos de constantes inteiras que</p><p>têm nomes para tornar um programa mais fácil de ler e manter.</p><p>Como definimos o tipo de casa, seria interessante definir também a própria classe Casa (figura 8).</p><p>1. class Casa</p><p>2. {</p><p>3. public TipoCasa Tipo { get; private set; }</p><p>4. public int? CasaDeRetorno { get; private set; } // Usado para</p><p>casas de retorno</p><p>5.</p><p>6. public Casa(TipoCasa tipo, int? casaDeRetorno = null)</p><p>7. {</p><p>8. Tipo = tipo;</p><p>9. CasaDeRetorno = casaDeRetorno;</p><p>10. }</p><p>11. }</p><p>Figura 8 – Classe Casa</p><p>A linha 3 define uma propriedade chamada Tipo. TipoCasa é o tipo enumerado (definido na figura 7),</p><p>que lista possíveis tipos de casa no tabuleiro (como Normal, Evento, Duelo etc.). Modificador public</p><p>significa que qualquer parte do código pode acessar essa propriedade, enquanto private set indica que</p><p>somente a própria classe Casa pode alterar seu valor. Criamos também outra propriedade na linha 4,</p><p>chamada CasaDeRetorno, e int? significa que pode armazenar um número inteiro ou ser nulo (null),</p><p>sendo usado em casas especiais que enviam o jogador de volta a outra casa.</p><p>25</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>Finalmente, da linha 6 à 10 definimos o construtor da classe Casa. Lembre-se: construtor é um</p><p>método especial chamado quando criamos um novo objeto da classe; ele configura o objeto com</p><p>valores iniciais.</p><p>O método tem dois parâmetros de entrada:</p><p>• TipoCasa tipo: parâmetro obrigatório ao criar um objeto Casa. Ele define o tipo da casa.</p><p>• int? casaDeRetorno = null: parâmetro opcional. Se não for fornecido, seu valor será null. Ele</p><p>indica a casa para a qual um jogador deve retornar, se aplicável.</p><p>As linhas 8 e 9 atribuem os valores recebidos pelos parâmetros às propriedades da classe. Isso</p><p>significa que, quando criamos um objeto Casa, definimos seu tipo e opcionalmente uma casa de retorno.</p><p>Programadas as casas, podemos montar o tabuleiro: o código apresentado na figura 9 ilustra o tabuleiro</p><p>no qual os jogadores, representados por personagens como D’Artagnan e Athos, avançam por ele nos</p><p>diferentes tipos de casa, enfrentando eventos variados e duelos.</p><p>1. class Tabuleiro</p><p>2. {</p><p>3. private int numeroDeCasas;</p><p>4. private List casas;</p><p>5.</p><p>6. // Adicionar um getter público para numeroDeCasas</p><p>7. public int NumeroDeCasas</p><p>8. {</p><p>9. get { return numeroDeCasas; }</p><p>10. }</p><p>11.</p><p>12. // Construtor do Tabuleiro</p><p>13. public Tabuleiro(int numeroDeCasas)</p><p>14. {</p><p>15. this.numeroDeCasas = numeroDeCasas;</p><p>16. casas = new List();</p><p>17. InicializarCasas();</p><p>26</p><p>Unidade I</p><p>18. }</p><p>19. private void InicializarCasas()</p><p>20. {</p><p>21. // Adiciona casas vazias ou com ações especificadas</p><p>22. for (int i = 1; i casas.Count)</p><p>77. {</p><p>78. throw new InvalidOperationException(“A posição do jogador está</p><p>fora do tabuleiro.”);</p><p>79. }</p><p>80. return casas[posicao – 1]; // Ajuste para índice baseado em zero</p><p>81. }</p><p>82.</p><p>83. }</p><p>Figura 9 – Classe Tabuleiro</p><p>A lógica da classe Tabuleiro é fundamental para o jogo funcionar, sendo responsável por definir o</p><p>layout e as regras do tabuleiro, composto por um número especificado de casas (numeroDeCasas), cada</p><p>uma com um tipo definido (TipoCasa). Esses tipos variam entre normal, evento, escola de esgrima, duelo</p><p>de honra e outros, influenciando diretamente o desenrolar do jogo a cada turno.</p><p>A decisão de incluir um getter público (linha 7) para uma propriedade privada (linha 3) na classe</p><p>Tabuleiro é um exemplo clássico de encapsulamento, pilar central da POO. Essa abordagem – que pode</p><p>parecer menos direta do que simplesmente tornar a propriedade pública – traz consigo uma série de</p><p>benefícios sutis, mas poderosos para a estrutura e integridade do seu código.</p><p>Comecemos considerando o que encapsulamento realmente significa. Em seu ser cerne está a ideia</p><p>de esconder detalhes internos de uma classe do mundo externo, expondo apenas o que é necessário. Ao</p><p>manter a propriedade numeroDeCasas privada e fornecer um getter público, controlamos rigorosamente</p><p>como essa informação é acessada por outras partes do programa. Isso significa que, enquanto outras</p><p>classes podem ler o valor de numeroDeCasas, elas não podem modificá-lo diretamente. É uma maneira de</p><p>proteger os dados internos da classe Tabuleiro contra alterações inesperadas ou indesejadas, garantindo</p><p>assim a integridade do estado do tabuleiro durante o jogo.</p><p>Essa proteção não é apenas uma questão de segurança de dados, mas também de manutenção e</p><p>flexibilidade do código. Imagine que no futuro mude a lógica para determinar o número de casas no</p><p>tabuleiro; se numeroDeCasas fosse propriedade pública, qualquer alteração na sua lógica de cálculo</p><p>exigiria mudanças em todas as partes do código que a acessam. Com um getter, no entanto, essa</p><p>mudança seria encapsulada dentro da classe Tabuleiro. O restante do seu programa não precisaria saber</p><p>como numeroDeCasas é calculado ou armazenado; ele só precisa saber que pode obter esse valor através</p><p>do getter, tornando seu código muito mais fácil de manter e evoluir. Além disso, um getter abre a porta</p><p>para possíveis validações ou transformações dos dados quando acessados.</p><p>29</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>Contrastando com a abordagem de uma propriedade pública – na qual numeroDeCasas seria exposto</p><p>diretamente –, a abordagem atual oferece uma camada adicional de proteção. Com uma propriedade</p><p>pública, qualquer parte do código poderia alterar o valor de numeroDeCasas, potencialmente levando a</p><p>estados incoerentes ou a bugs difíceis de rastrear. A abordagem adotada,</p><p>com um campo privado e um</p><p>getter público, assegura que a classe Tabuleiro tenha controle total sobre seu estado interno.</p><p>Portanto, ao escolher um getter público para a propriedade privada numeroDeCasas, adotamos uma</p><p>prática de design que protege os dados internos da classe ao mesmo tempo que oferece flexibilidade,</p><p>segurança e facilidade de manutenção para o código. Ao encapsular a propriedade dessa maneira, a classe</p><p>Tabuleiro mantém um controle rigoroso sobre como seus dados internos são acessados e manipulados,</p><p>garantindo que o estado do tabuleiro seja sempre apresentado de forma consistente e confiável ao resto</p><p>do programa.</p><p>Na linha 13 o construtor da classe Tabuleiro recebe o número total de casas como parâmetro</p><p>e inicializa a lista de casas. Em C# as listas são parte essencial do trabalho com coleções de dados;</p><p>tecnicamente, lista é a implementação do conceito de uma lista dinâmica, fornecida pela classe</p><p>List no namespace System.Collections.Generic. Essa estrutura é amplamente utilizada devido à</p><p>sua versatilidade e eficiência em armazenar e manipular sequências de elementos.</p><p>Listas em C# são fortemente tipadas graças aos recursos genéricos da linguagem. O tipo T na</p><p>declaração List representa o tipo de elementos que a lista pode conter; isso significa que, se você</p><p>criar uma List, por exemplo, ela só poderá conter inteiros. Essa tipagem forte ajuda a evitar erros</p><p>em tempo de execução e melhora a legibilidade e a manutenção do código. Outro aspecto importante</p><p>das listas é a variedade de métodos disponíveis para manipular dados, incluindo adicionar (Add), remover</p><p>(Remove), encontrar (Find), ordenar (Sort) e muitos outros. Listas também suportam iteração, permitindo</p><p>que os desenvolvedores percorram seus elementos facilmente usando loops foreach, por exemplo.</p><p>É interessante discutir a decisão de usar um parâmetro numeroDeCasas na classe Tabuleiro, mesmo</p><p>quando o jogo tem número fixo de 60 casas, o que inicialmente pode parecer uma inclusão desnecessária.</p><p>No entanto, essa abordagem traz várias vantagens do ponto de vista do design de software e da</p><p>flexibilidade do código. Ao parametrizar o número de casas, o código fica mais flexível e adaptável a</p><p>mudanças. Se no futuro for decidido alterar o jogo para ter, digamos, 80 casas, essa mudança pode ser</p><p>feita facilmente, sem necessidade de alterar a lógica interna da classe Tabuleiro.</p><p>Essa abordagem segue o princípio de design de software conhecido como open/closed principle,</p><p>para o qual as classes devem estar abertas para extensão, mas fechadas para modificação. Além</p><p>disso, ao definir explicitamente o número de casas como parâmetro, o código se torna mais claro e</p><p>autoexplicativo. Outros desenvolvedores que trabalharem com ele poderão entender rapidamente que o</p><p>tamanho do tabuleiro é uma característica configurável. Por fim, ao permitir que o número de casas seja</p><p>definido como parâmetro, fica mais fácil testar a classe Tabuleiro; por exemplo, para testes unitários,</p><p>um tabuleiro menor pode ser criado para testar certas funcionalidades sem precisar percorrer todas</p><p>as 60 casas.</p><p>30</p><p>Unidade I</p><p>A função InicializarCasas (linha 17) é chamada para preencher a lista, onde cada casa é instanciada</p><p>com um tipo específico, a depender de sua posição no tabuleiro. Isso é feito por uma série de instruções</p><p>switch-case, que associam tipos específicos de casas a certas posições no tabuleiro. Por exemplo, casas</p><p>numeradas 5, 20, 35, 46 e 55 (linha 26 à 30) são definidas como casas de evento (linha 31). Note o</p><p>uso do método Add que citamos e observe também que, ao adicionar algumas casas (por exemplo,</p><p>na linha 56), a chamada ao construtor colocou um número como parâmetro. Esse número é a casa de</p><p>retorno. Além disso, o método ObterCasa (linha 73) da classe Tabuleiro é crucial. Ele recebe a posição</p><p>do jogador no tabuleiro e retorna a casa correspondente a essa posição. Essa função é essencial para</p><p>determinar eventos ou ações que ocorrem quando um jogador cai em determinada casa.</p><p>A instrução throw no método ObterCasa da classe Tabuleiro é um exemplo clássico de como a</p><p>programação defensiva é implementada em C#. A princípio, pode parecer estranho incluir uma checagem</p><p>para uma condição que, sob circunstâncias normais, nunca deveria ocorrer – um jogador saindo dos</p><p>limites do tabuleiro. No entanto, essa abordagem é justificada e benéfica em diversos aspectos.</p><p>Em primeiro lugar, a programação defensiva, que prepara o código para lidar com situações</p><p>inesperadas, é prática recomendada. No caso, embora a lógica sugira que os jogadores sempre estejam</p><p>dentro dos limites do tabuleiro, erros inesperados podem ocorrer. Pode haver bugs no código ou</p><p>interações imprevistas entre diferentes partes do jogo que, por alguma razão, movam alguém para uma</p><p>posição inválida. Ao usar throw para lançar uma exceção quando isso acontece, o código fornece um</p><p>meio de identificar rapidamente tais problemas.</p><p>Adicionalmente, ao lidar com uma exceção de maneira explícita, o jogo se torna mais robusto e</p><p>confiável. Isso é particularmente importante em ambientes onde a estabilidade é crucial. Se um jogador,</p><p>por acidente ou bug, for colocado fora dos limites do tabuleiro, essa exceção interromperá o fluxo</p><p>normal do programa e destacará o problema, permitindo que seja corrigido mais facilmente do que se</p><p>o programa continuasse a operar em estado inválido.</p><p>Além disso, throw é uma forma de comunicar claramente aos outros desenvolvedores sobre as</p><p>expectativas do código. Quando outro desenvolvedor lê o código da classe Tabuleiro, a presença</p><p>da exceção sinaliza que a posição de um jogador deve sempre estar dentro de um intervalo específico.</p><p>Isso aumenta a legibilidade e a manutenção do código, facilitando o entendimento e o trabalho com</p><p>ele no futuro.</p><p>Por fim, num jogo complexo, com várias lógicas e interações, não é possível prever todas as situações;</p><p>há sempre uma chance, mesmo que pequena, de um jogador acabar fora dos limites do tabuleiro</p><p>devido a uma sequência rara e não antecipada de eventos. A exceção serve como uma rede de segurança</p><p>para tais casos, garantindo que o jogo não perca sua lógica interna. Para ilustrar a importância dessa</p><p>rede, considere que há um problema comum em jogos conhecido como clipping (ou travamento de</p><p>objetos), em que personagens ou entidades ficam presas em objetos do ambiente, como paredes. Isso</p><p>geralmente se deve a falhas no sistema de colisão ou na física do jogo, e possivelmente as exceções não</p><p>foram adequadamente colocadas no código.</p><p>31</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>A seguir, alguns jogos famosos que contêm problemas do tipo:</p><p>• Franquia Grand theft auto (GTA): em vários jogos da série evidenciam-se personagens ou</p><p>veículos presos em edifícios ou outros objetos. Isso se deve muitas vezes à complexidade de seu</p><p>mundo aberto e à liberdade de ação oferecida.</p><p>• Minecraft: em algumas ocasiões, especialmente em versões mais antigas ou em servidores com</p><p>lag, os jogadores podem experimentar clipping através do terreno, que às vezes os deixa presos.</p><p>• World of Warcraft: como massive multiplayer online (MMO), teve sua parcela de clipping – os</p><p>jogadores ficavam presos em texturas ou terrenos do jogo. Isso geralmente ocorria em áreas</p><p>recém-introduzidas ou após grandes atualizações.</p><p>É importante entender que a classe Tabuleiro age como estrutura de dados que mapeia o estado</p><p>do tabuleiro do jogo. Ela não apenas armazena informações sobre cada casa, mas também fornece</p><p>métodos para interagir com o tabuleiro, como ObterCasa, essencial para avançar o jogo a cada turno. Em</p><p>termos técnicos, a classe Tabuleiro exemplifica como encapsular dados (no caso, o layout do tabuleiro) e</p><p>comportamento (como obter uma casa baseada na posição do jogador) de maneira que sejam fáceis de</p><p>gerenciar e manter. O encapsulamento, um dos princípios da POO, ajuda a manter o código organizado</p><p>e flexível, facilitando modificações futuras, como adição de novos tipos de casa ou alteração do layout</p><p>do tabuleiro.</p><p>Se já temos o tabuleiro,</p><p>vamos às cartas. Iniciaremos pela classe CartaDeEvento (figura 10),</p><p>responsável por introduzir eventos aleatórios que afetam o estado e as decisões dos jogadores. Ela</p><p>denota o dinamismo e a imprevisibilidade inerentes ao jogo.</p><p>1. class CartaDeEvento</p><p>2. {</p><p>3. public string Descricao { get; private set; }</p><p>4. public Action Efeito { get; private set; }</p><p>5.</p><p>6. public CartaDeEvento(string descricao, Action efeito)</p><p>7. {</p><p>8. Descricao = descricao;</p><p>9. Efeito = efeito;</p><p>10. }</p><p>11. }</p><p>Figura 10 – Classe CartaDeEvento</p><p>CartaDeEvento é uma classe com duas propriedades principais: Descricao e Efeito.</p><p>32</p><p>Unidade I</p><p>Quadro 4 – Cartas e suas descrições e efeitos no jogador</p><p>Carta Descrição/efeito</p><p>1 Desafio do cardeal: você foi pego em uma emboscada pelos guardas de Richelieu. Perca uma rodada enquanto escapa</p><p>2 O olhar de Constance: Constance Bonacieux, por quem você se apaixona, lhe deu um olhar encorajador. Avance duas</p><p>casas com a alegria do amor</p><p>3 Intriga da rainha: a rainha Ana da Áustria confiou a você uma missão secreta para recuperar diamantes. Avance três casas</p><p>e ganhe uma ficha de honra</p><p>4 Duelo com mosqueteiro: um desentendimento o levou a um duelo com um de seus camaradas. Volte uma casa e perca</p><p>uma ficha de honra</p><p>5 Trama de Milady: a sedutora e astuta Milady de Winter tramou contra você. Volte duas casas enquanto desvenda a armadilha</p><p>6 Auxílio de Buckingham: o duque de Buckingham, aliado da rainha, oferece ajuda em sua jornada. Avance duas casas com</p><p>o apoio dele</p><p>7 Festa em Paris: há uma festa extravagante em Paris, e você é o convidado de honra. Perca uma rodada enquanto celebra</p><p>8 Missão secreta: uma carta selada da rainha lhe é entregue, pedindo uma missão sigilosa. Avance três casas com sua nova</p><p>responsabilidade</p><p>9 Emboscada na estrada: enquanto viaja, você é emboscado por inimigos desconhecidos. Volte duas casas enquanto escapa</p><p>do perigo</p><p>10 Defesa do rei: Luís XIII está sob ameaça e você o defendeu bravamente. Avance duas casas e ganhe duas fichas de honra</p><p>11 Convite do cardeal: Richelieu, num movimento inesperado, convida você para um banquete. Perca uma rodada devido à</p><p>cautela, mas ganhe uma ficha de fortuna pelas oportunidades que surgem</p><p>12 Refúgio num mosteiro: escapando de perseguidores, você se refugia num mosteiro. Perca uma rodada, mas ganhe uma</p><p>ficha de honra pela proteção oferecida</p><p>13 Treinamento intensivo: você treina duro com os companheiros mosqueteiros. Avance uma casa e ganhe uma ficha de fortuna</p><p>14 Intriga na corte: rumores sobre você circulam na corte. Volte uma casa, mas ganhe uma ficha de fortuna pelo aprendizado</p><p>com a situação</p><p>15 Mensagem de socorro: Constance lhe envia uma mensagem urgente. Avance duas casas enquanto se apressa em ajudá-la</p><p>e ganhe uma ficha de honra</p><p>16 Dádiva do duque: o duque de Buckingham presenteia você com ricas vestimentas. Ganhe duas fichas de fortuna</p><p>17 Perseguição nas ruas: envolvido numa perseguição pelas ruas de Paris, você escapa por pouco. Perca uma ficha de honra,</p><p>mas ganhe uma de fortuna pela experiência</p><p>18 Rivalidade na guarda: um rival na Guarda do Rei desafia você. Volte duas casas enquanto lida com a situação, mas ganhe</p><p>uma ficha de fortuna pelo aprendizado</p><p>19 Conspiração desvendada: você descobre uma conspiração contra os mosqueteiros. Avance duas casas e ganhe uma ficha</p><p>de honra</p><p>20 Perdido numa floresta: durante uma missão, você se perde em uma floresta densa. Volte duas casas enquanto encontra</p><p>seu caminho</p><p>21 Presente da rainha: a rainha Ana da Áustria lhe dá um presente por sua lealdade. Ganhe três fichas de fortuna</p><p>A propriedade Descricao (linha 3) é uma string que fornece uma narrativa ou contexto para o</p><p>evento, enquanto Efeito (linha 4) é um delegado do tipo Action, particularmente interessante</p><p>pois encapsula a lógica que será aplicada ao jogador quando a carta for usada. Essencialmente, efeito</p><p>é uma função que recebe um objeto Jogador e executa ações que modificam seu estado, como alterar</p><p>posição, fichas de fortuna ou honra, entre outras.</p><p>A flexibilidade da classe CartaDeEvento reside na sua capacidade de encapsular diferentes tipos</p><p>de lógica dentro de Efeito, tornando cada carta única em termos de impacto no jogo. Isso é feito por</p><p>expressões lambda passadas no construtor da CartaDeEvento, permitindo uma variedade de efeitos</p><p>possíveis, desde mover o jogador para frente ou para trás no tabuleiro, alterar suas fichas de fortuna</p><p>33</p><p>PROGRAMAÇÃO ORIENTADA A OBJETOS II</p><p>ou honra, até forçar o jogador a perder um turno. Veremos essas expressões quando analisarmos a</p><p>classe Baralho, que instancia os objetos CartaDeEvento e, portanto, aciona o construtor passando cada</p><p>expressão lambda desejada como parâmetro.</p><p>Delegado é um tipo que representa referências a métodos com uma lista de parâmetros e um</p><p>tipo de retorno específicos. Pode ser pensado como um tipo de ponteiro de função, mas com segurança</p><p>de tipo e mais integrado ao sistema de tipos do C#. Delegados são usados para passar métodos como</p><p>argumentos para outros métodos, permitindo alta flexibilidade e reusabilidade do código.</p><p>Action é um tipo de delegado genérico fornecido pelo framework .NET que pode encapsular um</p><p>método que recebe um único parâmetro do tipo T e não retorna um valor (ou seja, seu tipo de retorno é</p><p>void). No seu caso, Action representa um delegado que pode apontar para qualquer método</p><p>que aceite um único argumento do tipo Jogador e não retorne nada.</p><p>Dentro da classe CartaDeEvento, a propriedade Efeito é do tipo Action. Isso significa que</p><p>ela pode armazenar uma referência a qualquer método que corresponda à assinatura especificada:</p><p>um método que leva um Jogador como argumento e não retorna um valor. Por exemplo, pode ser um</p><p>método que modifique a posição do jogador no tabuleiro, altere sua quantidade de fichas de honra ou</p><p>fortuna, ou qualquer outra ação que afete o estado do jogador.</p><p>Observação</p><p>Além do Action, projetado para métodos que não retornam valor,</p><p>existem os delegados Func e Predicate, cada um com propósitos</p><p>específicos: Func – delegado genérico, representa um método</p><p>que recebe um ou mais parâmetros e retorna um valor. O último tipo</p><p>genérico especifica o tipo de retorno do método. Por exemplo, Func representa um método que recebe dois inteiros (int) como</p><p>parâmetros e retorna um booleano (bool). A classe Func pode suportar de</p><p>0 a 16 parâmetros de entrada; Predicate – especialização do Func, o Predicate é um delegado usado para expressar um teste que</p><p>retorna um valor booleano. Ele recebe um único parâmetro do tipo T e</p><p>então retorna um bool. É frequentemente usado em métodos de busca e</p><p>filtragem, como os métodos Find ou Exists em listas, em que é necessário</p><p>testar cada elemento para uma condição específica.</p><p>Esses delegados são parte do namespace System, e sua disponibilidade</p><p>e uso generalizado na plataforma .NET simplificam muito a passagem de</p><p>métodos como argumentos, tornando o código mais flexível e reutilizável.</p><p>São essenciais para programação funcional em C# e são amplamente</p><p>usados em LINQ e para manipular eventos e callbacks. Por exemplo:</p><p>Action pode representar um método que recebe um inteiro e não</p><p>retorna nada; Func representa um método que recebe um</p><p>34</p><p>Unidade I</p><p>inteiro e retorna um double; Predicate pode ser um método que</p><p>recebe uma string e retorna um booleano, indicando se a string satisfaz</p><p>uma condição particular.</p><p>Esses delegados tornam o código mais limpo e expressivo, além de</p><p>facilitar operações como manipulação de coleções, programação assíncrona</p><p>e construção de pipelines de processamento de dados.</p><p>O uso de Action como tipo para a propriedade Efeito oferece várias vantagens. Em primeiro</p><p>lugar, permite que diferentes ações sejam atribuídas a diferentes cartas de evento; e cada carta de evento</p><p>pode ter efeito único sem precisar criar uma nova classe para cada tipo de ação. Em segundo lugar, permite</p><p>encapsulamento e clareza,</p>