Entender a estrutura do aplicativo Reversi

Entender a estrutura do aplicativo Reversi

[ Este artigo destina-se aos desenvolvedores do Windows 8.x e do Windows Phone 8.x que escrevem aplicativos do Windows Runtime. Se você estiver desenvolvendo para o Windows 10, consulte documentação mais recente]

A amostra de Reversi usa uma estrutura em camadas conhecida como padrão MVVM (Model-View-ViewModel). Essa estrutura separa a IU (interface do usuário) do código não IU, o que pode simplificar depuração, teste e desenvolvimento futuro. Esse tópico descreve cada camada da amostra, as partes de cada camada e como todas as partes trabalham juntas.

A amostra de Reversi difere de amostras XAML mais simples, que colocam a maior parte do código não XAML em arquivos code-behind. O Reversi adiciona código aos arquivos code-behind em uns poucos casos, mas move a maioria do código não XAML para outras camadas e usa vinculação de dados para conectar essas camadas à IU.

A amostra de Reversi também difere das amostras XAML mais complexas que usam técnicas mais avançadas, incluindo estruturas MVVM. Essas estruturas são úteis para aplicativos de complexidade crescente, mas a amostra de Reversi mostra que há benefícios mesmo em uma compilação simples de estrutura em camadas usando XAML padrão.

Para ver uma breve introdução ao MVVM, confira Usar o padrão MVVM (Model-View-ViewModel).

Este tópico pressupõe que você já conheça XAML e C#. Se não estiver familiarizado com essas tecnologias, veja Crie seu primeiro aplicativo da Windows Store em C# ou Visual Basic. Se você conhece o C++, pode também aproveitar a versão em C++ do mecanismo do jogo Reversi, embora isso não seja necessário para entender o restante da amostra.

Para uma introdução geral sobre a amostra, veja Reversi, um jogo da Windows Store em XAML, C# e C++. Para ver como são usados os recursos específicos na amostra, confira Saiba como a amostra de Reversi usa os recursos de aplicativo da Windows Store. Para aprender como o mecanismo do jogo em C# original foi portado para C++, veja Conheça o mecanismo do jogo Reversi em C++.

Baixe o aplicativo de exemplo Reversi ou procure o código-fonte.

Camadas de aplicativo

O aplicativo Reversi está dividido nestas camadas:

  • A camada modelo inclui o código central do jogo, que representa as regras, o estado e a IA (inteligência artificial) do jogo e é completamente independente da interface do usuário. A maior parte do código do modelo está no projeto ReversiGameComponentCS da versão em C# e no projeto ReversiGameComponentCPP da versão em C++ do mecanismo do jogo.
  • A camada exibição é implementada pela pasta Views no projeto Reversi. Esse código fornece a IU do jogo, que é definida em XAML com uso extensivo de vinculação de dados.
  • A camada modelo de exibição é implementada pela pasta ViewModels no projeto Reversi. Esse código fornece destinos de vinculação de IU e lida com todas as interações de usuário, mas é completamente independente da IU.

A camada modelo

A amostra de Reversi inclui duas implementações diferentes da camada modelo:

  • A versão em C# original está no projeto ReversiGameComponentCS.
  • A versão em C++ com desempenho aprimorado está no projeto ReversiGameComponentCPP.

Existe também um projeto ReversiGameModel que contém interfaces e classes de suporte usadas para separar os testes de aplicativo e unidade da implementação dos componentes do jogo. Os projetos de teste de unidade e Reversi têm dependências apenas nessas interfaces e classes de suporte, e não têm nenhuma dependência direta sobre as implementações de componentes de jogo.

O projeto ReversiGameComponentCS implementa a interface do modelo do jogo diretamente. No entanto, o projeto ReversiGameComponentCPP não pode implementar interfaces do .NET. Portanto, o projeto Reversi inclui uma classe CLRSerializableCPPGame na pasta Models. Essa classe é que implementa a interface do jogo e oferece um wrapper do .NET em torno do componente C++. Dessa forma, os testes de unidade e o aplicativo principal podem consumir o componente C# ou C++, inclusive serializar os objetos do jogo no disco quando o aplicativo for suspenso.

O aplicativo certamente não foi projetado para usar as duas implementações de mecanismo do jogo ao mesmo tempo. Ele sempre cria as instâncias do jogo usando a classe GameFactory na pasta Models. Essa classe vai criar e retornar uma instância de componente C# ou C++, caso tenha sido definida uma constante do compilador CSHARP.

As duas versões da camada modelo são implementadas como projetos do Componente do Tempo de Execução do Windows. Cada versão do componente inclui a lógica central do jogo: tudo o que é necessário para representar as regras do jogo, o estado de mudança de um jogo real em andamento e a inteligência artificial do jogo.

Convém colocar a lógica central do jogo em um projeto separado do Componente do Tempo de Execução do Windows por vários motivos; dentre eles, permitir a criação de uma versão separada desenvolvida em C++. Por exemplo, você pode criar um projeto de componente de jogo e fazer referência a ele como um assembly de biblioteca em uma nova solução de aplicativo. Dessa forma, você pode substituir a IU (talvez como um exercício de aprendizagem) sem alterar nada mais. Você pode até mesmo usar o componente em um aplicativo baseado em JavaScript.

Outra vantagem do uso de uma camada separada é manter o código central do jogo o mais simples possível. A criação de um aplicativo completo de jogo de tabuleiro é muito mais difícil do que escrever a quantidade mínima de código para as regras de um jogo de tabuleiro. Entretanto, até mesmo para regras simples de jogo, o código ainda pode ser bastante complexo; por isso, deixar a interface do usuário fora desse código torna mais fácil escrever e testar.

Para saber mais, veja Usando um componente do Tempo de Execução do Windows em Cenários de recursos do Reversi.

A camada exibição

A camada de exibição fica na pasta Views do projeto Reversi e inclui o código das partes visuais mais importantes do aplicativo, incluindo cada página, os submenus Configurações e os controles de jogo.

A maioria dessas partes tem arquivos XAML e code-behind, mas, em alguns casos, o code-behind é apenas o código de texto clichê gerado pelo modelo de aplicativo do Microsoft Visual Studio. A maior parte do código que, normalmente, estaria nos arquivos code-behind está no modelo e nas camadas view-model (modelo de exibição).

Os arquivos XAML contêm a maior parte do código de exibição. Esses arquivos incluem o uso principal da extensão de marcação de vinculação do XAML para definir as vinculações de dados entre o XAML e os modelos de exibição.

Algumas exibições também usam code-behind para executar tarefas que são mais difíceis de implementar em XAML e não são adequadas às camadas modelo de exibição e modelo. Isso inclui:

  • Configuração especial do contexto de dados ou das vinculações de dados das exibições.
  • Chamada de métodos de IU na API XAML; por exemplo, métodos de navegação e animação.
  • Manipulação de eventos de IU para chamar métodos view-model quando não há alternativa XAML conveniente.

À medida que o aplicativo fica mais complexo, pode ser conveniente refatorar essas tarefas em mais componentes ou camadas, ou usar uma das muitas estruturas MVVM que fornecem serviços relacionados.

Para saber mais sobre como as exibições se conectam aos modelos de exibição, veja Vinculação de dados em Cenários de recursos do Reversi.

Páginas

O Reversi inclui três páginas:

  • A StartPage exibe as opções do jogo e da ajuda, as quais são mostrada quando o aplicativo é iniciado.
  • A GamePage exibe um controle PlayerStatus e um controle Board repleto de controles BoardSpace.
  • A HelpPage exibe as instruções o jogo, incluindo PlayerStatus e Board para fins de demonstração e ilustração.
Sobreposição de capturas de tela das três páginas de aplicativo do Reversi

Estas páginas foram criadas usando o modelo Página Básica na caixa de diálogo Projeto -> Adicionar Novo Item do Visual Studio.

Controles do usuário e controles personalizados

A principal complexidade da IU está nos controles que representam o tabuleiro do jogo, o placar e o relógio. Esses elementos incluem os controles do usuário e os controles personalizados. A interface do usuário para esses controles, incluindo algumas transições animadas de estado, é definida em XAML. Os controles gerenciam as transições por meio das propriedades de dependência personalizadas, que são dados associados às propriedades view-model. Para saber mais, veja Vinculação de dados e Propriedades de dependência personalizadas em Cenários de recursos do Reversi.

  • Board representa o tabuleiro do jogo. Isso é uma subclasse de UserControl e exibe um painel Grid contendo os controles BoardSpace e as várias mensagens do jogo (jogo pausado, movimento de passagem e fim do jogo). Board é um controle de usuário porque controles desse tipo facilitam a definição de uma parte única da IU e sua respectiva funcionalidade em um par XAML e code-behind. Em algum ponto no desenvolvimento futuro, isso poderá ser útil para refatorá-lo em um controle personalizado para aumentar a usabilidade.
  • BoardSpace representa cada espaço no tabuleiro do jogo. Este é um controle personalizado derivado da classe Button, que aproveita as vantagens da propriedade Command, mas substitui a IU padrão. Para substituições de IU mais simples, você pode usar um Button e apenas definir a propriedade Style, como demonstrado pelos botões de texto localizados na página inicial. BoardSpace é um controle personalizado porque usa uma propriedade de dependência personalizada para direcionar as transições de IU animadas. Sendo um controle personalizado, ele é definido no arquivo BoardSpace.cs e no arquivo Reversi/Themes/Generic.xaml.
  • PlayerStatus representa o placar e o relógio. Este é um controle personalizado derivado da classe Control e é definido em PlayerStatus.cs e no arquivo Generic.xaml. É um controle personalizado porque incorpora controles BoardSpace e exige um controle maior sobre a inicialização do estado visual do que um UserControl pode fornecer. Especificamente, ele usa uma substituição do método OnApplyTemplate para inicializar o BoardSpace em um de dois estados, antes que as transições animadas sejam disparadas automaticamente pela inicialização da vinculação de dados. Esse método não está disponível na classe UserControl, onde a primeira oportunidade de modificar o controle após a vinculação de dados está em um manipulador de eventos Loaded— tarde demais para evitar as transições animadas padrão.

No tempo de execução, os controles do Reversi têm esta aparência.

Página de jogo do Reversi mostrando os controles PlayerStatus, Board e BoardSpace

Submenus de configurações

O Reversi inclui dois controles SettingsFlyout que ficam na pasta Exibir/Configurações:

  • O DisplaySettings inclui opções para mostrar ou ocultar o relógio e os indicadores que mostram os movimentos válidos e os efeitos do último movimento.
  • NewGameSettings inclui opções para alterar o tamanho do tabuleiro, alternar jogadores e computador, e alterar a profundidade de pesquisa de IA.

Submenus de configurações do Reversi

Para saber mais, veja Submenus de configurações em Cenários de recursos do Reversi.

Dados de designer

A pasta Views/SampleData inclui definições XAML de instâncias GameViewModel e SettingsViewModel. Esses dados são referenciados por vários arquivos de IU XAML na pasta Views, para exibir uma visualização que faça sentido em um designer. Não são usados no tempo de execução.

Os dados do designer de SettingsViewModel também usam a classe GameDesignerStub na pasta Models. O designer não usa facilmente a classe GameFactory para criar uma instância do componente do jogo em C# ou C++, portanto, a classe GameDesignerStub fornece uma implementação de IGame somente com o básico que o designer precisa para criar uma instância dos dados XAML.

A camada view-model

A camada view-model está no projeto Reversi, na pasta ViewModels, e inclui o código da interface do usuário não visual e o código que interage com a camada modelo.

Como mencionado anteriormente, as exibições usam a extensão de marcação de vinculação do XAML para vincular as propriedades view-model. Os modelos de exibição, porém, não fazem referência direta com as exibições. Em vez disso, eles fornecem propriedades e comandos para a exibição à qual serão vinculados.

As propriedades view-model podem fornecer:

  • Exposição direta de propriedades do modelo.
  • Propriedades de modelo reformatadas ou convertidas em tipo.
  • Valores calculados com base nas várias propriedades de modelo ou nos múltiplos valores de retorno de chamada do método.
  • Comandos que convertem a entrada de usuário em chamadas ao método de modelo.
  • Valores que representam a IU ou os estados de aplicativo que não são relevantes ao modelo.

Por exemplo, a interface do usuário do Reversi precisa renderizar o estado de cada espaço no tabuleiro, o qual fica armazenado no objeto de modelo Game. Entretanto, a interface do usuário também precisa exibir indicadores que sinalizem quais movimentos são legítimos e quais espaços foram afetados pelo movimento mais recente. A classe Game inclui essas informações no formulário de métodos move-validation e os valores de retorno dos métodos de movimentos. Entretanto, o modelo de exibição é necessário para reunir essas informações em uma única propriedade (a propriedade do indexador de cadeia de caracteres this da classe GameViewModel) à qual a exibição possa se vincular.

O Reversi inclui 3 modelos de exibição:

  • GameViewModel contém todas as interações com a classe de modelo Game e inclui uma referência a um ClockViewModel.
  • SettingsViewModel contém as propriedades que armazenam as configurações atuais do aplicativo e inclui uma referência ao GameViewModel que representa o jogo.
  • ClockViewModel contém toda a funcionalidade do relógio, incluindo a formatação de exibição e os comandos de pausa e início do relógio.

A pasta ViewModels também inclui:

  • As definições de interface ISettingsViewModel, IGameViewModel e IClockViewModel, que separam os testes de unidade de modelo de exibição das implementações de modelo de exibição. Para saber mais, veja a seção Testes de unidade.
  • As enumerações Player e BoardSpaceState, que fornecem valores para algumas propriedades view-model.

Para saber mais, veja Vinculação de dados e Compartilhamento de conteúdo em Cenários de recursos do Reversi.

Infraestrutura e código comum

O projeto Reversi também inclui algum código de infraestrutura para que tudo funcione e algum código reutilizável que complementa as bibliotecas da plataforma.

Os principais arquivos de infraestrutura são o arquivo App.xaml e respectivo code-behind e o arquivo Package.appxmanifest. Para saber mais sobre esses arquivos, veja Modelos de projeto C#, VB e C++ para aplicativos da Windows Store e Usando o Designer de Manifesto.

A pasta Common contém arquivos que são adequados para reutilização em outros projetos. A maioria desses arquivos é gerada pelo aplicativo e pelos modelos de item no Visual Studio. Entretanto, o Reversi adiciona alguns arquivos e faz pequenas alterações nos arquivos gerados pelo modelo.

Estes arquivos são compatíveis com cenários comuns de vinculação de dados:

  • BindableBase fornece a classe base de cada modelo de exibição. Essa classe oferece código reutilizável de infraestrutura de notificação de alteração, que funciona com o sistema de vinculação de dados. Para saber mais, veja Vinculação de dados em Cenários de recursos do Reversi.
  • DelegateCommand e DelegateCommandBase fornecem implementações de ICommand que lhe permitem definir funcionalidades de comando em linha em uma implementação de propriedade vinculável. Essa implementação é copiada do Prism para o Tempo de Execução do Windows, e é necessária porque a classe RelayCommand gerada pelo modelo não suporta métodos de comando assíncronos.
  • O NullStateToVisibilityConverter fornece um modo padrão para associações, para converter valores não null em valores Visible e valores null em valores Collapsed. Você pode usar esse conversor em vez de criar propriedades view-model especiais para executar essa conversão.
  • BooleanToVisibilityConverter executa uma conversão semelhante entre valores booleanos e Visibility. Esse conversor é gerado por modelos do Visual Studio, mas o Reversi usa uma versão modificada para aceitar um valor ConverterParameter que reverte uma conversão.
  • O ObservableDictionary fornece uma classe de modelo de exibição padrão para páginas geradas por modelos.

Estes arquivos são compatíveis com navegação e layout de página, incluindo suspensão e retomada:

  • RichTextColumns fornece um painel que renderiza conteúdo rich text em uma ou mais colunas, conforme o necessário ao tamanho de tela atual, à resolução e ao estado da exibição. O Reversi usa essa classe na HelpPage.
  • SuspensionManager é uma classe gerada via modelo para salvar e restaurar o estado temporário de aplicativo quando o seu aplicativo for suspenso ou encerrado. O Reversi usa uma versão modificada dessa classe para habilitá-la a serializar os modelos de exibição interconectados.
  • O NavigationHelper é usado por meio da geração de modelo para executar tarefas comuns, como navegar para trás e para a frente e carregar ou salvar dados de página.
  • O RelayCommand oferece uma implementação de ICommand simples usada pelos botões NavigationHelper e de voltar de páginas geradas por modelo. Essa classe é semelhante ao DelegateCommand, mas não suporta métodos de comando assíncronos.

Além disso, a classe Toast oferece um serviço simples de notificação do sistema.

Teste de unidade

A pasta Tests inclui dois projetos:

  • O projeto ReversiGameComponentTests inclui testes de unidade para projetos de componentes do jogo Reversi. Esses testes funcionam nas versões em C# e C++ do componente do jogo e fazem o teste de qualquer versão configurada para uso no arquivo Models\GameFactory.cs no projeto Reversi.
  • O projeto ReversiViewModelTests inclui testes de unidade para as classes view-model do projeto Reversi.

Os testes no projeto ReversiGameComponentTests foram criados durante o desenvolvimento da lógica central do jogo para verificar as várias pressuposições de design, à medida que aumentava a complexidade do código. A complexidade fez com que o código ficasse, de alguma forma, propenso a erros, e os testes de unidade ajudaram a identificar e diagnosticar bugs e a melhorar gradualmente o design do código.

Os testes estão divididos em 4 classes:

  • BasicFunctionalityTests inclui testes para as APIs Board, Moves e Move da classe Game e de recursos como, por exemplo, desfazer e refazer.
  • ValidationTests inclui testes para o código que verifica se um determinado movimento é legítimo.
  • AiTests incluir testes que verificam o comportamento da IA do jogo, que, na verdade, é apenas o código que avalia a qualidade dos vários movimentos.
  • SerializationTests inclui testes do código que representa os movimentos do jogo e os estados como cadeias de caracteres. Esse código é útil em outros testes.

Esses testes fornecem uma pequena quantidade de cobertura, mas são suficientes para proporcionar um valor significativo durante o desenvolvimento.

Os testes no projeto ReversiViewModelTests foram criados para completar a cobertura do teste de unidade e mostrar como resolver outras complexidades do teste do modelo de exibição. Os modelos de exibição são mais complicados de testar porque têm dependências com as classes do modelo e entre si. Para assegurar que os testes de unidade sejam realizados apenas nos modelos de exibição específicos, todas as dependências externas são resolvidas usando objetos fictícios, que fornecem implementação mínima das interfaces necessárias.

Assim como os demais benefícios de MVVM, o teste de unidade não precisa ser uma questão de tudo ou nada. Mesmo pequenas quantidades de separação de código e teste de unidade podem propiciar um ótimo valor. Na verdade, o uso de pequenas quantidades no início ajudará você a entender melhor o que funciona e o que não funciona. Dessa forma, você pode gerenciar a crescente complexidade de um aplicativo durante o desenvolvimento e evitar a adição de complexidade antes que isso seja necessário.

Para saber mais, veja Criando e executando testes de unidade em um aplicativo da Windows Store.

Tópicos relacionados

Aplicativo de exemplo Reversi
Reversi, um jogo da Windows Store em XAML, C# e C++
Usar o padrão MVVM (Model-View-ViewModel)
Saiba como o aplicativo de exemplo Reversi usa os recursos do aplicativo da Windows Store
Entender a estrutura do aplicativo Reversi
Conheça o mecanismo do jogo Reversi em C++
Crie seu primeiro aplicativo da Windows Store em C# ou Visual Basic
Mapa de aplicativos do Tempo de Execução do Windows em C# ou Visual Basic
Vinculação de dados

 

 

Mostrar:
© 2017 Microsoft