Prévia do material em texto
162
Unidade III
Unidade III
5 ARQUITETURA DE SOFTWARE E PADRÕES DE DESIGN
Arquitetura de software desempenha um papel crucial no desenvolvimento de sistemas eficientes
e robustos, sendo comparável à planta de uma construção na engenharia civil, pois fornece um plano
detalhado que orienta a construção do software. No caso de desenvolvedores que utilizam C#, é
fundamental compreender e aplicar padrões de design apropriados para criar aplicações ao mesmo
tempo escaláveis, manuteníveis e eficientes.
Padrões de design de software, em sua essência, são soluções padronizadas para problemas
comuns nessa área; eles representam as melhores práticas desenvolvidas e refinadas ao longo de
anos de experiência coletiva. Para desenvolvedores C#, usá‑los não é apenas uma questão de seguir a
tradição, mas sim uma estratégia eficaz para lidar com as complexidades inerentes ao desenvolvimento
de software moderno.
Um aspecto crucial dos padrões de design é a maneira como eles reutilizam o código e a modularidade.
Por exemplo, o padrão model‑view‑controller (MVC) é amplamente usado em aplicações para separar
a lógica de negócios da interface do usuário, o que não apenas simplifica o desenvolvimento e a
manutenção, mas também facilita a testabilidade e a escalabilidade das aplicações. Em ambientes de
desenvolvimento C#, onde eficiência e desempenho são essenciais, tais práticas são inestimáveis.
No mais, padrões de design ajudam na comunicação entre desenvolvedores. Ao utilizar uma
terminologia comum, equipes podem discutir soluções de forma mais eficaz, reduzindo mal‑entendidos
e aumentando a eficiência do desenvolvimento. Todavia, é importante os desenvolvedores lembrarem
que padrões de design não são soluções universais; cada padrão tem um contexto específico no qual
é mais eficaz, e seu uso inadequado pode levar a uma complexidade desnecessária ou até mesmo a
problemas de desempenho. Portanto é crucial não apenas entender como implementar um padrão de
design, mas também quando e por que usá‑lo.
Em suma, arquitetura de software e padrões de design são fundamentais para o desenvolvimento
eficiente de software em C#, porque oferecem estruturas comprovadas que ajudam a organizar e
reutilizar o código, a melhorar a comunicação entre membros da equipe e, em última análise, a entregar
aplicações robustas e eficazes. Contudo, é essencial que os desenvolvedores apliquem esses padrões com
discernimento, adaptando‑os às necessidades de cada projeto para alcançar os melhores resultados.
Neste tópico veremos com mais detalhes essa dinâmica.
163
PROGRAMAÇÃO ORIENTADA A OBJETOS II
5.1 Desenvolvimento em camadas e MVC
No desenvolvimento de software, a arquitetura em camadas e o MVC são abordagens que ajudam a
organizar e estruturar aplicações de maneira eficiente e sustentável. Ambos os conceitos visam separar
as responsabilidades dentro de uma aplicação para facilitar a manutenção, melhorar a reusabilidade e
garantir a escalabilidade.
Na arquitetura em camadas, a aplicação é dividida em diferentes níveis, cada um responsável por
um aspecto específico do processo. Geralmente essas camadas incluem interface do usuário, lógica
de negócios e acesso a dados. A camada da interface do usuário é a frente (frontend) da aplicação,
interagindo diretamente com o usuário e apresentando dados de forma compreensível. A lógica de
negócios, por sua vez, lida com o processamento de informações, regras de negócio e decisões.
Por fim, a camada de acesso a dados é responsável pela comunicação com os sistemas de
armazenamento (como bancos de dados), garantindo que sejam guardados e recuperados de forma
eficaz. Essa separação clara permite que cada camada seja desenvolvida, testada e mantida de
forma independente, o que aumenta a eficiência e reduz a complexidade geral do sistema. Além disso,
tal estrutura oferece flexibilidade para alterar ou atualizar uma camada específica sem afetar as outras,
facilitando a adaptação a novas tecnologias ou requisitos de negócios.
Paralelamente, o padrão MVC, especialmente popular em ambientes de desenvolvimento como
C#, oferece um framework robusto para separar responsabilidades. No MVC, a aplicação é dividida em
três componentes principais: modelo, visão e controlador. O modelo representa a lógica de dados
e as regras de negócio, mantendo‑se independente da interface do usuário; visão é a camada que
apresenta os dados ao usuário, sendo uma representação visual das informações contidas no modelo;
já o controlador atua como intermediário entre o modelo e a visão, manipulando os dados de um
e apresentando‑os à outra. Ele responde às interações do usuário, como cliques ou entradas de dados, e
realiza as ações correspondentes.
MVC é particularmente eficaz para separar responsabilidades, facilitando a manutenção e a expansão
das aplicações. Ao isolar a lógica de negócios e a interface do usuário, permite aos desenvolvedores
alterar a aparência da aplicação sem interferir na lógica por trás dela. Essa separação facilita o teste
em diferentes componentes de forma independente, contribuindo para a qualidade e confiabilidade
do software.
Analisando, por exemplo, o código‑fonte do jogo em C# apresentado no tópico 1.1, é possível tecer
considerações sobre a estrutura e a aderência aos princípios de desenvolvimento em camadas e ao
padrão MVC. Inicialmente, é notável que o código mistura lógica de negócios com interação do usuário,
o que pode ser um ponto de atenção. Em um design ideal seguindo o MVC, a lógica de negócios (modelo)
deveria ser claramente separada da lógica de apresentação (visão) e da lógica de controle (controlador).
No código fornecido, vemos elementos do modelo, como classes que representam jogadores,
casas, baralhos e lógica do jogo, misturados com operações de entrada/saída (E/S), típicas da visão,
como chamadas para Console.WriteLine e Console.ReadKey. Essa mistura dificulta a manutenção, a
164
Unidade III
testabilidade e a reutilização do código (o que deve ter sido observado na tentativa de criar a versão do
jogo para Windows Forms).
Além disso, a classe Jogo assumiu múltiplas responsabilidades, incluindo inicializar o jogo,
gerenciar o fluxo de jogo e executar ações específicas dele. Idealmente, cada classe deve ter uma única
responsabilidade, de acordo com o princípio de responsabilidade única – um dos cinco princípios
SOLID de design de software orientado a objetos. Isso melhora a modularidade e a flexibilidade do código;
por exemplo, a lógica de inicialização e de execução do jogo poderia ser separada em diferentes classes.
Saiba mais
O princípio SOLID, sigla para cinco princípios de design de software
em POO, foi popularizado por Robert C. Martin em seus diversos trabalhos.
Embora não exista um único livro de referência exclusivamente dedicado
ao SOLID, as ideias por trás desses princípios são discutidas em detalhe em
várias de suas publicações.
O livro mais influente de Robert C. Martin que aborda os princípios
SOLID é este:
MARTIN, R. C. Código limpo: habilidades práticas do Agile software. São
Paulo: Alta Books, 2009.
Nosso livro‑texto, embora não seja inteiramente dedicado ao SOLID,
aborda muitos de seus princípios subjacentes e é considerado um texto
essencial na comunidade de desenvolvimento de software.
Outro livro relevante da área é:
MARTIN, R. C.; MARTIN, M. Agile principles, patterns, and practices in
C#. Londres: Prentice Hall, 2006.
Apesar de focar C#, oferece uma discussão abrangente sobre princípios
ágeis de desenvolvimento de software, incluindo os princípios SOLID.
Classes como Jogador, Tabuleiro, Baralho e Casa servem como modelo no padrão MVC, representando
os dados e a lógica do negócio. Essas classes estão bem definidas e parecem encapsular adequadamente
propriedades e comportamentos específicos de suas respectivas entidades. Porém não há uma separação
clara da visão no código; as interações com o usuário estão embutidas diretamente nas classes de
negócio, em especial em Jogo.Normais
4. for (int i = 0; icom bases de dados em aplicações desenvolvidas usando o .NET
Framework, é fundamental compreender dois aspectos‑chave: strings de conexão e provedores de
dados. Aquelas são essencialmente uma coleção de parâmetros utilizados para definir a comunicação
entre uma aplicação e um banco de dados, que podem incluir o nome do servidor, o nome do banco de
dados, credenciais de usuário e senha e outras configurações relevantes. Uma string de conexão bem
configurada é crucial para garantir uma conexão segura e eficiente com o banco.
Entre os provedores de dados mais utilizados no .NET Framework para gerenciar essas conexões,
destaca‑se o SQLClient, projetado especificamente para interagir com bases de dados SQL
Server. É amplamente reconhecido por sua alta performance e integração estreita com o SQL Server,
proporcionando um meio direto e eficaz de executar comandos SQL, ler dados e manipular transações.
Observação
SQL Server é um sistema de gerenciamento de banco de dados relacional
(SGBD) desenvolvido pela Microsoft projetado para atender a uma ampla
gama de necessidades de dados, incluindo aplicativos de comércio eletrônico,
armazenamento de dados e aplicativos de missão crítica, com suporte para
transações ACID (atomicidade, consistência, isolamento, durabilidade).
É uma escolha popular de uso com a linguagem de programação C# (.NET)
devido à sua integração nativa e ao suporte extensivo da Microsoft para
ambas as tecnologias.
As edições do SQL Server variam desde edições de uso geral até
versões especializadas para diferentes cargas de trabalho e requisitos de
processamento de dados. As principais edições incluem:
207
PROGRAMAÇÃO ORIENTADA A OBJETOS II
Express: edição gratuita, destinada a desenvolver pequenas aplicações
desktop e servidores web. Tem limitações de tamanho de banco de dados e
recursos; Standard: oferece suporte a funcionalidades de gerenciamento
de dados e business intelligence para aplicativos departamentais e
pequenas empresas. É uma versão paga; Enterprise: versão mais completa,
oferece suporte para grandes volumes de dados, processamento analítico
avançado e recursos de segurança e business intelligence. Volta‑se para
organizações grandes e é uma versão paga; Developer: contém todas
as funcionalidades da versão Enterprise, mas é licenciada para uso
apenas como ambiente de desenvolvimento e teste, não para produção.
Gratuita para desenvolvedores; Web: fornece suporte a aplicações web de
pequena e média escala. Disponível a preço reduzido para provedores de
serviços de hospedagem.
Saiba mais
Para entender em detalhe cada uma das versões do SQL Server e obter
uma comparação delas ao longo do tempo, leia:
ATUALIZAÇÕES mais recentes e histórico de versões para SQL Server.
Microsoft, 14 mar. 2024. Disponível em: https://tinyurl.com/7twb67jz.
Acesso em: 8 abr. 2024.
COMPARAÇÃO do SQL Server 2022. Microsoft, [s.d.]. Disponível em:
https://tinyurl.com/2s45t74e. Acesso em: 8 abr. 2024.
EDIÇÕES e recursos com suporte do SQL Server 2022. Microsoft, 22 dez.
2023. Disponível em: https://tinyurl.com/jpzdkeu9. Acesso em: 8 abr. 2024.
Um dos principais benefícios do SQLClient é sua alta eficiência e desempenho. Por ser uma
solução dedicada ao SQL Server, é capaz de aproveitar características específicas desse sistema de
gerenciamento de banco de dados, oferecendo uma comunicação mais rápida e eficaz em comparação
com provedores de dados mais genéricos. Isso se traduz em tempos de resposta mais rápidos e
menor consumo de recursos, aspectos críticos para aplicações de larga escala e alta demanda.
Além da performance, o SQLClient se destaca por sua segurança e estabilidade, pois abarca uma gama
de recursos de segurança do SQL Server, incluindo autenticação integrada e encriptação de dados. Isso
permite que os desenvolvedores construam aplicações que não apenas se comunicam eficientemente
com o banco de dados, mas também protegem os dados e as transações de possíveis vulnerabilidades
de segurança.
208
Unidade III
Outro aspecto importante do SQLClient é sua forte tipagem. O suporte a tipos de dados específicos
do SQL Server reduz a ocorrência de erros de tipo em tempo de execução e melhora a legibilidade e
manutenção do código. Essa abordagem assegura que os dados sejam manuseados de forma mais
segura e eficiente, minimizando problemas comuns de conversão e formatação. O SQLClient também
facilita a execução de procedimentos armazenados e transações, oferecendo um controle granular
sobre o comportamento das operações de banco de dados. Essa capacidade é útil em cenários complexos
de negócios, nos quais operações múltiplas precisam ser executadas de forma atômica e consistente.
Observação
O termo atômica, nesse contexto, refere‑se a uma propriedade
fundamental dos sistemas de gerenciamento de banco de dados,
especialmente no que tange a executar transações. Uma operação atômica
(ou transação atômica) segue o princípio de “tudo ou nada”; ou seja, dentro
de uma transação múltipla, todas as operações individuais devem ser
completadas com sucesso para a transação como um todo ser considerada
bem‑sucedida. Se qualquer uma das operações falhar, toda a transação
deve ser revertida, ou seja, nenhuma das operações parciais deve ter efeito
permanente no banco de dados.
Dessa forma, atômica destaca a capacidade do SQLClient de manejar
transações complexas de maneira segura e confiável, garantindo que
as operações sejam completamente aplicadas ou completamente
desfeitas, sem estados intermediários que possam levar a inconsistências
no banco de dados.
Além disso, atomicidade é uma das quatro propriedades principais das
transações em sistemas de banco de dados, conhecidas pelo acrônimo ACID.
Na prática, o SQLClient se utiliza de classes como SqlConnection, SqlCommand e SqlDataReader.
A classe SqlConnection estabelece a conexão com o banco de dados, enquanto SqlCommand permite
executar consultas SQL e procedimentos armazenados. Por sua vez, o SqlDataReader é uma ferramenta
eficiente para ler dados de forma rápida e sequencial.
Neste livro‑texto usaremos o SQL Server Express LocalDB, uma edição leve do SQL Server Express
projetada para ser fácil de instalar e que não requer um servidor de banco de dados dedicado.
O LocalDB é iniciado sob demanda e roda em modo de usuário, tornando‑o uma boa escolha para
desenvolvedores de aplicações desktop e estudantes que precisam de um SGBD simples e sem grande
esforço de configuração.
O LocalDB tem limitações, como o tamanho máximo do banco de dados (10 GB por banco) e recursos
reduzidos em comparação com as edições mais completas do SQL Server. Também é gratuito e oferece a
209
PROGRAMAÇÃO ORIENTADA A OBJETOS II
mesma linguagem T‑SQL e tipos de dados do SQL Server; ou seja, os desenvolvedores podem facilmente
migrar aplicativos do LocalDB para uma instância completa do SQL Server, se necessário.
Vamos usar o SQL Server Express LocalDB dentro do Visual Studio para criar uma tabela chamada
Jogador, que armazenará os dados da posição e fichas (fortuna e honra) de cada jogador. Para criar a
tabela no LocalDB, são necessários alguns passos:
1) Abra o Visual Studio Community.
2) Conecte‑se ao LocalDB (vide figura 76):
a) No Visual Studio, vá até o menu Visualizar e escolha SQL Server Object Explorer ou
Explorador de Objetos do SQL Server.
b) Clique com o botão direito em SQL Server e selecione Adicionar SQL Server.
c) Na caixa de diálogo de conexão, selecione ou digite “(localdb)\MSSQLLocalDB” como
nome do servidor. Use autenticação do Windows e clique em Conectar.
3) Crie uma nova base de dados (vide figura 77):
a) Para criar a tabela Jogador, que armazenará as informações dele, clique com o botão
direito em Bancos de Dados e selecione Adicionar Novo Banco de Dados.
b) Dê um nome ao seu banco de dados e clique em Ok.
4) Crie a tabela (vide figura 78):
a) Expanda o banco de dados onde você deseja criar a tabela.
b) Clique com o botão direito em Tabelas e escolha AdicionarNova Tabela.
c) Na janela do designer de tabelas, você pode começar a definir as colunas da sua tabela.
Insira‑as com os nomes e tipos de dados desejados. No exemplo:
• Jogador (definimos como NCHAR(10)).
• Posicao (definimos como NCHAR(2)).
• Fortuna (definimos como NCHAR(2)).
• Honra (definimos como NCHAR(2)).
210
Unidade III
5) Defina a chave primária (no exemplo usamos o campo Jogador como chave).
6) Salve a tabela:
a) Definidas as colunas, clique com o botão direito na aba da tabela e escolha Salvar Tabela.
Dê um nome para ela (exemplo: Jogadores).
7) Crie a tabela no banco de dados:
a) Após salvar, o Visual Studio vai gerar um script SQL para criar a tabela. Você pode
executá‑lo diretamente no Visual Studio para criá‑la no seu banco de dados.
Figura 76 – Conectando‑se no SQL Server
Figura 77 – Criando a tabela Jogador
211
PROGRAMAÇÃO ORIENTADA A OBJETOS II
Figura 78 – Definindo as colunas da tabela Jogador
Criada a tabela e configurado o banco de dados, podemos fazer a interação do código‑fonte C# com
essa base de dados. Na linha 2 da figura 79 o código importa um namespace necessário para a aplicação
funcionar. Além disso, o System.Data.SqlClient é específico para operações de banco de dados com SQL
Server; a linha 10, no método Main, declara uma string de conexão connectionString, que contém as
informações necessárias para se conectar ao banco de dados SQL Server local. Essa string inclui o
nome da fonte de dados (Data Source), o nome do banco de dados (Initial Catalog) e o tipo de segurança
utilizada (Integrated Security), que neste caso usa a autenticação do Windows para se conectar ao
banco de dados.
Definida a connectionString, o método Main chama repetidamente o método InsertJogador,
passando a connectionString e os dados de cada jogador (nome, posição, fortuna, honra) como
argumentos. Cada chamada ao método InsertJogador resulta na inserção de um jogador na tabela
Jogador do banco de dados.
1. using System;
2. using System.Data.SqlClient;
3.
4. namespace ConsoleApp4
5. {
6. internal class Program
7. {
8. static void Main(string[] args)
9. {
10. string connectionString = @”Data
Source=(localdb)\MSSQLLocalDB;Initial Catalog=Jogador;Integrated
Security=True”;
212
Unidade III
11.
12. // Inserir cada jogador
13. InsertJogador(connectionString, “Athos”, “01”, “05”, “05”);
14. InsertJogador(connectionString, “Pothos”, “01”, “05”, “05”);
15. InsertJogador(connectionString, “Aramis”, “01”, “05”, “05”);
16. InsertJogador(connectionString, “Dartagnan”, “01”, “05”, “05”);
17. Console.ReadLine();
18. }
19. static void InsertJogador(string connectionString, string nome,
string posicao, string fortuna, string honra)
20. {
21. string query = “INSERT INTO Jogador (Jogador, Posicao, Fortuna,
Honra) VALUES (@Jogador, @Posicao, @Fortuna, @Honra)”;
22. using (SqlConnection connection = new
SqlConnection(connectionString))
23. {
24. SqlCommand command = new SqlCommand(query, connection);
25. command.Parameters.AddWithValue(“@Jogador”, nome);
26. command.Parameters.AddWithValue(“@Posicao”, posicao);
27. command.Parameters.AddWithValue(“@Fortuna”, fortuna);
28. command.Parameters.AddWithValue(“@Honra”, honra);
29.
30. connection.Open();
31. int result = command.ExecuteNonQuery();
32. if (result > 0)
33. Console.WriteLine($”Inserção de {nome} realizada com
sucesso!”);
34. else
35. Console.WriteLine(“Inserção falhou.”);
36. }
37. }
38. }
39. }
Figura 79 – String de conexão e inserção de informações na tabela Jogador
213
PROGRAMAÇÃO ORIENTADA A OBJETOS II
O nome dos jogadores (Athos, Pothos, Aramis, Dartagnan) e seus respectivos valores para posicao,
fortuna e honra estão fixos e predefinidos no exemplo. O método InsertJogador que criamos na linha 19
executa a inserção no banco de dados e recebe os dados do jogador como parâmetro, constrói uma
consulta SQL (linha 21) de inserção utilizando placeholders para os valores (evitando assim SQL
Injection) e executa essa consulta no banco. Note que é necessário conhecer a linguagem SQL para
utilizá‑la no C# e que, obviamente, tivemos que criar a base de dados no SGBD antes de usá‑la.
Observação
A segurança nas strings de conexão é um aspecto crítico, especialmente
em ambientes empresariais onde a proteção de dados é primordial.
É importante assegurar que as strings de conexão não sejam expostas nem
armazenadas de maneira insegura, pois contêm informações sensíveis, como
nomes de usuário e senhas. Além disso, é vital se atentar às vulnerabilidades
de injeção de SQL; para mitigar esse risco, é recomendável usar parâmetros
nas consultas SQL e evitar construir strings de consulta SQL dinamicamente
com entrada do usuário – práticas que o SQLClient e outros provedores de
dados do .NET suportam de maneira robusta.
SQL Injection é um tipo de ataque cibernético em que o invasor insere
ou “injeta” um código SQL malicioso dentro de uma consulta (query)
que será executada pelo banco de dados, o que pode resultar em acesso
não autorizado, manipulação ou destruição de dados. Para prevenir tais
vulnerabilidades, é crucial adotar técnicas que impeçam a execução de
código SQL indesejado.
No contexto do código apresentado, previne‑se SQL Injection com
parâmetros na construção da query SQL. Em vez de concatenar diretamente
os valores de entrada na string de consulta SQL (como nome, posição,
fortuna e honra dos jogadores), o método InsertJogador utiliza placeholders
(@Jogador, @Posicao, @Fortuna, @Honra) na query. Esses placeholders são
substituídos pelos valores reais através do método AddWithValue (linhas 25
a 28) do objeto SqlCommand. Esse método vincula os valores de entrada
aos parâmetros de forma segura, garantindo que eles sejam tratados como
dados, e não como parte do código SQL.
Na linha 22, using é uma construção da linguagem C# que fornece uma maneira conveniente
e segura de liberar recursos adquiridos por um objeto quando não é mais necessário. O bloco using
assegura que o método Dispose do objeto – que é parte da interface IDisposable – seja chamado
automaticamente ao final de sua execução. Para objetos que representam conexões com banco de
dados (como SqlConnection), o método Dispose chama internamente Close para fechar a conexão com
o banco de dados se ainda estiver aberta.
214
Unidade III
A conexão com o banco de dados é estabelecida usando a connectionString fornecida, e um
objeto SqlCommand (linha 24) prepara e executa a instrução SQL. Os parâmetros da query são
preenchidos com os valores recebidos pelo método InsertJogador através do método AddWithValue.
Ao abrir a conexão com connection.Open (linha 30), a query é executada chamando command.
ExecuteNonQuery (linha 31), que retorna o número de linhas afetadas pela operação. Se uma ou mais
linhas forem afetadas, isso significa que a inserção foi bem‑sucedida, e uma mensagem indicando
sucesso é exibida no console; caso contrário, uma mensagem de falha é mostrada.
Observação
É comum usar “result > 0” na verificação do resultado de command.
ExecuteNonQuery para determinar o sucesso de operações de inserção,
atualização ou exclusão em um banco de dados. Essa verificação é útil para
fornecer feedback imediato ao usuário ou ao sistema sobre o sucesso da
operação; no nosso exemplo, por ser simples, esse condicional é descartável.
Uma operação de inserção pode tecnicamente retornar zero linhas afetadas
em casos muito específicos, como quando se usa um INSERT com uma
cláusula WHERE não atendida (em alguns sistemas de gerenciamento de
banco de dados que suportam essa sintaxe) ou quando se tenta inserir em
uma tabela que automaticamente rejeita certos dados devido a gatilhos ou
regras de validação definidas que não são atendidas.
Em resumo, o código‑fonte da figura 79 cria uma aplicação console simples em C# que interage
com um banco SQL Server para inserir dados de maneirasegura e eficiente. O código‑fonte da figura 80
adiciona uma nova funcionalidade para ler e exibir informações de jogadores do SQL Server.
Como vimos, o método Main contém uma string connectionString que armazena as informações
necessárias para se conectar ao banco de dados local SQL Server, incluindo a fonte de dados, o catálogo
inicial (nome do banco) e o tipo de segurança utilizado. O método ReadJogadores (definido na linha 18)
é responsável por ler e exibir as informações dos jogadores armazenados na tabela Jogador – método
que recebe a connectionString (linha 15) como argumento e a utiliza para estabelecer uma conexão
com o banco de dados através de um objeto SqlConnection (linha 22).
Estabelecida a conexão, um objeto SqlCommand é criado (linha 24) para executar a consulta SQL
que seleciona as colunas Jogador, Posicao, Fortuna e Honra de todos os registros na tabela Jogador.
Aberta a conexão com o banco de dados (linha 25), o método ExecuteReader do objeto SqlCommand
é chamado (linha 26) para executar a consulta e retornar um objeto SqlDataReader que permite ler os
dados retornados pela consulta linha por linha em um loop while, no qual o método Read avança para
o próximo registro. Para cada registro, os valores das colunas são acessados pelo nome e exibidos no
console, fornecendo uma visão detalhada dos dados dos jogadores armazenados no banco.
215
PROGRAMAÇÃO ORIENTADA A OBJETOS II
1. using System;
2. using System.Data.SqlClient;
3.
4. namespace ConsoleApp4
5. {
6. internal class Program
7. {
8. static void Main(string[] args)
9. {
10. string connectionString = @”Data
Source=(localdb)\MSSQLLocalDB;Initial Catalog=Jogador;Integrated
Security=True”;
11.
12. // Inserir cada jogador como fizemos anteriormente
13.
14. // Ler todos os jogadores
15. ReadJogadores(connectionString);
16. }
17. // Metodo InsertJogador aqui
18. static void ReadJogadores(string connectionString)
19. {
20. string query = “SELECT Jogador, Posicao, Fortuna, Honra FROM
Jogador”;
21.
22. using (SqlConnection connection = new
SqlConnection(connectionString))
23. {
24. SqlCommand command = new SqlCommand(query, connection);
25. connection.Open();
26. using (SqlDataReader reader = command.ExecuteReader())
216
Unidade III
27. {
28. while (reader.Read())
29. {
30. Console.WriteLine($”Jogador: {reader[“Jogador”]},
Posição: {reader[“Posicao”]}, Fortuna: {reader[“Fortuna”]}, Honra:
{reader[“Honra”]}”);
31. }
32. Console.ReadLine();
33. }
34. }
35. }
36. }
37. }
Figura 80 – Lendo dados da tabela Jogador
Finalmente, a conexão com o banco de dados é fechada automaticamente ao final do bloco using,
garantindo que os recursos sejam liberados de maneira adequada. A chamada a Console.ReadLine ao
final do método ReadJogadores pausa a execução do programa e permite que o usuário visualize os
dados exibidos no console antes de o programa terminar.
Lembrete
Antes de compilar esse código, não esqueça de limpar a base de dados
Jogador, pois a execução do código da figura 79 criou os quatro jogadores
na tabela de mesmo nome, tendo o nome do jogador como chave primária.
Executado o código da figura 80 (colocando o método InsertJogador),
os jogadores serão reinseridos na base. Observe também que é necessária
alguma proficiência na linguagem SQL para construir as consultas.
Para interagir no C# com um banco de dados SQLServer, vimos que é preciso:
1) que o servidor já esteja configurado e que a(s) tabela(s) já tenha(m) sido criada(s) na base;
2) colocar System.Data.SqlClient nas diretivas using;
3) criar a string de conexão com os parâmetros adequados;
217
PROGRAMAÇÃO ORIENTADA A OBJETOS II
4) definir uma consulta em uma string usando a linguagem SQL;
5) criar o objeto de conexão (SqlConnection) passando a string de conexão. É recomendável utilizar
using para a conexão se encerrar automaticamente ao final, otimizando recursos;
6) criar o objeto de execução (SqlCommand) passando a string da consulta que desejamos executar
e o objeto de conexão (SqlConnection);
7) abrir a conexão com o banco usando o método Open do objeto SqlConnection;
8) executar o SqlComand com o método adequado ao tipo de consulta. No nosso exemplo, para
inserir dados do jogador (INSERT INTO Jogador), usamos o método ExecuteNonQuery, e para ler
dados (SELECT... FROM Jogador), usamos o método ExecuteReader.
Com esses exemplos, percebemos que no desenvolvimento de aplicações .NET, que interagem com
bancos de dados SQL Server, as classes SqlConnection, SqlCommand e SqlDataReader desempenham
papéis cruciais, facilitando uma ampla gama de operações de banco de dados, desde a conexão até a
execução de comandos e a leitura de resultados.
O quadro 20 apresenta alguns detalhes dessas classes. Ao fornecer métodos específicos para
diferentes tarefas, elas ajudam a abstrair a complexidade do gerenciamento de conexões de banco
de dados, execução de consultas e manipulação de resultados, permitindo que os desenvolvedores se
concentrem na lógica de negócios da aplicação.
Quadro 20 – Classes e métodos que formam a espinha dorsal
de operações de banco de dados em aplicações .NET
Classe Método Descrição do método
SqlConnection Open()
Abre uma conexão com o banco de dados especificado na string de conexão do
objeto SqlConnection. É necessário chamar esse método antes de executar uma
consulta ou comando no banco
SqlCommand
ExecuteNonQuery()
Executa uma instrução SQL no banco (como INSERT, UPDATE, DELETE) e retorna o
número de linhas afetadas. Não é utilizado para comandos que retornam linhas,
apenas para aqueles que alteram dados ou executam operações no banco
ExecuteReader()
Executa a consulta SQL associada ao SqlCommand e retorna um SqlDataReader, que
pode ser utilizado para ler os dados retornados pela consulta. É usado principalmente
em operações de seleção (SELECT)
ExecuteScalar()
Executa a consulta e retorna o valor da primeira coluna da primeira linha no
conjunto de resultados retornado pela consulta. Útil para consultas que retornam um
único valor
Parameters.
AddWithValue()
Adiciona um novo objeto SqlParameter à coleção Parameters do SqlCommand,
definindo um nome para o parâmetro e o valor a ser usado na consulta SQL. Também
previne o SQL Injection ao incluir dados do usuário em uma consulta SQL
SqlDataReader
Read()
Avança o SqlDataReader para o próximo registro. Retorna true se houver mais linhas;
caso contrário, retorna false. É utilizado em um loop para processar todas as linhas
retornadas por uma consulta SELECT
GetXXX(index)
Métodos para obter dados de um campo específico no registro atual, como GetString,
GetInt32 etc., onde XXX representa o tipo de dado. O parâmetro index especifica a
coluna do registro a partir da qual se lê o dado
218
Unidade III
Além do SQLClient, existem outros provedores de dados, como Object Linking and Embedding
Database (OLE DB) e Open Database Connectivity (ODBC). O primeiro é um conjunto de interfaces que
permite acesso uniforme a diferentes tipos de fontes de dados; é versátil, mas pode ser um pouco mais
complexo de se usar em algumas situações. Já o segundo é um padrão que permite conexão com uma
ampla variedade de bancos, utilizando um driver intermediário.
Embora ofereça grande flexibilidade, o ODBC pode não ser tão otimizado para determinados tipos
de banco, como o SQLClient, que serve apenas para o SQL Server. A decisão de usar OLE DB ou ODBC em
vez de SqlClient depende de uma avaliação cuidadosa das necessidades da aplicação, incluindo o tipo de
fonte de dados a ser acessado, a necessidade de portabilidade entre diferentes sistemas operacionais e
ambientes, e os requisitos de desempenho e funcionalidade.
OLE DB é uma tecnologia mais antiga, que oferece uma maneira uniforme de acessar uma variedade
de fontes de dados, indicada para aplicações que necessitam de flexibilidade maior na comunicação
com diferentes tiposde banco (não se limitando ao SQL Server), incluindo fontes de dados como Access,
Oracle e até mesmo fontes não tradicionais, como arquivos de texto e planilhas.
OLE DB é útil quando a aplicação precisa interagir com múltiplos sistemas de banco de dados ou
quando o banco de dados alvo não oferece suporte direto ao .NET. No entanto, devido à sua natureza
mais genérica e ao fato de ser uma tecnologia mais antiga, ela pode não oferecer o mesmo nível de
desempenho otimizado ou recursos específicos do banco de dados que um provedor dedicado como
o SqlClient.
Open Database Connectivity (ODBC), por outro lado, é um padrão que permite conexão a uma
variedade de bancos de dados usando SQL como linguagem comum, sendo útil quando a portabilidade
da aplicação for uma preocupação‑chave, como aplicações que precisam operar tanto em Windows
quanto em outros sistemas operacionais, como Linux ou macOS. ODBC serve como camada de abstração
que permite à mesma base de código acessar diferentes sistemas de gerenciamento de banco de dados
(SGBDs) sem precisar reescrever o código para cada SGBD. Isso o torna ideal para aplicações que
precisam de uma ampla compatibilidade com diferentes bancos de dados ou que são distribuídas em
um ambiente heterogêneo.
O quadro 21 destaca as principais alterações no código‑fonte da figura 79 ao transitar entre
diferentes provedores de acesso a dados em .NET para operações de banco. Embora a sintaxe para
estabelecer conexões e executar comandos seja bastante similar entre os três provedores, as principais
diferenças residem no namespace utilizado, no tipo de objeto de conexão e comando, e na maneira
como os parâmetros são adicionados para consultas parametrizadas. Importante observar que, para OLE
DB e ODBC, ao contrário do SqlClient, os parâmetros na consulta SQL são indicados por ? e devem ser
adicionados à coleção de parâmetros na ordem exata em que aparecem na consulta.
219
PROGRAMAÇÃO ORIENTADA A OBJETOS II
Quadro 21 – Adaptações de código para OLE DB e ODBC
SQLClient OLE DB ODBC
using System.Data.SqlClient; using System.Data.OleDb; using System.Data.Odbc;
SqlConnection connection = new
SqlConnection(connectionString)
OleDbConnection connection = new
OleDbConnection(connectionString)
OdbcConnection connection = new
OdbcConnection(connectionString)
SqlCommand command = new
SqlCommand(query, connection);
OleDbCommand command = new
OleDbCommand(query, connection);
OdbcCommand command = new
OdbcCommand(query, connection);
string query = “INSERT INTO Jogador
(Jogador, Posicao, Fortuna, Honra)
VALUES (@Jogador, @Posicao, @
Fortuna, @Honra)”;
string query = “INSERT INTO Jogador
(Jogador, Posicao, Fortuna, Honra)
VALUES (?, ?, ?, ?)”;
string query = “INSERT INTO Jogador
(Jogador, Posicao, Fortuna, Honra)
VALUES (?, ?, ?, ?)”;
command.Parameters.
AddWithValue(“@Jogador”, nome);
command.Parameters.
AddWithValue(“Jogador”, nome);
command.Parameters.
AddWithValue(“Jogador”, nome);
Independentemente do provedor usado, a segurança das interações é crucial, tanto para proteger
os dados manipulados quanto para a integridade do sistema como um todo. Ao desenvolver aplicações
que se comunicam com bancos de dados, os desenvolvedores devem adotar várias medidas de
segurança para mitigar riscos como injeção de SQL, vazamento de informações sensíveis e outros
ataques cibernéticos.
Vimos que uma das principais práticas de segurança é o uso de instruções SQL parametrizadas,
e outra prática recomendada é limitar privilégios. Desenvolvedores devem garantir que as contas de
banco de dados utilizadas pelas aplicações tenham apenas os privilégios necessários para suas funções.
Isso significa evitar o uso de contas com privilégios de administrador ou similares, que podem executar
uma ampla gama de operações.
Limitados os privilégios, mesmo que um atacante consiga explorar alguma vulnerabilidade, o impacto
será minimizado, pois as operações que ele pode realizar serão restritas. Além disso, é fundamental
implementar uma gestão segura de strings de conexão. Informações sensíveis, como credenciais de
acesso ao banco de dados, não devem ser armazenadas em texto claro no código‑fonte. Ferramentas
e técnicas como armazenar configurações sensíveis em variáveis de ambiente e usar arquivos de
configuração criptografados podem ajudar a proteger essas informações.
Também é essencial adotar práticas de codificação segura, como validar entradas para prevenir
ataques como cross‑site scripting (XSS). Mesmo que o foco esteja no SQLClient e na interação com o
banco de dados, garantir que as entradas dos usuários sejam adequadamente verificadas e tratadas
antes de ser utilizadas pela aplicação contribui para a segurança geral do sistema.
6.2 Conexões locais e servidores remotos no contexto da computação
em nuvem
No cenário atual da tecnologia da informação, no qual a computação em nuvem ganha cada vez mais
destaque, é fundamental compreender as nuances que envolvem conexões locais e servidores remotos.
Essa compreensão não apenas amplia as possibilidades de desenvolver aplicativos mais eficientes,
como também abre portas para inovar e otimizar recursos. A transição do armazenamento de dados
220
Unidade III
e do processamento local para servidores remotos hospedados na nuvem representa uma mudança
significativa na maneira como os dados são gerenciados e acessados, o que promove a eficiência e a
escalabilidade, além de introduzir novos desafios relacionados à segurança, à latência e à integração
de sistemas.
A computação em nuvem, ao permitir acesso a recursos computacionais avançados sob demanda,
elimina a necessidade de investimentos pesados em infraestrutura física. Para um desenvolvedor que
utiliza C# para criar soluções que interajam com bancos de dados SQL Server, isso significa que é possível
escalar recursos de maneira flexível, pagando apenas pelo que se usa. No entanto, essa flexibilidade
implica garantir que as conexões entre ambientes locais e servidores remotos sejam seguras e eficientes.
No que diz respeito à segurança, é imprescindível que os dados transmitidos entre cliente e
servidor remoto sejam criptografados e protegidos contra acessos não autorizados. Utilizar práticas
recomendadas de segurança, como implementação de redes privadas virtuais (VPNs) e protocolos de
criptografia robustos, torna‑se essencial para prevenir violações de dados.
Além da segurança, latência é outro fator crítico a considerar. Desenvolvedores que trabalham
com aplicações em tempo real sabem que tempo de resposta é crucial para a experiência do usuário.
Portanto, ao migrar para a nuvem, é importante escolher provedores de serviços em nuvem que ofereçam
baixa latência e garantam a rápida transmissão de dados entre cliente e servidor. Isso é relevante para
aplicações que utilizam C# e SQL Server, pois as operações de leitura e escrita no banco de dados
precisam ser rápidas e eficientes.
Integração de sistemas locais com servidores remotos na nuvem também representa um desafio. É
essencial que os desenvolvedores criem interfaces de programação de aplicativos (APIs) bem projetadas
e utilizem serviços de middleware para facilitar a comunicação entre diferentes sistemas. Entender como
utilizar adequadamente os serviços de nuvem e como integrá‑los às aplicações locais é fundamental
para o sucesso do projeto.
Para um desenvolvedor que deseja iniciar sua jornada na programação em nuvem, diversas mudanças
são esperadas no que tange ao ponto de vista do código, embora os princípios fundamentais de
programação e banco de dados permaneçam consistentes. O primeiro impacto significativo é
introduzir serviços de nuvem e integrá‑los devidamente ao desenvolvimento de software.
Ao migrar para a nuvem, o desenvolvedor precisa se familiarizar com os serviços de computação em
nuvem oferecidos por plataformas como Azure (Microsoft), AWS (Amazon) ou Google Cloud Platform
(Google). Elas fornecem serviços de banco de dados que podem se assemelharao SQL Server (como o
Azure SQL Database no caso do Azure), mas também introduzem novos paradigmas (como bancos de
dados NoSQL), funções como serviço (FaaS) e serviços de computação sem servidor.
221
PROGRAMAÇÃO ORIENTADA A OBJETOS II
1. using System;
2. using System.Data.SqlClient;
3. class Program
4. {
5. static void Main()
6. {
7. // String de conexão ao Azure SQL Database
8. var connectionString =
“Server=tcp:seuservidor.database.windows.net,1433;Initial
Catalog=seuBanco;Persist Security Info=False;User
ID=seuUsername;Password=seuPassword;MultipleActiveResultSets=False;Encrypt
=True;TrustServerCertificate=False;Connection Timeout=30;”;
9. // Consulta SQL para executar no banco de dados
10. var sqlQuery = “SELECT TOP 10 * FROM SuaTabela”;
11. using (var connection = new SqlConnection(connectionString))
12. {
13. var command = new SqlCommand(sqlQuery, connection);
14. connection.Open();
15. using (var reader = command.ExecuteReader())
16. {
17. while (reader.Read())
18. {
19. Console.WriteLine(reader[“NomeDaSuaColuna”].ToString());
20. }
21. }
22. }
23. }
24. }
Figura 81 – Consulta simples usando Azure SQL Database
A figura 81 apresenta um exemplo de como um desenvolvedor C# pode se conectar ao Azure SQL
Database e executar uma consulta SQL. Esse exemplo utiliza a biblioteca System.Data.SqlClient para
demonstrar a continuidade no uso de ferramentas conhecidas, mesmo ao mover‑se para a nuvem.
222
Unidade III
Na figura podemos ver que um desenvolvedor pode continuar utilizando suas habilidades em SQL e C#
ao migrar para a nuvem, com mudanças principalmente na forma de se conectar ao banco de dados
(neste caso usando uma string de conexão específica do Azure SQL Database).
Embora a sintaxe SQL e as operações de banco de dados continuem em grande parte as mesmas, o
desenvolvedor deve se adaptar ao ambiente de nuvem, que traz novas ferramentas e práticas, como a
gestão de identidades e o monitoramento de desempenho e custos na plataforma de nuvem.
Saiba mais
Azure é uma plataforma de computação em nuvem criada pela Microsoft
que oferece mais de 200 produtos e serviços projetados para ajudar a
resolver desafios modernos de negócio, permitindo a desenvolvedores
e empresas criar, testar, implantar e gerenciar aplicativos e serviços com
centros de dados gerenciados pela Microsoft em todo o mundo. Com
forte ênfase em segurança, privacidade, conformidade e sustentabilidade,
Azure se destaca pela sua integração com ferramentas da Microsoft,
suporte a várias linguagens de programação, frameworks e sistemas
operacionais. É conhecido por sua capacidade de oferecer soluções em
várias camadas, incluindo infraestrutura como serviço (IaaS), plataforma
como serviço (PaaS) e pacotes de software como serviço (SaaS), facilitando
a transformação digital, a inovação em IA, a computação em nuvem híbrida
e a internet das coisas (IoT).
Uma boa referência para se aprofundar no assunto é este livro:
MODI, R.; LEE, J.; SKARIA, R. Azure for architects: third edition – create
secure, scalable, high‑availability applications on the cloud. Birmingham:
Packt Publishing, 2020.
Amazon Web Services (AWS) é a plataforma de serviços de nuvem mais
abrangente e amplamente adotada do mundo. Fornecendo uma coleção
massiva de IaaS, PaaS e SaaS, ela possibilita às empresas flexibilidade,
escalabilidade e confiabilidade. Desde o armazenamento de dados com
o Amazon S3 até a computação com o EC2, passando por tecnologias
emergentes como aprendizado de máquina, inteligência artificial, internet
das coisas e muito mais, a AWS fornece as ferramentas necessárias para
que empresas de todos os tamanhos acelerem seu crescimento. Sua ampla
rede global de centros de dados garante alta disponibilidade e redundância,
tornando‑se uma escolha popular entre startups, grandes corporações e
agências governamentais.
223
PROGRAMAÇÃO ORIENTADA A OBJETOS II
Para entender mais da AWS, consulte:
LAKHERA, P. AWS for system administrators: build, automate, and
manage your infrastructure on the most popular cloud platform – AWS.
Birmingham: Packt Publishing, 2021.
WITTIG, A.; WITTIG, M. Amazon web services in action. 3. ed. Nova York:
Manning, 2023.
Já Google Cloud é uma suíte de computação em nuvem que oferece
hospedagem em infraestrutura moderna e serviços de computação para
desenvolver softwares, análises e aprendizado de máquina fornecidos pelo
Google. Abraçando inovação e escalabilidade, Google Cloud permite que
empresas e desenvolvedores tirem proveito da mesma infraestrutura e de
ferramentas que o Google usa para seus próprios produtos, como Google
Search, YouTube e outros. Com serviços como Google Compute Engine,
Google App Engine, Google Cloud Storage e Google Kubernetes Engine, a
plataforma é projetada para oferecer segurança de alto nível, análise de
dados avançada e capacidades de machine learning integradas. O Google
Cloud destaca‑se pela capacidade de processar grandes volumes de dados
e pela sua integração profunda com ferramentas de análise e machine
learning, tornando‑se ideal para organizações que buscam inovar e escalar
rapidamente suas operações em um ambiente de nuvem.
Para conhecer melhor essa opção, consulte o livro a seguir:
HUNTER, T.; PORTER, S. Google Cloud Platform for developers.
Birmingham: Packt Publishing, 2018.
No que se refere ao código, o desenvolvedor precisará aprender a usar kits de desenvolvimento de
software (SDKs) específicos da nuvem para interagir com esses serviços. Por exemplo, ao usar o Azure
com C#, o desenvolvedor pode utilizar o Azure SDK para .NET para criar, consultar e gerenciar recursos
do Azure diretamente de seu código C#, incluindo criar instâncias de banco de dados, gerenciar
armazenamento ou implementar lógicas de aplicação através do Azure Functions, que permite
executar código em resposta a eventos específicos, tudo isso gerenciado pela plataforma de nuvem.
Além disso, a programação em nuvem incentiva fortemente práticas de DevOps, incluindo integração
contínua (CI) e entrega contínua (CD), para automatizar o teste, a construção e a implantação de
aplicações. Isso requer que o desenvolvedor se familiarize com ferramentas e plataformas de CI/CD,
como Azure DevOps, GitHub Actions ou Jenkins. Adotar essas práticas e ferramentas não muda apenas
a maneira como o código é escrito, mas também como é compilado, testado e implantado, promovendo
maior eficiência e colaboração entre as equipes de desenvolvimento e operações.
224
Unidade III
Lembrete
Outra mudança significativa já mencionada é a necessidade de adotar
uma mentalidade de segurança em primeiro lugar, dada a natureza pública
dos serviços de nuvem. Isso envolve o uso de práticas recomendadas de
segurança, como gestão de identidade e acesso, criptografia de dados
em repouso e em trânsito, e configuração segura dos serviços de nuvem.
O código precisa ser desenvolvido com essas considerações, o que pode
incluir a implementação de autenticações e autorizações, Managed
Identities para serviços do Azure e políticas de segurança no acesso a dados.
6.3 Arquitetura do ADO.NET: DataSet, DataTable, DataView, DataRow
e DataColumn
A arquitetura do ADO.NET é parte fundamental do desenvolvimento de aplicações .NET que interagem
com bancos de dados e oferece um conjunto rico de classes projetadas para facilitar a comunicação entre
aplicações .NET e fontes de dados. Entre as principais classes e componentes do ADO.NET, destacam‑se
DataSet, DataTable, DataView, DataRow e DataColumn, que juntos oferecem um modelo robusto para
manipular dados de forma desconectada.
DataSet é um componente do ADO.NET que representa um conjunto de dados em memória,
independentemente de qualquer fonte. Ele pode conter uma ou mais tabelas de dados (DataTable), além
de relações entremente elas (DataRelation) e restrições (Constraint). É usado para armazenar dados
recuperados de uma fonte, permitindo que a aplicação trabalhecom eles de maneira desconectada.
Processados ou alterados os dados no DataSet, as modificações podem ser sincronizadas
de volta à fonte de dados. Já o DataTable representa uma única tabela de dados em memória,
contendo linhas (DataRow) e colunas (DataColumn). Cada DataTable dentro de um DataSet pode
ser manipulado individualmente, permitindo selecionar, inserir, atualizar e excluir dados dentro da
tabela, oferecendo métodos para adicionar, remover ou encontrar linhas.
DataRow é o componente que representa uma única linha numa DataTable. Cada DataRow contém
os valores para cada coluna da tabela, e os objetos DataRow são usados para ler, inserir, atualizar e excluir
dados de uma DataTable. Complementarmente, DataColumn representa uma coluna em uma DataTable,
definindo o tipo de dado e outras propriedades da coluna, como se permite null, a chave primária, e
assim por diante. Cada DataColumn em uma DataTable define o esquema (ou estrutura) da tabela.
Finalmente, DataView representa uma visão personalizável de uma DataTable que pode ser ordenada
e filtrada sem modificar os dados subjacentes na DataTable. Isso é útil para exibir dados em controles
de interface do usuário, como grades de dados, onde diferentes visualizações dos mesmos dados são
necessárias simultaneamente.
225
PROGRAMAÇÃO ORIENTADA A OBJETOS II
Vamos usar a analogia de um restaurante para fixar os conceitos de DataSet, DataTable, DataView,
DataRow e DataColumn do ADO.NET. Imagine que um DataSet é o restaurante inteiro. Dentro dele
existem diferentes menus disponíveis, como menu do almoço, do jantar e de sobremesas; cada menu
pode ser visto como uma DataTable, e cada um contém itens específicos de comida servidos em diferentes
momentos ou ocasiões.
Cada item no menu – seja um prato principal, uma entrada ou sobremesa – pode ser comparado a
uma DataRow. Um prato específico, como lasanha à bolonhesa, seria uma linha na tabela do menu do
jantar. As características desse prato – como preço, ingredientes e tempo de preparo – seriam as colunas
(DataColumn) da tabela. Cada DataColumn armazena um tipo de informação sobre o prato, assim como
as colunas de uma tabela em um banco de dados.
Agora imagine que você queira ver uma versão personalizada do menu, talvez uma lista de pratos
vegetarianos ou que tenham menos de 500 calorias. Essa lista pode ser representada por uma DataView,
que permite filtrar e ordenar os itens do menu (DataRows) sem alterar os menus originais (DataTables),
oferecendo uma maneira flexível de visualizar os dados conforme a necessidade do cliente. Portanto, em
nosso restaurante (DataSet), os diversos menus (DataTables) apresentam os pratos (DataRows) com suas
informações (DataColumns), e as visualizações personalizadas dos menus (DataViews) permitem aos
clientes filtrar e ordenar os pratos de acordo com suas preferências ou restrições dietéticas, sem alterar
a composição dos menus originais.
Essa analogia ilustra como o ADO.NET organiza e manipula dados de maneira desconectada,
permitindo criar, manipular e visualizar de forma personalizada os dados de modo eficiente e adaptável
às necessidades dos usuários da aplicação. Para ilustrar o uso desses componentes, considere a figura 82,
que começa criando um DataSet chamado Restaurante (linha 8).
1. using System;
2. using System.Data;
3. class Program
4. {
5. static void Main()
6. {
7. // Criando o restaurante (DataSet)
8. DataSet restaurante = new DataSet(“Restaurante”);
9. // Criando o menu do jantar (DataTable)
10. DataTable menuJantar = new DataTable(“MenuJantar”);
11. // Adicionando colunas ao menu do jantar (DataColumn)
12. menuJantar.Columns.Add(“Prato”, typeof(string));
13. menuJantar.Columns.Add(“Vegetariano”, typeof(bool));
14. menuJantar.Columns.Add(“Preco”, typeof(decimal));
226
Unidade III
15. // Adicionando pratos ao menu do jantar (DataRow)
16. menuJantar.Rows.Add(“Lasanha à Bolonhesa”, false, 25.99m);
17. menuJantar.Rows.Add(“Risoto de Cogumelos”, true, 22.50m);
18. menuJantar.Rows.Add(“Salada Caesar”, true, 18.00m);
19.
20. // Adicionando o menu do jantar ao restaurante (DataSet)
21. restaurante.Tables.Add(menuJantar);
22.
23. // Criando uma DataView para filtrar pratos vegetarianos
24. DataView vistaVegetariana = new DataView(menuJantar);
25. vistaVegetariana.RowFilter = “Vegetariano = true”;
26. Console.WriteLine(“Pratos Vegetarianos no Menu do Jantar:”);
27. foreach (DataRowView row in vistaVegetariana)
28. {
29. Console.WriteLine($”{row[“Prato”]} – Preço: {row[“Preco”]}”);
30. }
31. }
32. }
Figura 82 – Analogia do restaurante usando DataSet, DataTable, DataView, DataRow e DataColumn
Em seguida, o programa cria uma DataTable para o menu do jantar (linha 10) adicionando três
colunas: Prato (nome do prato), Vegetariano (booleano que indica se o prato é vegetariano ou não) e Preco
(preço do prato). Depois adicionamos alguns pratos ao menu do jantar usando Rows.Add. Com o menu
do jantar preenchido, ele é adicionado ao DataSet do restaurante. Para encontrar pratos vegetarianos,
criamos uma DataView do menu do jantar (linha 26) e aplicamos um filtro usando RowFilter. Por fim,
iteramos os resultados filtrados e exibimos os pratos vegetarianos disponíveis.
Para fazer o DataSet interagir com um banco de dados SQL, podemos criar um método que utilize a
classe SqlConnection para estabelecer uma conexão com o banco de dados, com SqlDataAdapter para
preencher o DataSet com dados vindos de uma tabela SQL e para atualizá‑la com as mudanças feitas no
DataSet. SqlDataAdapter é uma classe do ADO.NET que atua como ponte entre um banco de dados SQL
Server e um DataSet no .NET Framework. Ele permite transferir dados entre um banco de dados e um
DataSet, e vice‑versa, facilitando operações de leitura e escrita de dados sem precisar escrever comandos
SQL explícitos para cada operação.
Nesse novo exemplo (disposto na figura 83), vamos criar um método chamado
SincronizarComBancoDeDados, que irá atualizar o banco de dados com base nas alterações feitas em
227
PROGRAMAÇÃO ORIENTADA A OBJETOS II
nosso DataSet. Esse método inicia abrindo uma conexão com o banco de dados usando a string de
conexão fornecida e então configura um SqlDataAdapter com uma consulta SQL que seleciona todos os
registros de uma tabela especificada.
O SqlCommandBuilder gera automaticamente os comandos SQL necessários para atualizar o banco
de dados com base nas alterações feitas no DataTable. Por fim, o método Update do SqlDataAdapter é
chamado para aplicar essas mudanças no banco.
1. using System;
2. using System.Data;
3. using System.Data.SqlClient;
4. class Program
5. {
6. static void Main()
7. {
8. // Exemplo de como usar o método
9. DataSet restaurante = CriarDataSetRestaurante();
10. SincronizarComBancoDeDados(restaurante, “MenuJantar”);
11. }
12.
13. static DataSet CriarDataSetRestaurante()
14. {
15. // Método de criação do DataSet omitido por simplicidade
16. // Retorne um DataSet preenchido similar ao exemplo anterior
17. }
18.
19. static void SincronizarComBancoDeDados(DataSet dataSet, string
nomeTabela)
20. {
21. string connectionString = “Data Source=NomeDoServidor;Initial
Catalog=NomeDoBancoDeDados;Integrated Security=True”;
22. using (SqlConnection connection = new SqlConnection(connectionString))
23. {
24. try
25. {
26. connection.Open();
228
Unidade III
27. // Criar um SqlDataAdapter com base em uma consulta SQL SELECT
para o nome da tabela
28. string query = $”SELECT * FROM {nomeTabela}”;
29. SqlDataAdapter adapter = new SqlDataAdapter(query, connection);
30.
31. // Usar SqlCommandBuilder para gerar comandos SQL
automaticamente para atualizações
32. SqlCommandBuilder commandBuilder = new SqlCommandBuilder(adapter);
33.
34. // Obter o DataTable do DataSet
35. DataTable table = dataSet.Tables[nomeTabela];
36.
37. // Atualizar o banco de dadosSQL com as alterações feitas
no DataTable
38. adapter.Update(table);
39.
40. Console.WriteLine(“Banco de dados sincronizado com sucesso.”);
41. }
42. catch (Exception ex)
43. {
44. Console.WriteLine($”Erro ao sincronizar com o banco de dados:
{ex.Message}”);
45. }
46. }
47. }
48.}
Figura 83 – Analogia do restaurante e persistência num banco de dados SQL Server
Lembrete
Note que este é um exemplo básico e que a manipulação real de dados
pode requerer operações mais complexas, como um tratamento mais
detalhado de exceções e transações antes de atualizar o banco de dados.
Além disso, é importante garantir que a string de conexão e quaisquer
comandos SQL utilizados sejam seguros para evitar vulnerabilidades de
segurança, como injeção de SQL.
229
PROGRAMAÇÃO ORIENTADA A OBJETOS II
6.4 LINQ e ADO.NET
LINQ e ADO.NET são duas tecnologias essenciais para manipular e acessar dados, cada uma com suas
particularidades e casos de uso. Compreendê‑las é fundamental para desenvolvedores que desejam
explorar plenamente as capacidades do .NET em termos de interação com diferentes fontes, sejam elas
bancos de dados relacionais, documentos XML ou coleções de objetos em memória.
O LINQ, uma extensão do .NET, introduz um método para consultar dados, permitindo que sejam
escritos diretamente na linguagem C# de maneira declarativa. Essa abordagem não apenas simplifica
o código – tornando‑o mais legível e conciso –, mas também oferece uma camada de abstração que
desacopla o código das especificidades das fontes de dados. Isso significa que, independentemente do
tipo de dados – seja numa coleção em memória, arquivo XML ou banco de dados –, o desenvolvedor
pode utilizar a mesma sintaxe de consulta, facilitando a manutenção e a reutilização do código.
Além disso, o LINQ proporciona uma integração profunda com a linguagem C#, permitindo o uso
de recursos como intellisense e a verificação de tipo em tempo de compilação, o que contribui para
reduzir erros e aumentar a eficiência do desenvolvimento (já utilizamos LINQ neste livro‑texto com as
expressões lambda).
Por outro lado, ADO.NET é uma estrutura mais antiga e robusta, projetada para acessar dados e
manipular bancos relacionais. ADO.NET oferece um conjunto de classes que permitem conectar‑se
a fontes de dados, executar comandos, gerenciar transações e lidar com o retorno de dados. Essa
tecnologia é conhecida por sua performance e por oferecer um controle fino sobre o processo de
interação com o banco, incluindo a capacidade de trabalhar diretamente com conexões, comandos e
adaptadores de dados.
ADO.NET é útil em cenários que exigem operações complexas, transações ou otimizações
específicas de desempenho além do escopo do LINQ. Embora LINQ e ADO.NET pareçam ferramentas
concorrentes à primeira vista, na realidade se complementam dentro do ecossistema .NET. Enquanto
o LINQ se destaca pela sua facilidade de uso, legibilidade e capacidade de trabalhar com diferentes
fontes de dados de maneira uniforme, o ADO.NET oferece controle mais granular e desempenho
otimizado para interações diretas com bancos de dados. Desenvolvedores frequentemente utilizam
LINQ para consultas e operações de dados de alto nível, recorrendo ao ADO.NET para cenários que
requerem manipulações mais detalhadas ou específicas do banco.
Escolher entre LINQ e ADO.NET, portanto, não é uma questão de um substituir o outro, mas sim de
entender o contexto da aplicação e os requisitos específicos do projeto. A capacidade de usar LINQ para
rapidamente consultar e manipular dados em uma variedade de fontes, com a robustez e o controle fino
oferecido pelo ADO.NET, proporciona aos desenvolvedores em C# uma gama poderosa de ferramentas
para enfrentar praticamente qualquer desafio de acesso a dados.
A integração entre LINQ e ADO.NET também se manifesta no LINQ to Entities, parte do Entity
Framework, uma implementação do LINQ que trabalha com objetos de banco de dados. Nesse contexto
o LINQ é usado para escrever consultas contra o modelo de objetos do Entity Framework, que por
230
Unidade III
sua vez traduz essas consultas para SQL e as executa através do ADO.NET. Isso proporciona uma
camada adicional de abstração, permitindo que os desenvolvedores escrevam consultas de maneira
declarativa e orientada a objetos, e ainda se beneficiem do poderoso acesso a dados e controle
oferecido pelo ADO.NET.
6.5 Projeto intermediário: interface desktop com acesso aos dados do banco
Integrar um banco de dados ao jogo Os desafios dos mosqueteiros: duelos & destinos pode
enriquecer significativamente a experiência do usuário, oferecendo funcionalidades como a persistência
de dados entre sessões e a análise de suas estatísticas ao longo do tempo. Armazenar informações
sobre jogadores – como nomes, posições no tabuleiro, quantidade de fichas de fortuna e de honra e
seus estados (eliminado, pausando o próximo turno, no reconhecimento da corte etc.) – é um primeiro
passo fundamental, pois permite a eles retomar partidas interrompidas e possibilita criar um sistema de
rankings ou de conquistas baseado no desempenho histórico deles.
Implementar um histórico de partidas detalhado poderia oferecer insights valiosos sobre o
desenvolvimento do jogo ao longo do tempo, destacando, por exemplo, quais jogadores frequentemente
alcançam o reconhecimento da corte ou quais estratégias levam à vitória com mais frequência. Esses
dados, ao serem analisados, poderiam informar ajustes de balanceamento ou até mesmo inspirar a
criação de novas regras ou mecânicas.
A armazenagem das cartas de evento sorteadas e dos resultados dos duelos em banco de dados
também contribuiria para uma experiência mais rica, permitindo a análise da frequência de certos
eventos ou a taxa de sucesso dos jogadores em duelos. Isso poderia inclusive guiar os desenvolvedores na
hora de ajustar a dificuldade ou a aleatoriedade do jogo, garantindo que a experiência seja desafiadora,
porém justa.
Uma possibilidade interessante é usar esses dados para introduzir mecânicas de jogo adaptativas.
Por exemplo, se alguém estiver com dificuldades constantes, o jogo poderia ajustar automaticamente
a dificuldade dos desafios enfrentados ou oferecer caminhos alternativos para progredir. Da mesma
forma, quem busca um desafio maior poderia encontrar mecânicas que aumentem a dificuldade
ou adicionem objetivos secundários com base em seu histórico de desempenho. Integrar um banco
de dados faria o jogo deixar de ser uma experiência isolada e estática para se tornar uma jornada
dinâmica e personalizada, e o progresso do jogador seria reconhecido e recompensado, dado que os
desenvolvedores teriam à disposição um rico conjunto de dados para melhorá‑lo continuamente.
Além disso, com a interface gráfica, armazenar preferências do usuário se torna particularmente
valioso, pois isso não apenas melhora a experiência – ao permitir que o jogo se adapte às escolhas
individuais de visualização e interação –, mas também pode reforçar o engajamento ao fazer com que
os jogadores se sintam mais entrosados. Seja lembrando o volume de som preferido, as configurações
de dificuldade ou até temas visuais escolhidos, a personalização via dados persistidos cria uma conexão
mais profunda entre o jogo e quem joga.
231
PROGRAMAÇÃO ORIENTADA A OBJETOS II
Adicionalmente, analisar como os jogadores interagem com a interface do Windows Forms pode
fornecer insights cruciais para desenvolvedores. Saber quais funcionalidades são mais usadas ou quais
caminhos os usuários preferem para navegar pelo jogo pode orientar melhorias na interface, tornando‑a
mais intuitiva e agradável. Essa análise pode ir desde a frequência de cliques em determinadas áreas
da tela até o tempo gasto em diferentes seções do jogo, oferecendo uma rica fonte de dados para
otimizar a experiência.
A coleta de dados de desempenho também assume um papel importante. Monitorar o desempenho
do aplicativo em diferentes configurações de hardwarepode ajudar a identificar pontos de otimização,
garantindo que o jogo funcione de maneira fluida em uma ampla gama de sistemas. Isso é relevante
para jogos desenvolvidos em plataformas como Windows Forms, nos quais a experiência do usuário
pode variar significativamente com o desempenho do aplicativo. Assim, a persistência de dados em
um ambiente desktop vai além da simples manutenção do estado do jogo, abrindo caminhos para uma
experiência mais rica, personalizada e otimizada.
232
Unidade III
Resumo
Nesta unidade discorremos sobre a necessidade de uma arquitetura de
software bem planejada, comparável à fundação de um edifício que sustenta
e direciona o desenvolvimento eficaz de aplicações. Usando padrões de design,
como model‑view‑controller (MVC) e model‑view‑viewmodel (MVVM),
ilustramos como essas metodologias ajudam a separar as responsabilidades
dentro de um projeto, melhorando a manutenção, reusabilidade e
escalabilidade do software. O MVC, em particular, é referenciado por sua
capacidade de separar a lógica de negócios da apresentação da interface
do usuário, permitindo uma maior flexibilidade e testabilidade.
A arquitetura em camadas é abordada como meio de organizar o
código para que cada camada tenha responsabilidades claras, facilitando
a manutenção e a atualização do sistema. A discussão sobre padrões de
design e arquitetura de software foi enriquecida com exemplos de código
em C#, proporcionando uma visão prática de como esses conceitos podem
ser aplicados no desenvolvimento de software. Os exemplos ilustram como
esses padrões podem resolver problemas comuns de design, facilitando a
criação de um software que seja ao mesmo tempo poderoso e fácil de manter.
Vimos como o padrão MVVM desempenha um papel crucial na programação
Windows Presentation Foundation com C#, oferecendo uma abordagem
clara e eficiente para separar a lógica de negócios da interface do usuário (UI).
Esse padrão facilita o desenvolvimento e a manutenção de aplicações
complexas, permitindo que os desenvolvedores trabalhem em aspectos da
UI e da lógica de negócios de maneira independente.
No MVVM, o Model representa os dados e a lógica de negócios, a View
corresponde à interface do usuário, e o ViewModel atua como intermediário
que liga o Model à View. Essa separação promove a reutilização de código,
facilita testes automatizados e melhora a colaboração em equipes grandes,
permitindo que especialistas em UI e lógica de negócios concentrem‑se em
suas áreas sem interferir um no trabalho do outro.
Vimos que a integração com bancos de dados usando SQL é
crucial no desenvolvimento de software, pois a eficiência no acesso e
na manipulação de dados pode impactar significativamente o desempenho
da aplicação. Um entendimento profundo de SQL e de como interagir com
bancos é vital para o sucesso de aplicações robustas e eficientes.
233
PROGRAMAÇÃO ORIENTADA A OBJETOS II
Além disso, o uso de banco de dados na nuvem tem sido cada vez mais
importante, transformando a forma como as aplicações armazenam e
acessam dados, pois ela oferece escalabilidade, flexibilidade e acessibilidade
superiores, permitindo ajustar recursos de maneira eficiente de acordo com
a demanda. Integrar bancos de dados na nuvem em aplicações WPF pode
aumentar significativamente a eficiência operacional, reduzir custos com
infraestrutura local e melhorar a disponibilidade e a resiliência dos dados.
Isso é particularmente benéfico em aplicações distribuídas e móveis,
nas quais é crítica a necessidade de acessar dados de qualquer lugar e a
qualquer momento.
Enfim, destacamos a importância de uma sólida arquitetura de
software e de padrões de design como fundamentos para desenvolver
aplicações eficientes, reutilizáveis e escaláveis. Através da separação de
responsabilidades, integração eficaz com bancos de dados e interfaces
de usuário intuitivas, desenvolvedores podem construir um software
robusto, que atenda a necessidades e se adapte facilmente a mudanças.
234
Unidade III
Exercícios
Questão 1. (Cesgranrio 2022, adaptada) Um programador de sistemas computacionais vai utilizar
o padrão MVC para desenvolver um aplicativo para um banco. O principal processamento da aplicação
será realizado quando o usuário clicar um objeto botão. O evento acionado pelo botão usará um
intermediador, que vai preparar a informação e executar o processamento.
Esse intermediador, na arquitetura MVC, deve ser tratado na camada:
A) Controller.
B) Middleware.
C) Model.
D) Restore.
E) View.
Resposta correta: alternativa A.
Análise da questão
Arquitetura MVC é um padrão de design de software que divide uma aplicação em três componentes
interconectados: Modelo (Model), Visão (View) e Controlador (Controller).
A camada Model representa os dados e as regras de negócio da aplicação. View é a camada de
apresentação, que expressa a interface do usuário. A camada Controller, por sua vez, atua como
intermediária entre a Model e a View, manipulando os dados do modelo com base na entrada do usuário
e atualizando a visão para refletir as alterações neles.
No contexto da questão, o evento acionado pelo clique em um botão usará um intermediador,
que deve preparar a informação e executar o processamento. Esse intermediador deve ser tratado na
camada Controller.
235
PROGRAMAÇÃO ORIENTADA A OBJETOS II
Questão 2. A respeito do SQLClient, avalie as afirmativas.
I – Trata‑se de uma biblioteca do .NET Framework.
II – Faz parte do ADO.NET, um conjunto de componentes no .NET Framework.
III – Uma string de conexão, usada para conectar uma aplicação a um banco de dados, não deve
conter informações sensíveis.
É correto o que se afirma em:
A) I, apenas.
B) III, apenas.
C) I e II, apenas.
D) II e III, apenas.
E) I, II e III.
Resposta correta: alternativa C.
Análise das afirmativas
I – Afirmativa correta.
Justificativa: o SQLClient é de fato uma biblioteca do .NET Framework que fornece uma série de
classes para acessar e gerenciar bancos de dados SQL Server.
II – Afirmativa correta.
Justificativa: o SQLClient faz parte do ADO.NET, um conjunto de componentes no .NET Framework
que fornece acesso a dados para aplicativos .NET.
III – Afirmativa incorreta.
Justificativa: uma string de conexão, usada para conectar uma aplicação a um banco de dados,
contém informações sensíveis, como nome do servidor do banco de dados, nome do próprio banco,
nome de usuário e senha (informações necessárias para estabelecer uma conexão com o banco).
Por conta da natureza sensível desses dados, é fundamental armazenar e transmitir de maneira segura
as strings de conexão.Idealmente, haveria uma camada ou conjunto de classes dedicadas à
interação com o usuário, como a exibição de informações e a coleta de entradas, separadas da lógica
de negócios.
165
PROGRAMAÇÃO ORIENTADA A OBJETOS II
O controle do fluxo do jogo está principalmente na classe Jogo, mas, assim como a visão, ele está
fortemente acoplado com a lógica do negócio. Idealmente, um controlador separado deveria gerenciar a
interação entre a visão e o modelo, processando as entradas do usuário e atualizando a visão e o modelo
conforme necessário.
A figura 59 elenca alguns pontos de melhoria no código do jogo.
Separação da lógica
do negócio, da
lógica de controle
e da interface
do usuário em
diferentes camadas
ou classes. Isso
não apenas adere
melhor ao MVC,
mas também facilita
a manutenção e a
expansão do códigoDe
sa
co
pl
am
en
to
A estrutura atual
torna difícil escrever
testes unitários,
especialmente porque
a lógica do negócio e a
interação do usuário
estão misturadas.
A separação de
responsabilidades
permitiria testar a
lógica do negócio de
forma isolada
Te
st
ab
ili
da
de
Com a separação em
camadas, alterações
na interface do
usuário ou na lógica
do negócio podem
ser feitas de forma
mais independente
e flexível
Fl
ex
ib
ili
da
de
e
es
ca
la
bi
lid
ad
e
A modularização e
o desacoplamento
de responsabilidades
facilitam a reutilização
de código em
diferentes partes
do programa ou em
projetos futuros
Re
ut
ili
za
çã
o
do
c
ód
ig
o
Figura 59 – Pontos de melhoria no código de Os desafios dos mosqueteiros: duelos & destinos
O código‑fonte do jogo não apresenta uma separação clara entre as diferentes camadas, como
lógica de negócios, acesso a dados e apresentação. Uma estrutura em camadas bem definida poderia
ajudar a organizar melhor o código, facilitando alterações e adicionando novas funcionalidades; por
exemplo, poderia existir uma camada de dados para lidar com a persistência do estado do jogo, separada
da lógica que define suas regras.
Embora o código demonstre uma boa tentativa de criar um jogo interativo com várias classes
representando diferentes aspectos do jogo, a falta de separação entre as preocupações e a ausência
de uma arquitetura em camadas claramente definida podem levar a desafios no que diz respeito a
escalabilidade, manutenção e teste do software.
5.2 Entendendo os componentes do Windows Presentation Foundation (WPF)
O padrão model‑view‑viewmodel (MVVM) é uma abordagem arquitetônica no desenvolvimento
de software que enfatiza a separação clara de responsabilidades entre a interface do usuário e a
lógica de negócios, algo de extrema importância em projetos complexos, especialmente naqueles
desenvolvidos com tecnologias Microsoft, como WPF em C#. Originário do desejo de facilitar
a manutenção e a escalabilidade de aplicações, o MVVM tornou‑se elemento central na arquitetura
de software moderna. No centro do MVVM está a divisão de uma aplicação em três componentes
principais: o Model, o View e o ViewModel.
166
Unidade III
Model representa a lógica de negócios e os dados subjacentes. É o núcleo funcional da aplicação,
contendo regras, algoritmos e estruturas de dados que definem seu comportamento da aplicação.
Em um contexto C#, Model seria composto por classes que gerenciam os dados e a lógica de negócios
da aplicação, muitas vezes interagindo com bancos de dados ou serviços web.
O View, por outro lado, é a interface do usuário, a representação visual da aplicação, responsável
por apresentar a ele os dados e capturar suas interações. Em WPF e outras tecnologias baseadas em
C#, o View é frequentemente construído usando Extensible Application Markup Language (XAML), que
permite a descrição declarativa da interface do usuário.
Finalmente, o ViewModel atua como intermediário entre o Model e o View, sendo responsável
por lidar com a lógica de apresentação, transformar dados do Model em formatos que podem ser
facilmente apresentados no View e implementar comandos e ações acionados pela interface do usuário.
Essencialmente, ele “traduz” as informações do Model de forma que o View possa entendê‑las e
apresentá‑las de forma eficaz.
Uma das vantagens mais significativas do MVVM é a facilitação de testes unitários e manutenção.
Como o ViewModel não depende diretamente do View, é possível testar a lógica de apresentação sem
a necessidade de uma interface de usuário, o que simplifica os testes automatizados. Além disso, essa
separação permite que designers e desenvolvedores trabalhem de maneira mais independente e paralela,
melhorando a eficiência do desenvolvimento.
Outra característica importante do MVVM em C# é a implementação do data binding – conceito
que permite ligar elementos da interface do usuário a propriedades no ViewModel. Isso significa que,
quando os dados no ViewModel mudam, a interface do usuário é automaticamente atualizada para
refletir essas mudanças, e vice‑versa. Essa abordagem reduz a quantidade necessária de código de
aderência para conectar a interface do usuário à lógica de negócios, simplificando o desenvolvimento
e a manutenção.
Em resumo, o padrão MVVM oferece uma estrutura clara e eficiente para construir aplicações em
C#, especialmente as que utilizam WPF. Ele promove uma separação limpa de responsabilidades, facilita
os testes unitários, melhora a manutenção e permite um desenvolvimento mais ágil e colaborativo.
Ao adotá‑lo, desenvolvedores e designers podem trabalhar de maneira mais eficiente, resultando em
aplicações robustas, manuteníveis e de alta qualidade.
Dada a estrutura de Os desafios dos mosqueteiros: duelos & destinos – que tem uma lógica de
negócios bem definida e potencial para uma interface de usuário complexa (especialmente se planejarmos
adicionar elementos gráficos, animações etc.) –, o MVVM pode ser a escolha mais apropriada. Essa
arquitetura facilitará a manutenção e a expansão do jogo, além de promover uma boa separação entre
a lógica de negócios e a user interface (UI), o que é crucial em aplicações gráficas complexas. Além disso,
como neste tópico vamos escolher o WPF para a interface gráfica, o MVVM será uma escolha natural,
uma vez que é o padrão recomendado para aplicações WPF.
167
PROGRAMAÇÃO ORIENTADA A OBJETOS II
5.3 MVVM e sua integração com XAML
MVVM é uma abordagem arquitetural particularmente eficaz em tecnologias baseadas em XAML,
como WPF, Universal Windows Platform (UWP) e Xamarin.Forms. Nesses contextos, a View utiliza
XAML como linguagem de marcação, o que permite descrever a UI de forma declarativa e intuitiva.
A View é responsável por exibir os dados ao usuário e capturar suas interações, como cliques em
botões ou entradas de texto. No entanto, não contém lógica de negócios nem manipulação de dados;
concentra‑se apenas na apresentação.
O ViewModel expõe propriedades e comandos aos quais a View pode se vincular. Através do
mecanismo de binding de dados do XAML, as propriedades do ViewModel são automaticamente
atualizadas na UI quando mudam, e as ações do usuário na UI podem ser traduzidas em comandos
tratados no ViewModel. Esse mecanismo de binding é um dos aspectos centrais do MVVM e é facilitado
pelas características do XAML, que suporta bindings expressivos e dinâmicos entre a UI e os dados
subjacentes. Além disso, a natureza declarativa do XAML torna mais direta e menos propensa a erros a
implementação de interfaces dinâmicas e reativas do usuário.
WPF, UWP e Xamarin.Forms são tecnologias distintas usadas no desenvolvimento de interfaces de
usuário, embora todas usem XAML como linguagem de marcação; todas têm peculiaridades e se destinam
a diferentes cenários de desenvolvimento. WPF é um framework que cria aplicações ricas para o sistema
operacional Windows. Lançada inicialmente com o .NET Framework 3.0, volta‑se principalmente para
aplicativos de desktop no Windows e oferece um sistema abrangente e consistente de design de UI,
incluindo um motor de renderização gráfica avançadoe suporte para gráficos vetoriais, animações e
uma variedade de controles de interface do usuário.
WPF é amplamente utilizada para desenvolver aplicações tradicionais de desktop com interfaces
complexas e customizáveis. Por outro lado, UWP é uma plataforma introduzida pela Microsoft com
o Windows 10; seu objetivo é permitir o desenvolvimento de aplicações que funcionem em todos os
dispositivos que executam Windows 10, incluindo PCs, tablets, Xbox, dispositivos com internet das coisas
e até mesmo HoloLens.
O UWP fornece uma base comum para todos esses dispositivos, permitindo uma experiência unificada.
Além disso, introduz um modelo de segurança mais robusto e um modelo de distribuição que passa
pela Microsoft Store, facilitando a instalação e atualização de aplicativos. O Xamarin.Forms, por sua
vez, é um framework que permite desenvolver aplicações móveis multiplataforma usando C# e XAML.
Diferente da WPF e da UWP – centradas no ecossistema Windows –, o Xamarin.Forms (que veremos em
mais detalhe no tópico 8.1) é projetado para criar aplicações que podem ser executadas em Android,
iOS e Windows com uma base de código compartilhada, o que permite aos desenvolvedores escrever a
lógica de negócios e a interface do usuário uma vez e implantá‑las em múltiplas plataformas móveis.
Embora ofereça uma maneira de criar interfaces de usuário com XAML, o Xamarin.Forms
tem um conjunto de controles e comportamentos otimizados para dispositivos móveis. Cada
uma dessas tecnologias tem seu próprio conjunto de ferramentas, bibliotecas e comunidades de
desenvolvedores. Enquanto WPF é ideal para aplicações Windows desktop complexas, UWP é mais
168
Unidade III
adequado para aplicações modernas que precisam funcionar em toda a família de dispositivos
Windows. Xamarin.Forms, por outro lado, é a escolha para desenvolvedores que buscam criar
aplicações móveis multiplataforma com uma base de código compartilhada. Embora todas elas
utilizem XAML, as particularidades de cada plataforma influenciam como essa linguagem é usada
e os recursos disponíveis.
5.4 Aprimoramento: projeto de interface desktop com XAML
Neste livro‑texto desenvolvemos um jogo que utiliza a interface de console para interagir com o
usuário (figura 54); num segundo momento, a interface foi adaptada para Windows Forms (figura 55).
Agora vamos reprojetar o aplicativo usando WPF com MVVM. A figura 60 ilustra o jogo na nova versão
(note que utilizamos o mesmo layout da versão Windows Forms):
Figura 60 – Os desafios dos mosqueteiros: duelos & destinos na versão WPF
Criar um aplicativo WPF no Visual Studio Community envolve várias etapas:
• Criar um novo projeto
— Abrir o Visual Studio: inicie o Visual Studio Community.
— Criar novo projeto: na tela inicial, clique em Criar um Projeto.
— Selecionar tipo de projeto: na caixa de pesquisa, digite “WPF” e selecione Aplicativo WPF
(.NET) da lista. Clique em Próximo.
• Configurar o projeto
— Configurar detalhes do projeto: forneça um nome para seu projeto, escolha o local para
salvá‑lo e defina o nome da solução. Clique em Próximo.
169
PROGRAMAÇÃO ORIENTADA A OBJETOS II
— Selecionar a versão do .NET: escolha a versão do .NET Framework que deseja usar.
Recomenda‑se a versão mais recente. Clique em Criar.
• Estrutura do projeto
— Criado o projeto, você verá a estrutura de soluções com os seguintes arquivos principais:
— App.xaml: ponto de entrada do aplicativo, usado para configuração global.
— MainWindow.xaml: a janela principal do seu aplicativo.
• Desenvolvendo a interface do usuário
— Editar MainWindow.xaml: abra o arquivo MainWindow.xaml. Aqui você pode usar XAML para
projetar a interface do usuário.
— Utilizar o designer: você pode arrastar e soltar controles da caixa de ferramentas para a
janela de design.
• Adicionar lógica de programação
— Código‑behind: abra o arquivo MainWindow.xaml.cs, que é o código‑behind para sua
janela principal. Aqui você pode adicionar a lógica de manipulação de eventos e outras
funcionalidades.
• Construir e executar o aplicativo
— Construir o projeto: vá para Build > Build Solution para compilar o projeto.
— Executar o aplicativo: pressione F5 ou clique em Start para executar o aplicativo.
• Depuração e testes
— Utilize as ferramentas de depuração do Visual Studio para testar e depurar seu aplicativo,
identificando e corrigindo bugs.
Para nosso jogo, chamamos o projeto de JogoWPF e a MainWindow de JanelaPrincipal. A figura 61
mostra o arquivo XAML usado na interface gráfica.
170
Unidade III
1.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
171
PROGRAMAÇÃO ORIENTADA A OBJETOS II
27.
28.
29.
35.
36.
37.
38.
41.
42.
44.
45.
46.
47.
48.
172
Unidade III
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.59.
60.
61.
63.
64.
65.
66.
67.
69.
70.
71.
72.
73.
Figura 61 – JanelaPrincipal.xaml
173
PROGRAMAÇÃO ORIENTADA A OBJETOS II
Para entendê‑lo, vamos analisar algumas partes. Na primeira linha temos a declaração de uma janela
que se estende até a linha 73. A palavra‑chave Window define uma janela no aplicativo WPF, e x:Class
especifica o nome da classe no código C# relacionada a essa janela. Nas linhas 2 a 6, xmlns define
os namespaces XML necessários para WPF e permitem o uso de diferentes elementos e propriedades
na interface.
A linha 7 define o título da janela e suas dimensões (altura e largura) iniciais. Entre as linhas 8 e 23
temos a definição de um recurso dentro da janela, neste caso um estilo personalizado para botões,
necessário porque o dado do jogo é uma imagem cujo formato não é retangular (figura 56), e o
botão‑padrão precisa ser ajustado visualmente para obter o efeito necessário.
O conteúdo principal da janela está entre as linhas 25 e 71, e a palavra‑chave Viewbox define
um container que pode escalar seu conteúdo. Ela não é obrigatória; nós a utilizamos para que
os elementos gráficos aumentem ou diminuam proporcionalmente de tamanho quando o usuário
expande (maximiza) ou reduz (minimiza) a janela do jogo. Já a palavra‑chave grid (linha 26) define uma
grade para organizar os elementos da interface.
Dentro da grade temos vários elementos, como:
• Image: define imagens a exibir.
• Button: estabelece um botão com estilo personalizado (TransparentButton) e eventos
associados como Click.
• TextBlock: exibe texto na tela.
• StackPanel: organiza elementos em pilhas, seja vertical, seja horizontalmente.
Muitos elementos (por exemplo, nas linhas 34, 35 e 38) usam {Binding …}. Conforme discutimos,
isso se chama data binding e conecta propriedades dos elementos da interface a propriedades ou
valores no código do aplicativo. Eventos como Click (linha 31), PreviewMouseDown (linha 33) etc. estão
associados a métodos no código C# que definem o comportamento quando o usuário interage com
esses elementos.
Para exemplificar o data binding, vamos analisar o elemento Button denominado BotaoDado, entre
as linhas 29 e 36. No contexto desse botão, o binding é empregado para integrar propriedades de estilo
e comportamento dinâmico, alinhando a interface de usuário com a lógica subjacente do aplicativo.
Primeiramente, vamos recordar o efeito desejado: quando um jogador clica no dado, queremos
que a imagem Dado.png seja substituída temporariamente por sua versão escura (DadoE.png), para
fornecer ao jogador um feedback visual do clique. Para isso, inicialmente observa‑se o uso do binding
na propriedade Style do botão; aqui o estilo TransparentButton, definido nos recursos da janela, é
aplicado ao botão. Esse estilo personalizado, encontrado dentro do , estabelece
características visuais específicas para o botão, como fundo e borda transparentes.
174
Unidade III
Essencialmente, esse estilo é referenciado pelo nome através do binding, permitindo reutilizar
configurações de estilo em múltiplos componentes da interface, garantindo consistência e facilitando
manutenções futuras. Ademais, o botão usa binding em sua visibilidade e na imagem exibida.
A propriedade Visibility do botão está vinculada a uma propriedade VisibilidadeDado no ViewModel.
Esse tipo de binding é crucial para a interatividade da interface, pois permite que mudanças no estado
ou na lógica do aplicativo se reflitam automaticamente na visibilidade do botão na interface de usuário.
Por exemplo, o botão pode ser exibido ou ocultado com base em certas condições de jogo definidas
no ViewModel. A troca efetiva da imagem exibida no botão é feita pela propriedade Source da Image
dentro do botão, ligada a uma propriedade ImagemDado no ViewModel.
Essa ligação dinâmica entre a fonte da imagem e uma propriedade no ViewModel permite que
a imagem do botão seja alterada programaticamente durante a execução do aplicativo, refletindo
diferentes estados ou ações no jogo. Além disso, o botão está configurado com eventos como Click
(dispara a rolagem do dado), PreviewMouseDown e PreviewMouseUp (trocam a imagem do dado),
ligados a métodos no código‑behind.
Entre as linhas 38 e 40, temos a definição da imagem da carta de evento – imagem também trocada
quando o jogador está em uma casa de evento. A imagem CartaVerso.png dá lugar à imagem CartaFace.png
(vide figura 56) até o próximo clique do mouse sobre a carta de evento. Para isso, o binding foi novamente
utilizado; a figura 60 mostra justamente o momento da substituição. Observe também que o dado foi
configurado para desaparecer quando a cartaFace.png está ativa, para o jogador clicar na carta, e não no dado.
Na sequência, entre as linhas 42 e 44, vemos a implementação de um TextBlock que irá exibir
as mensagens do jogo; como elas são dinâmicas, também foi utilizado o binding. Observe o uso da
propriedade Opacity para dar um efeito de transparência no fundo da mensagem; esse é um pequeno
exemplo de como nosso código XAML revela um cuidadoso trabalho na estilização de elementos de
interface de usuário, demonstrando como aspectos visuais são essenciais para a experiência do usuário
em um aplicativo WPF.
Olhando integralmente o código, é possível observar a aplicação de várias propriedades estéticas
que contribuem para a aparência e a funcionalidade da interface. Além do já citado estilo especial para
botões, há um uso significativo de propriedades estéticas nos TextBlocks. Eles estão configurados com
a fonte Old English Text MT e refletem o tema ou o estilo visual do jogo. O tamanho da fonte,
o peso (negrito) e a cor (branca) são ajustados para garantir que o texto seja legível e esteticamente
agradável contra o fundo, que é configurado com uma cor específica e, como vimos, uma opacidade de
0,8, permitindo uma leve transparência.
A propriedade TextWrapping=“Wrap” nos TextBlocks é outro detalhe importante, pois permite
que o texto seja quebrado em linhas conforme necessário, garantindo que o conteúdo seja exibido de
forma clara e completa, sem cortes. O alinhamento do texto e dos elementos (HorizontalAlignment
e VerticalAlignment) é cuidadosamente escolhido para posicionar os componentes na interface
de maneira harmoniosa e funcional.
175
PROGRAMAÇÃO ORIENTADA A OBJETOS II
Imagens são um componente visual chave nesse código, usadas não apenas como elementos
decorativos, mas também como partes interativas da interface, como evidenciado pelo botão com uma
imagem e pelas figuras que representam cartas e outros ícones. A propriedade Stretch=“Fill” nas imagens
garante que elas ocupem todo o espaço disponível no botão ou na área designada, adaptando‑se ao
layout sem distorcer as proporções originais. Finalmente, o layout geral é gerenciado usando Grid e
StackPanel, que são containers para organizar os elementos. O uso de Margin para espaçamento e a
escolha de Orientation nos StackPanels são essenciais para uma disposição eficaz e esteticamente
agradável dos componentes.
Detalhamos os principais elementos de interface, mas há outros, como o painel de informações
do jogador (linhas 46 a 59) e opções na audiência real (linhas 61 a 65) – estas devem ficar invisíveis
incialmente (Visibility=“Collapsed”) e só aparecerão na tela quando o jogador estiver na casa 60. Para
isso acontecer, o binding precisa ser implementado no XAML.
Você deve ter percebido que a interface não está completa: na linha 69 você pode implementar os
RadioButtons para escolher o oponente em duelo, que ficarão invisíveis na maior parte do tempo e só
deverão aparecer quando houver de fato um duelo. Também há outro data binding não implementado:
a troca de cenas (troca de imagem de fundo da janela). Lembre‑se que na versão Windows Forms a
imagem de fundo do formulário é trocada nos duelos, no reconhecimento da corte etc.
1. using JogoWPF.ViewModel;
2. using System;
3. using System.Collections.Generic;
4. using System.Linq;
5. using System.Text;
6. using System.Threading.Tasks;
7. using System.Windows;
8. using System.Windows.Controls;
9. using System.Windows.Data;
10. using System.Windows.Documents;
11. using System.Windows.Input;
12. using System.Windows.Media;
13. using System.Windows.Media.Imaging;
14. using System.Windows.Navigation;
15. using System.Windows.Shapes;
176
Unidade III
16.
17. namespace JogoWPF
18. {
19. ///
20. /// Interação lógica para JanelaPrincipal.xam
21. ///
22. public partial class JanelaPrincipal : Window
23. {
24. private JogoViewModel jogoViewModel;
25.
26. public JanelaPrincipal()
27. {
28. InitializeComponent();
29. jogoViewModel = new JogoViewModel();
30. this.DataContext = jogoViewModel; // Define o DataContext
da janela
31. }
32. private void RolarDado_Click(object sender, RoutedEventArgs e)
33. {
34. int resultadoDado = jogoViewModel.RolarDado();
35. jogoViewModel.MoverJogadorAtual(resultadoDado);
36. }
37.
38. private void BotaoDado_PreviewMouseDown(object sender,
MouseButtonEventArgs e)
39. {
40.
41. jogoViewModel.ImagemDado = “/DadoE.png”;
42. }
177
PROGRAMAÇÃO ORIENTADA A OBJETOS II
43.
44. private void BotaoDado_PreviewMouseUp(object sender,
MouseButtonEventArgs e)
45. {
46.
47. jogoViewModel.ImagemDado = “/Dado.png”;
48. }
49.
50. private void ImagemCarta_MouseDown(object sender,
MouseButtonEventArgs e)
51. {
52. jogoViewModel.ImagemCarta = “/CartaVerso.png”;
53. jogoViewModel.VisibilidadeDado = Visibility.Visible;
54. }
55. }
56. }
Figura 62 – JanelaPrincipal.xaml.cs
Como vimos, o código XAML da figura 61 define uma janela com vários elementos, como imagens,
botões e textos, organizados dentro de uma grade. A aparência e o comportamento desses elementos
são controlados por estilos, data binding e eventos. Esse é um exemplo típico de como as interfaces
de usuário são construídas em aplicativos WPF usando XAML. Convém lembrar que há um código C#
associado a essa janela. É o JanelaPrincipal.xaml.cs da figura 62.
O código‑behind é essencialmente a parte do código que define a lógica de interação e o
comportamento da interface de usuário definida no XAML. O arquivo descrito é um exemplo clássico
de como a lógica de interação entre a interface gráfica e a lógica de negócios é implementada no WPF
usando o padrão MVVM. Na linha 22, a classe JanelaPrincipal é declarada como parte do namespace
JogoWPF, definida como uma extensão da classe Window, que é a base para todas as janelas do WPF.
A extensão indica que JanelaPrincipal não é apenas uma janela comum, mas uma com características
e comportamentos específicos definidos pelo desenvolvedor.
As linhas 24 à 31 inicializam e configuram uma instância da JogoViewModel. Esse trecho é crucial
pois estabelece a ligação entre a interface do usuário e a lógica de negócios/modelo de dados do
aplicativo; a instância de JogoViewModel é criada e atribuída ao DataContext da janela. Esse passo é
178
Unidade III
fundamental no padrão MVVM, pois permite que propriedades e comandos definidos no ViewModel
sejam acessados e manipulados pela interface de usuário.
Observação
DataContext no WPF refere‑se à propriedade que define o contexto
de dados de um elemento da interface, como uma janela ou um controle.
Esse contexto de dados é normalmente um objeto com informações que
a interface de usuário precisa exibir ou com as quais precisa interagir.
O DataContext pode ser considerado a fonte‑padrão para as operações de
binding (ligação de dados) dentro de determinado escopo da interface.
A importância do DataContext é evidente quando se deseja separar a
lógica de apresentação da lógica de negócios de uma aplicação. Por exemplo,
ao desenvolver um aplicativo usando o padrão MVVM, a ViewModel atua
como camada intermediária entre a View (interface de usuário) e o Model
(dados/negócio). Nesse cenário, a ViewModel é frequentemente definida
como o DataContext para a View. Isso significa que, quando os elementos
da interface de usuário precisam exibir ou interagir com dados, eles se
referirão ao DataContext (a ViewModel) para essas informações.
No código da figura 62, o DataContext é estabelecido para a janela
principal do aplicativo. Ao definir o DataContext como instância do
JogoViewModel, todas as propriedades e comandos definidos na ViewModel
estão agora disponíveis para binding na interface de usuário. Isso significa
que elementos como botões, campos de texto e outros controles na
interface de usuário podem ser facilmente ligados a propriedades e métodos
definidos na ViewModel, permitindo uma interação dinâmica e responsiva
entre a interface de usuário e a lógica subjacente do aplicativo.
As linhas 32 à 36 definem o método RolarDado_Click, um manipulador de eventos para um
botão da interface de usuário. Quando o botão é clicado, esse método é invocado, e dentro dele a
lógica para rolar um dado virtual é executada chamando RolarDado() no ViewModel. O resultado é
usado para mover o jogador atual no jogo através da chamada de MoverJogadorAtual.
Isso ilustra uma interação típica em aplicativos WPF, onde ações do usuário na interface gráfica
desencadeiam lógicas de negócio no código por trás da cena. Entre as linhas 38 e 48 estão os
métodos BotaoDado_PreviewMouseDown e BotaoDado_PreviewMouseUp – manipuladores para
eventos de mouse relacionados ao botão do dado. O primeiro método altera a imagem do dado
para “/DadoE.png” quando o botão é pressionado, e o segundo reverte para “/Dado.png” quando o botão
é solto. Esse comportamento fornece um feedback visual para o usuário, indicando a interação com o
botão do dado.
179
PROGRAMAÇÃO ORIENTADA A OBJETOS II
Finalmente, nas linhas 50 a 54 o método ImagemCarta_MouseDown é definido para lidar com
eventos de clique sobre uma imagem de carta. Aqui a imagem da carta é alterada para “/CartaVerso.png”,
e a visibilidade de um dado é configurada para visível. Esse trecho implementa uma interação na qual
clicar na carta revela seu verso.
Em relação ao restante da estrutura do código, estabelecemos uma organização que facilita
o desenvolvimento do JogoWPF ao permitir uma clara separação entre a lógica de negócios, a
lógica de apresentação e a interface do usuário (a figura 63 ilustra essa organização). As alterações
no estado do jogo são gerenciadas pelo ViewModel, que por sua vez atualiza a View, permitindo
que a interface do usuário seja alterada dinamicamente em resposta a mudanças nos dados, sem
necessidade de manipulação direta dos elementos da interface, resultando em um código mais
limpo, organizado e fácil de manter.
Figura 63 – Organização das classes do MVVM no painel Gerenciador de Soluções
Nosso Model é representado por classes como CartaModel, DadoModel, JogadorModel, RoletaModel
e TabuleiroModel, que encapsulam a lógica de negócios e os dados do jogo, como as regras para rolar
dados, os efeitos das cartas e o estado dos jogadores. Por exemplo, o DadoModel gerencia a lógica para
simular o lançamento de um dado, enquanto o JogadorModel mantém informações sobre cada jogador,
como nome, fortuna, honra e posição no tabuleiro.
180
Unidade III
No aspecto da ViewModel, temos o JogoViewModel, que atua como intermediário entre o Model e
a View. Esse componente gerencia o estado da aplicação, processa a lógica necessária para responder
às ações do usuário e atualiza a interface dele através de mecanismos de binding. Por exemplo,o
JogoViewModel manipula ações como rolar um dado ou girar a roleta e atualiza a interface com
informações sobre o estado atual do jogo.
Lembrete
A View do jogo é composta por JanelaPrincipal e JogadorView, que
definem a interface gráfica com a qual os usuários interagem. Esse
componente utiliza XAML para definir a estrutura e o layout da interface, e
se conecta ao JogoViewModel através de bindings, permitindo a atualização
dinâmica da interface com base nas mudanças de estado no ViewModel.
Considerando essa estrutura, vamos analisar algumas classes do Model. O modelo denominado
TabuleiroModel (figura 64) está contido dentro do namespace JogoWPF.Model, indicando que faz
parte de uma aplicação de jogo desenvolvida com WPF. Ele é declarado como uma classe interna com
visibilidade limitada (internal), o que sugere destinar‑se a ser utilizado apenas dentro do assembly em
que foi definido.
Essa classe tem duas propriedades principais: Casas e CartasDeEvento. A primeira é um array
do tipo CasaTabuleiro e representa as diversas posições ou casas no tabuleiro do jogo, sendo
inicializado com 60 elementos na linha 17 (uma vez que o tabuleiro do jogo tem 60 casas). Cada
casa no tabuleiro é representada por uma instância da classe interna CasaTabuleiro.
A classe CasaTabuleiro é definida dentro de TabuleiroModel e contém uma propriedade Descricao,
que serve para descrever o que acontece ou o que é representado por aquela casa específica no jogo.
A propriedade Tipo é um enum TipoCasa, que define os vários tipos de casa que podem existir no
tabuleiro. Esse enum inclui tipos como Normal, Inicio, Evento, EscolaDeEsgrima, DueloDeHonra, entre
outros, indicando uma variedade de eventos ou situações possíveis, dependendo da casa em que o
jogador cai (note como os códigos das versões anteriores são reutilizados).
181
PROGRAMAÇÃO ORIENTADA A OBJETOS II
1. using System;
2. using System.Collections.Generic;
3. using System.Collections.ObjectModel;
4. using System.Linq;
5. using System.Text;
6. using System.Threading.Tasks;
7. using System.Windows;
8.
9. namespace JogoWPF.Model
10. {
11. internal class TabuleiroModel
12. {
13. public CasaTabuleiro[] Casas { get; private set; }
14.
15. public TabuleiroModel()
16. {
17. Casas = new CasaTabuleiro[60]; // Tabuleiro com 60 casas
18.
19. }
20.
21. // Classe interna para representar cada casa do tabuleiro
22. public class CasaTabuleiro
23. {
24. public string Descricao { get; set; }
25. public TipoCasa Tipo { get; set; }
26.
27. public enum TipoCasa
28. {
182
Unidade III
29. Normal,
30. Inicio,
31. Evento,
32. EscolaDeEsgrima,
33. DueloDeHonra,
34. Duelo,
35. Estalagem,
36. TributoAoCardeal,
37. Retorno,
38. AudienciaComRei,
39. //... outros tipos conforme necessário...
40. }
41. }
42. }
43. }
Figura 64 – TabuleiroModel.cs
Já na figura 65 temos o modelo simplificado da carta. Essa classe também está contida no
namespace JogoWPF.Model, indicando sua função dentro do contexto de um jogo; assim como
a classe TabuleiroModel, ela é marcada como interna (internal). Dentro da classe, existem duas
propriedades públicas: Descricao e Efeito. A primeira é uma string que contém uma descrição
textual da carta, explicando seu propósito ou efeito no jogo. A visibilidade dessa propriedade é
pública para leitura, mas privada para escrita (private set), ou seja, a descrição pode ser lida por
outras partes do programa, mas só pode ser definida dentro da classe CartaModel.
A segunda propriedade é do tipo Action, uma delegação em C# que permite definir
um método que será executado quando a carta for utilizada no jogo. O tipo JogadorModel sugere que
o efeito da carta é aplicado a uma instância de um jogador; assim como a Descricao, esta também é
pública para leitura, mas sua definição é restrita ao contexto da classe CartaModel.
O construtor da classe CartaModel aceita dois parâmetros – descricao e efeito –, usados para inicializar
as propriedades Descricao e Efeito, respectivamente. Essa abordagem garante que todas as instâncias
de CartaModel serão criadas com uma descrição e um efeito definidos, respeitando as regras do jogo.
183
PROGRAMAÇÃO ORIENTADA A OBJETOS II
1. using System;
2. using System.Collections.Generic;
3. using System.Linq;
4. using System.Text;
5. using System.Threading.Tasks;
6.
7. namespace JogoWPF.Model
8. {
9. internal class CartaModel
10. {
11. public string Descricao { get; private set; }
12. public Action Efeito { get; private set; }
13.
14. public CartaModel(string descricao, Action efeito)
15. {
16. Descricao = descricao;
17. Efeito = efeito;
18. }
19. }
20. }
Figura 65 – CartaModel.cs
A classe DadoModel (figura 66) é uma representação abstrata de um dado em um jogo de WPF,
encapsulando a lógica para gerar um resultado aleatório de um lançamento de dado. Ela utiliza um
provedor de números aleatórios chamado RandomProvider, acessado por RandomProvider.Instance.
Essa abordagem sugere que RandomProvider é uma implementação do padrão de design singleton,
que garante uma única instância global dessa classe durante toda a execução da aplicação, e seu uso
para gerar números aleatórios é uma decisão de design para garantir consistência e evitar problemas
comuns associados à criação de múltiplas instâncias de geradores de números aleatórios.
O método RolarDado continua a ser o foco da classe DadoModel – método que não recebe parâmetros
e retorna um inteiro. Ao invocar RandomProvider.Instance.Next(1, 7), ele gera um número aleatório
184
Unidade III
entre 1 e 6, simulando o lançamento de um dado. A faixa de valores é inclusiva para o limite inferior (1)
e exclusiva para o superior (7), por isso gera números de 1 a 6, valores típicos em um dado comum.
1. using System;
2. using System.Collections.Generic;
3. using System.Linq;
4. using System.Text;
5. using System.Threading.Tasks;
6. using JogoWPF.Utils; // Import necessário para acessar RandomProvider
7.
8. namespace JogoWPF.Model
9. {
10. internal class DadoModel
11. {
12. public int RolarDado()
13. {
14. return RandomProvider.Instance.Next(1, 7); // Usa a
instância única de Random
15. }
16. }
17. }
Figura 66 – DadoModel.cs
Embora o MVVM forneça estrutura para organizar o código, ele não dispensa outras práticas e
padrões de engenharia de software, como classes de serviço e utilidades. Portanto, criar classes que não
se enquadram estritamente nas categorias Model, View ou ViewModel (como as classes de utilidade
ou serviço) é uma prática aceitável e muitas vezes necessária no desenvolvimento de software.
Para evitar a duplicação da instância Random nas classes DadoModel, RoletaModel e JogoViewModel,
criamos uma única instância compartilhada de Random num local centralizado e acessível a todas
essas classes. A figura 67 mostra a classe estática que fornece acesso global a uma única instância de
Random, e criamos a pasta Utils no projeto (figura 63) para comportar essa classe.
185
PROGRAMAÇÃO ORIENTADA A OBJETOS II
Usar classes de serviço ou utilidades não viola os princípios do MVVM; pelo contrário, ajuda a organizar
o código, reutilizá‑lo e facilita sua manutenção. Essas classes ajudam a evitar a duplicação de código e
podem promover melhores práticas, como injeção de dependência. Classes de serviço são comuns em
aplicações mais complexas, especialmente as que utilizam injeção de dependência e princípios SOLID,
podendo incluir lógicas como acesso a banco de dados, operações de rede, manipulação de arquivos,
serviços de log e assim por diante.
1. using System;
2. using System.Collections.Generic;
3. using System.Linq;
4. using System.Text;
5. using System.Threading.Tasks;
6.
7. namespace JogoWPF.Utils
8. {
9. internal class RandomProvider
10. {
11. private static Random _instance = new Random();
12.13. public static Random Instance
14. {
15. get { return instance; }
16. }
17. }
18. }
Figura 67 – RandomProvider.cs
O padrão singleton garante que uma classe tenha apenas uma instância em toda a aplicação e
forneça um ponto global de acesso a ela. No caso de RandomProvider, isso é evidenciado pela criação
de um campo estático privado _instance, inicializado com uma nova instância da classe Random do
.NET Framework. A classe Random é comumente usada para gerar números aleatórios, e aqui serve
como base para o provedor de números aleatórios do jogo; já a propriedade Instance é a porta de acesso
pública para a instância de Random. Como é uma propriedade estática, ela permite que o código em
qualquer parte do jogo acesse a mesma instância de Random através de RandomProvider.Instance.
186
Unidade III
A classe RoletaModel da figura 68, definida como interna, tem a função de encapsular a lógica
específica da roleta dentro do jogo. Dentro da classe, a primeira definição é a da enumeração
ResultadoRoleta, que define três possíveis estados ou resultados: Vitoria, Empate e Derrota. O método
GirarRoleta é o foco da classe, não requer argumentos e retorna um valor do tipo ResultadoRoleta.
A lógica dentro do método determina o resultado de uma jogada na roleta, e para isso ele utiliza
uma instância de RandomProvider (note o uso de JogoWPF.Utils na linha 1). A chamada RandomProvider.
Instance.Next(3) gera um número inteiro entre 0 e 2, cada um correspondendo a um dos resultados
definidos na enumeração ResultadoRoleta. O número inteiro gerado é então convertido de volta em um
valor da enumeração ResultadoRoleta usando uma conversão explícita e retornado pelo método.
1. using JogoWPF.Utils;
2. using System;
3. using System.Collections.Generic;
4. using System.Linq;
5. using System.Text;
6. using System.Threading.Tasks;
7.
8. namespace JogoWPF.Model
9. {
10. internal class RoletaModel
11. {
12.
13. public enum ResultadoRoleta
14. {
15. Vitoria,
16. Empate,
17. Derrota
18. }
19.
20. public ResultadoRoleta GirarRoleta()
21. {
187
PROGRAMAÇÃO ORIENTADA A OBJETOS II
22. int resultado = RandomProvider.Instance.Next(3); // Gera
0, 1 ou 2
23. return (ResultadoRoleta)resultado;
24. }
25. }
26. }
Figura 68 – RoletaModel.cs
Finalizando a análise das classes do nosso modelo, a figura 69 mostra JogadorModel. Dentro da
classe há quatro propriedades públicas: Nome, Fortuna, Honra e PosicaoAtual. Essas propriedades são
definidas com acessadores get e set, permitindo a leitura e modificação dos valores. Nome é uma string
que representa o nome do jogador; Fortuna e Honra são representadas como inteiros, inicializadas com
o valor de cinco, pois cada jogador começa o jogo com cinco fichas de fortuna e honra, respectivamente.
PosicaoAtual, também um inteiro, indica a posição do jogador no tabuleiro, começando do ponto zero.
O construtor da classe JogadorModel aceita um parâmetro nome e é usado para inicializar a
propriedade Nome do jogador. Além disso, define os valores iniciais para Fortuna, Honra e PosicaoAtual,
estabelecendo seu estado inicial na partida.
1. using System;
2. using System.Collections.Generic;
3. using System.Linq;
4. using System.Text;
5. using System.Threading.Tasks;
6.
7. namespace JogoWPF.Model
8. {
9. internal class JogadorModel
10. {
11. public string Nome { get; set; }
12. public int Fortuna { get; set; }
13. public int Honra { get; set; }
14. public int PosicaoAtual { get; set; }
188
Unidade III
15.
16. // Construtor
17. public JogadorModel(string nome)
18. {
19. Nome = nome;
20. Fortuna = 5; // Cada jogador começa com 5 fichas de Fortuna
21. Honra = 5; // Cada jogador começa com 5 fichas de Honra
22. PosicaoAtual = 0; // Posição inicial no tabuleiro
23. }
24. public void AlterarFortuna(int valor)
25. {
26. Fortuna = Math.Max(0, Fortuna + valor);
27. }
28.
29. // Método para alterar a honra
30. public void AlterarHonra(int valor)
31. {
32. Honra = Math.Max(0, Honra + valor);
33. }
34. public void PerderTurno()
35. {
36. // Implemente a lógica para o jogador perder um turno
37. }
38. public void Mover(int casas)
39. {
40. PosicaoAtual += casas;
41. // Lógica adicional para lidar com a movimentação
do jogador no tabuleiro
189
PROGRAMAÇÃO ORIENTADA A OBJETOS II
42. }
43. }
44. }
Figura 69 – JogadorModel.cs
Dois métodos – AlterarFortuna (linha 24) e AlterarHonra (linha 30) – aceitam um parâmetro valor do
tipo inteiro. Eles modificam as propriedades Fortuna e Honra do jogador, e utilizam a função Math.Max
para garantir que esses valores nunca caiam abaixo de zero, o que adiciona uma regra de negócio ao
jogo e impede que os jogadores tenham valores negativos de fortuna ou honra. O método PerderTurno
(linha 34) é declarado, mas sua implementação está ausente, indicando que a lógica para o jogador
perder um turno ainda precisa ser desenvolvida; esse método implementa um mecanismo em que os
jogadores podem ser penalizados perdendo turnos (vide quadros 3 e 4).
Para concluir, o método Mover altera a PosicaoAtual do jogador no tabuleiro e aceita um inteiro
casas, que representa o número de casas pelas quais ele deve se mover. A lógica adicional para lidar com
a movimentação no tabuleiro é mencionada, mas não implementada no trecho de código fornecido.
Definimos três classes do ViewModel (TabuleiroViewModel, JogadorViewModel e JogoViewModel)
no namespace JogoWPF.ViewModel. A classe TabuleiroViewModel – detalhada na figura 70 – gerencia
as interações com o tabuleiro do jogo. Ela mantém uma referência ao TabuleiroModel, que contém
o estado atual do tabuleiro. Inicialmente, a classe declara uma propriedade pública Tabuleiro do tipo
TabuleiroModel, que armazena o estado atual do tabuleiro e é definida como privada para atribuição, ou
seja, só pode ser modificada dentro da classe TabuleiroViewModel.
Além disso, ela declara uma série de eventos – como ProcessarEventoAleatorio, IniciarDueloDeHonra,
GirarRoleta, entre outros – que permitem a comunicação com outras partes do programa quando
ações específicas ocorrem no tabuleiro, como a execução de eventos aleatórios ou o início de duelos.
Esses eventos são fundamentais para a dinâmica interativa do jogo e permitem que diferentes
componentes respondam a mudanças no estado do tabuleiro.
1. using JogoWPF.Model;
2. using System;
3. using System.Collections.Generic;
4. using System.Linq;
5. using System.Text;
6. using System.Threading.Tasks;
7.
190
Unidade III
8. namespace JogoWPF.ViewModel
9. {
10. internal class TabuleiroViewModel
11. {
12. public TabuleiroModel Tabuleiro { get; private set; }
13.
14. public event Action ProcessarEventoAleatorio;
15. public event Action IniciarDueloDeHonra;
16. public event Func GirarRoleta;
17. public event Action
ProcessarResultadoRoleta;
18. public event Action ProcessarTributoCardeal;
19. public event Action FinalizarJornada;
20. public event Action AtualizarMensagemCentral;
21.
22. public TabuleiroViewModel(TabuleiroModel tabuleiro)
23. {
24. Tabuleiro = tabuleiro;
25. }
26.
27. public void ExecutarAcaoCasa(JogadorModel jogador)
28. {
29. var casaAtual = Tabuleiro.Casas[jogador.PosicaoAtual];
30.
31. // Lógica para lidar com diferentes tipos de casas
32. switch (casaAtual.Tipo)
33. {
34. case TabuleiroModel.CasaTabuleiro.TipoCasa.Inicio:
191
PROGRAMAÇÃO ORIENTADA A OBJETOS II
35. // Lógica para a casa de início
36. break;
37. case TabuleiroModel.CasaTabuleiro.TipoCasa.Evento:
38. ProcessarEventoAleatorio?.Invoke(jogador);
39. break;
40. case
TabuleiroModel.CasaTabuleiro.TipoCasa.EscolaDeEsgrima:
41.jogador.AlterarHonra(1); // Incrementa a honra do
jogador
42. break;
43. case TabuleiroModel.CasaTabuleiro.TipoCasa.DueloDeHonra:
44. IniciarDueloDeHonra?.Invoke(jogador);
45. break;
46. case TabuleiroModel.CasaTabuleiro.TipoCasa.Duelo:
47. var resultadoDuelo = GirarRoleta?.Invoke();
48. if (resultadoDuelo.HasValue)
49. {
50.
ProcessarResultadoRoleta?.Invoke(resultadoDuelo.Value);
51. }
52. break;
53. case TabuleiroModel.CasaTabuleiro.TipoCasa.Estalagem:
54. jogador.AlterarFortuna(-1); // Decrementa a fortuna
do jogador
55. break;
56. case
TabuleiroModel.CasaTabuleiro.TipoCasa.TributoAoCardeal:
57. ProcessarTributoCardeal?.Invoke(jogador);
58. break;
59. case TabuleiroModel.CasaTabuleiro.TipoCasa.Retorno:
192
Unidade III
60. jogador.Mover(5 – jogador.PosicaoAtual); // Move o
jogador para a casa 5
61. break;
62. case
TabuleiroModel.CasaTabuleiro.TipoCasa.AudienciaComRei:
63. FinalizarJornada?.Invoke(jogador);
64. break;
65. // Pode adicionar mais casos para outros tipos de
casas se necessário
66. }
67. string mensagemAdicional = $”Você está na
{casaAtual.Descricao}.”;
68. AtualizarMensagemCentral?.Invoke(mensagemAdicional); // Chame
este evento
69. }
70. // Métodos adicionais conforme necessário
71. }
72. }
Figura 70 – TabuleiroViewModel.cs
O construtor da classe recebe um objeto TabuleiroModel e o atribui à propriedade Tabuleiro. Isso indica
que, para criar uma instância de TabuleiroViewModel, é necessário fornecer o estado atual do tabuleiro,
garantindo que o ViewModel comece com uma representação válida do jogo. Um método‑chave na
classe é ExecutarAcaoCasa, chamado quando um jogador se move no tabuleiro. Esse método verifica
o tipo de casa em que o jogador caiu e executa a ação correspondente. A lógica utiliza uma estrutura
switch que avalia o tipo de cada casa no tabuleiro, como casas que iniciam eventos aleatórios, duelos de
honra, ou que afetam a fortuna ou a honra do jogador.
Essa abordagem permite uma gestão eficaz das várias ações possíveis no tabuleiro, tornando o
código organizado e de fácil manutenção. Por exemplo, se o jogador cair numa casa que representa um
evento, o ProcessarEventoAleatorio é acionado, passando‑o como argumento. Se a casa for uma escola
de esgrima, sua honra é incrementada; esse método reflete a complexidade do tabuleiro do jogo, onde
cada casa tem um impacto significativo no progresso do jogador.
Finalmente, a classe TabuleiroViewModel comunica as ações do tabuleiro ao usuário, atualizando
uma mensagem central através do evento AtualizarMensagemCentral. Isso mostra como a classe não
apenas gerencia o estado do jogo, mas também interage com a interface do usuário, mantendo os
jogadores informados sobre o que está acontecendo.
193
PROGRAMAÇÃO ORIENTADA A OBJETOS II
Abordaremos agora uma classe mais modesta: a JogadorViewModel.
1. using JogoWPF.Model;
2. using System;
3. using System.Collections.Generic;
4. using System.Linq;
5. using System.Text;
6. using System.Threading.Tasks;
7.
8. namespace JogoWPF.ViewModel
9. {
10. internal class JogadorViewModel
11. {
12. public JogadorModel Jogador { get; private set; }
13.
14. public JogadorViewModel(JogadorModel jogador)
15. {
16. Jogador = jogador;
17. }
18.
19. public void Mover(int casas)
20. {
21. Jogador.PosicaoAtual += casas;
22. // Lógica adicional para lidar com a movimentação do
jogador no tabuleiro
23. }
24. // Métodos adicionais conforme necessário
25. }
26. }
Figura 71 – JogadorViewModel.cs
194
Unidade III
Na linha 12 a propriedade pública Jogador é o elemento central da classe JogadorViewModel e
armazena uma referência ao JogadorModel, que contém o estado e os dados do jogador na partida.
A propriedade é definida como privada para atribuição (indicada pelo private set), e o construtor
da classe JogadorViewModel aceita um parâmetro do tipo JogadorModel, utilizado para inicializar
a propriedade Jogador. Essa abordagem assegura que cada instância do JogadorViewModel esteja
vinculada a um objeto específico do JogadorModel, criando uma ligação direta entre a representação
do jogador no modelo de dados e sua gestão na camada de visualização.
Um aspecto fundamental da classe é o método Mover, que recebe um inteiro casas como parâmetro,
sendo responsável por atualizar a posição do jogador no tabuleiro, incrementando a propriedade
PosicaoAtual do JogadorModel com o número de casas especificado. A lógica adicional para lidar com
a movimentação do jogador no tabuleiro, mencionada nos comentários, serve para regras adicionais ou
efeitos colaterais da movimentação que devem ser implementados, como verificar condições de vitória,
interagir com casas especiais no tabuleiro ou aplicar penalidades e recompensas.
Embora a classe JogadorViewModel apresentada seja relativamente simples (com apenas um método
além do construtor), ela estabelece uma fundação para a expansão com métodos adicionais conforme a
necessidade do jogo, os quais poderiam incluir funcionalidades como interagir com outros componentes,
atualizar o estado do jogador baseado em ações específicas ou comunicar mudanças no seu estado para
outras partes do sistema.
Para finalizar o ModelView, precisamos entender a classe JogoViewModel, que é o núcleo do
jogo, coordenando a interação entre os modelos de dados, a lógica do jogo e a interface de usuário.
Ele gerencia o estado do jogo, responde a ações dos jogadores e atualiza a interface de usuário conforme
necessário, exemplificando os princípios do MVVM num contexto de jogo complexo e interativo.
A classe, cujo fragmento inicial pode ser visualizado na figura 72, começa importando várias
bibliotecas necessárias para sua funcionalidade, incluindo JogoWPF.Model e JogoWPF.Utils, indicando
sua dependência de outras partes do projeto. Ela implementa a interface INotifyPropertyChanged,
fundamental no MVVM para comunicar mudanças no estado do ViewModel para a interface de usuário,
permitindo atualizações dinâmicas. Em outras palavras, a interface INotifyPropertyChanged entra como
mecanismo para informar a View sobre alterações nos dados contidos no ViewModel.
195
PROGRAMAÇÃO ORIENTADA A OBJETOS II
1. using JogoWPF.Model;
2. using JogoWPF.Utils;
3. using System;
4. using System.Collections.Generic;
5. using System.Collections.ObjectModel;
6. using System.ComponentModel;
7. using System.Linq;
8. using System.Text;
9. using System.Threading.Tasks;
10. using System.Windows;
11.
12. namespace JogoWPF.ViewModel
13. {
14. internal class JogoViewModel : INotifyPropertyChanged
15. {
16. public event PropertyChangedEventHandler PropertyChanged;
17.
18. private string imagemDado = “/Dado.png”;
19. public string ImagemDado
20. {
21. get { return _imagemDado; }
22. set
23. {
24. if (_imagemDado != value)
25. {
26. _imagemDado = value;
27. OnPropertyChanged(nameof(ImagemDado));
28. }
196
Unidade III
29. }
30. }
31.
32. protected void OnPropertyChanged(string name)
33. {
34. PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs(name));
35. }
36.
37. private string _mensagemCentral;
38. public string MensagemCentral
39. {
40. get { return mensagemCentral; }
41. set
42. {
43. if (mensagemCentral != value)
44. {
45. _mensagemCentral = value;
46. OnPropertyChanged(nameof(MensagemCentral));
47. }
48. }
49. }
50. private int honra;
51. private int Honra
52. {
53. get { return honra; }
54. set
55. {
197
PROGRAMAÇÃO ORIENTADA A OBJETOS II
56. honra = value;
57. OnPropertyChanged(nameof(Honra));
58. }
59. }
60.
61. private int fortuna;
62. private int Fortuna63. {
64. get { return fortuna; }
65. set
66. {
67. fortuna = value;
68. OnPropertyChanged(nameof(Fortuna));
69. }
70. }
71. private string _imagemCarta = “/CartaVerso.png”;
72. private string ImagemCarta
73. {
74. get => imagemCarta;
75. set
76. {
77. imagemCarta = value;
78. OnPropertyChanged(nameof(ImagemCarta));
79. }
80. }
81.
82. private Visibility _visibilidadeDado = Visibility.Visible;
83. public Visibility VisibilidadeDado
198
Unidade III
84. {
85. get => _visibilidadeDado;
86. set
87. {
88. _visibilidadeDado = value;
89. OnPropertyChanged(nameof(VisibilidadeDado));
90. }
91. }
92.
93. public void MoverJogadorAtual(int casas)
94. {
95. JogadorAtual.PosicaoAtual = (JogadorAtual.PosicaoAtual + casas) %
60;
96. MensagemCentral = $”{JogadorAtual.Nome} moveu {casas} casas e
está na posição {JogadorAtual.PosicaoAtual}.”;
97. tabuleiroViewModel.ExecutarAcaoCasa(JogadorAtual);
98. ProximoTurno();
99. }
100. // Demais métodos...
101. }
102. }
Figura 72 – JogoViewModel.cs: fragmento inicial
Quando um ViewModel implementa INotifyPropertyChanged, ele deve fornecer um evento chamado
PropertyChanged, acionado sempre que o valor de uma propriedade do ViewModel for alterado. Para
cada propriedade cujas mudanças necessitam ser refletidas na View, o ViewModel chama o evento
PropertyChanged, passando informações sobre qual propriedade foi alterada.
Na prática, isso é frequentemente realizado com um método auxiliar, como o OnPropertyChanged,
que verifica se o evento PropertyChanged tem algum assinante e, em caso afirmativo, o aciona com
os detalhes da propriedade modificada. Esse método é geralmente chamado dentro dos setters das
propriedades do ViewModel. Por exemplo, ao alterar o valor da propriedade MensagemCentral, o
199
PROGRAMAÇÃO ORIENTADA A OBJETOS II
setter da propriedade não apenas atualiza o valor interno, mas também invoca OnPropertyChanged,
notificando assim a interface de usuário de que MensagemCentral foi alterada.
O processo permite que a View reaja automaticamente às mudanças no ViewModel. Num
contexto de WPF, a interface de usuário é frequentemente definida em XAML, na qual propriedades do
ViewModel podem ser vinculadas a elementos da UI usando data binding (como fizemos na figura 61).
Quando uma propriedade vinculada muda no ViewModel e o evento PropertyChanged é acionado, o
WPF automaticamente atualiza o elemento da UI correspondente com o novo valor. Esse mecanismo
de atualização automática é fundamental para criar interfaces de usuário reativas e dinâmicas que
respondem ao estado atual do jogo ou da aplicação.
A classe JogoViewModel tem várias propriedades, como ImagemDado, MensagemCentral, Honra,
Fortuna, ImagemCarta e VisibilidadeDado, mapeadas na JanelaPrincipal.xaml (figura 61). Cada
propriedade segue um padrão típico em MVVM: todas têm um campo privado correspondente e um
acessador público.
Quando o valor do campo é modificado através do acessador, o método OnPropertyChanged é
chamado. Esse método é responsável por notificar a view sobre a mudança da propriedade, permitindo que
a interface do usuário se atualize automaticamente, o que é feito invocando o evento PropertyChanged,
que é parte da interface INotifyPropertyChanged. Por exemplo, a propriedade ImagemDado tem um
campo privado _imagemDado (linha 18 da figura 72) que armazena o caminho da imagem de um dado.
O método get retorna o valor atual desse campo, enquanto o método set atualiza o valor e chama
OnPropertyChanged se o novo valor for diferente do antigo. Isso garante que a interface do usuário
reflita as alterações feitas na imagem do dado.
Além disso, a classe contém o método MoverJogadorAtual (linha 93), responsável por mover
um jogador no tabuleiro, atualizar a posição do jogador, alterar a MensagemCentral para refletir o
movimento (linha 96) e executar outras ações, como ExecutarAcaoCasa e ProximoTurno, relacionadas à
lógica específica do jogo.
Dentre suas propriedades principais, JogoViewModel gerencia coleções de CartaModel
e JogadorModel. Por sua vez, as propriedades CartasDeEvento e Jogadores são instâncias de
ObservableCollection, uma coleção especial que notifica a view sobre mudanças na sua composição
(como adição ou remoção de itens). Isso é especialmente útil em interfaces gráficas, onde as mudanças
na coleção devem refletir imediatamente na interface do usuário. CartasDeEvento guarda objetos do
tipo CartaModel, que indicam as cartas do jogo de tabuleiro, enquanto Jogadores contém objetos
do tipo JogadorModel e representa os participantes. Além disso, mantém referências a TabuleiroModel,
DadoModel e RoletaModel, refletindo os principais componentes do jogo.
A figura 73 ilustra a continuação do código da classe e explicita essas propriedades e referências.
200
Unidade III
1. // Propriedades vinculadas
2. public int ValorHonra { get; set; }
3. public int ValorFortuna { get; set; }
4.
5. public ObservableCollection CartasDeEvento { get;
private set; }
6. public ObservableCollection Jogadores { get;
private set; }
7. public TabuleiroModel Tabuleiro { get; private set; }
8. public DadoModel Dado { get; private set; }
9. public RoletaModel Roleta { get; private set; }
10.
11. private int jogadorAtualIndex;
12. public JogadorModel JogadorAtual => Jogadores[jogadorAtualIndex];
13. private TabuleiroViewModel tabuleiroViewModel;
14.
15.
16. public JogoViewModel()
17. {
18. Jogadores = new ObservableCollection();
19. Tabuleiro = new TabuleiroModel();
20. Dado = new DadoModel();
21. Roleta = new RoletaModel();
22. jogadorAtualIndex = 0;
23. MensagemCentral = “Bem-vindo ao jogo dos Mosqueteiros!”;
24.
25.
26. // Inicializar jogadores
27. InicializarJogadores();
201
PROGRAMAÇÃO ORIENTADA A OBJETOS II
28.
29. // Configurar o tabuleiro
30. ConfigurarTabuleiro();
31.
32. CartasDeEvento = new ObservableCollection();
33. InicializarCartasDeEvento();
34. // Adicione jogadores e configure o tabuleiro conforme necessário
35.
36. SetupTabuleiroViewModel();
37. }
38.
39. private void InicializarJogadores()
40. {
41. Jogadores.Add(new JogadorModel(“D’Artagnan”));
42. Jogadores.Add(new JogadorModel(“Athos”));
43. Jogadores.Add(new JogadorModel(“Porthos”));
44. Jogadores.Add(new JogadorModel(“Aramis”));
45. }
46. // Demais métodos
Figura 73 – JogoViewModel.cs: continuação do código (segunda parte)
O índice do jogador atual, jogadorAtualIndex, rastreia quem está atualmente jogando, e a propriedade
JogadorAtual fornece acesso direto ao modelo daquele em turno. A lógica de turno é uma parte crítica
do fluxo do jogo e indica a progressão entre jogadores; já construtor da classe inicializa as coleções e
configura o estado inicial do jogo. Métodos como InicializarJogadores e ConfigurarTabuleiro preparam o
cenário para o início do jogo, adicionando jogadores e definindo as características das casas do tabuleiro,
respectivamente.
O método SetupTabuleiroViewModel é vital porque configura o TabuleiroViewModel, vinculando
eventos a manipuladores específicos. Esses eventos permitem interação entre tabuleiro e jogadores,
como processar eventos aleatórios ou iniciar duelos de honra, demonstrando a complexa interatividade
presente no jogo.
202
Unidade III
A função ConfigurarTabuleiro (figura 74) desempenha papel importante na configuração inicial de
um jogo de tabuleiro virtual, definindo as características e regras de cada casa nele. Essa função inicia
seu processo definindo todas as casas como normais; para isso ela percorre cada casa inicializando‑as
com o tipo e a descrição‑padrão de Casa Normal. Esse passo estabelece uma base uniforme sobre a
qual variações específicas serão aplicadas.
1. private void ConfigurarTabuleiro()
2. {
3. // Inicializa todas as casas como