Alura > Cursos de Programação > Cursos de .NET > Conteúdos de .NET > Primeiras aulas do curso C#: aplique princípios SOLID

C#: aplique princípios SOLID

Importando Json - Apresentação

Daniel: Olá, dev amante do C#. Meu nome é Daniel Portugal, instrutor e programador de software na Alura e te acompanharei em mais um curso de boas práticas em C#.

Audiodescrição: Daniel é um homem branco de olhos castanhos. Tem barba e cabelo preto. Usa óculos de armação retangular e uma camisa preta com estampa de heavy metal. Ao fundo, uma parede sem decorações iluminada em azul.

Mas não estou sozinho para esse curso.

André: Meu nome é André Bessa, também sou instrutor da Alura e estarei acompanhando você e o Daniel neste curso.

Audiodescrição: André Bessa é um homem negro de rosto arredondado, com cabelo curto e barba por fazer. Usa uma camisa azul. Está em um ambiente iluminado com uma luz azul.

Público-alvo e pré-requisitos

André: Para quem é este curso? Para pessoas que já conhecem C# e querem continuar aprimorando suas habilidades, adotando boas práticas, princípios e padrões aos seus projetos.

Importante lembrar que este curso faz parte de uma formação de Boas Práticas em C#, por isso, para acompanhar esse curso seria importante também ter feito os cursos anteriores.

O que vamos aprender?

Daniel: Vamos aprender muitos conceitos nesse curso, então se prepare para dar um grande mergulho. O que vamos aprender?

Vamos conhecer mais padrões de projetos e novos recursos da linguagem C# que ainda não foram abordados em outros cursos anteriores.

Também vamos continuar a aplicar boas práticas agora para configuração de sistema e testes. Além disso, vamos aprender a enviar e-mail com C#.

Por fim, nosso foco principal é aplicar princípios SOLID. A aplicação dos princípios SOLID é extremamente importante em qualquer projeto de código para que você possa evoluir seu projeto com tranquilidade, sem impactar, sem gerar bugs, sem causar defeitos em vários módulos do sistema. O SOLID atende justamente a mudanças no seu projeto.

Para entender e aplicar esses princípios do SOLID, vamos continuar trabalhando com nosso projeto Adopet, aquele que faz importação de animais de estimação via console, mas agora vamos introduzir novas demandas. Temos três novas tarefas:

  1. Novo formato de arquivo de importação. Antes tínhamos CSV, agora vamos introduzir JSON.
  2. Novos comandos no nosso console, importação de clientes e listagem de clientes.
  3. Envio de e-mails após a importação de pets.

André: Parece interessante, Daniel. Vamos evoluir bastante essa aplicação.

Portanto, convidamos a todas as pessoas estudantes a acessarem os recursos da plataforma da Alura. Além dos vídeos, realizem as atividades propostas também. Também temos o apoio do fórum e de uma comunidade bem ativa no Discord da Alura.

Vamos estudar?

Importando Json - Testes para Json

Daniel: Bom dia, André. Tudo bem? Recebemos um e-mail do Nico solicitando um nova demanda que devemos entregar logo. Vamos conferí-lo:

Olá,

Pensando na evolução das ferramentas da solução do Adopet, agora nós precisamos fazer com que a aplicação console dê suporte para a importação de arquivos no formato .json.

Atenciosamente, Nico - gestor

André: Vamos retornar ao código. Primeiro, devemos executar os testes e garantir que eles continuam passando. Clicando com o botão direito sobre o teste de unidade no Gerenciador de Testes, vamos rodar esses 20 testes selecionando a opção "Executar" (ou "Ctrl + R, T").

A expectativa é que continue tudo passando. Perfeito, passaram todos os testes. Agora, vamos partir para a implementação.

Teste de leitor de arquivos JSON

André: No Gerenciador de Soluções do lado direito, temos a classe LeitorDeArquivo que faz a leitura de arquivos. Nela, o método RealizaLeitura() retorna uma lista de Pets.

Esse arquivo vai trabalhar num formato CSV, o formato com o qual iniciamos o nosso projeto. Por isso, faz sentido renomear essa classe para LeitorDeArquivoCsv. Para deixar bem isolado. Esta classe faz CSV e outra classe vai trabalhar com JSON.

Com o botão direito clicamos nesse arquivo, vamos escolher a opção "Renomear" e escrever LeitorDeArquivoCsv e dar um "Enter". Com isso, abre-se uma janela com as opções de refatoração do Visual Studio. Basta clicar em "Sim" para fazer as alterações das referências a essa classe.

Antes de criar a classe que vai implementar a leitura de arquivo JSON, o ideal é começar pelo teste. Afinal, é uma boa prática trabalhar com TDD (Test Driven Development ou Desenvolvimento Orientado a Testes).

Daniel: Vamos criar uma classe de teste chamada LeitorDeArquivosJsonTest no projeto de testes unitários. Assim, teremos um LeitorDeArquivoCsv e um LeitorDeArquivosJsonTest.

Para aproveitar o tempo, já criamos a classe que representa o teste para a leitura de arquivos JSON. É uma classe muito parecida com a CSV.

LeitorDeArquivosJsonTest.cs:

using Alura.Adopet.Console.Modelos;
using Alura.Adopet.Console.Servicos.Arquivos;

namespace Alura.Adopet.Testes.Servicos;

public class LeitorDeArquivoJsonTest : IDisposable
{
    private string caminhoArquivo;
    public LeitorDeArquivoJsonTest()
    {
        //Setup
        string conteudo = @"
            [
              {
                ""Id"": ""68286fbf-f6f4-4924-adab-0637511813e0"",
                ""Nome"": ""Mancha"",
                ""Tipo"": 1
              },
              {
                ""Id"": ""68286fbf-f6f4-4924-adab-0637511672e0"",
                ""Nome"": ""Alvo"",
                ""Tipo"": 1
              },
              {
                ""Id"": ""68286fbf-f6f4-1234-adab-0637511672e0"",
                ""Nome"": ""Pinta"",
                ""Tipo"": 1
              }
            ]
        ";

        string nomeRandomico = $"{Guid.NewGuid()}.json";

        File.WriteAllText(nomeRandomico, conteudo);
        caminhoArquivo = Path.GetFullPath(nomeRandomico);
    }

    [Fact]
    public void QuandoArquivoExistenteDeveRetornarUmaListaDePets()
    {
        //Arrange            
        //Act
        var listaDePets = new LeitorDeArquivosJson(caminhoArquivo).RealizaLeitura()!;
        //Assert
        Assert.NotNull(listaDePets);
        Assert.IsType<List<Pet>?>(listaDePets);
    }

    public void Dispose()
    {
        //ClearDown
        File.Delete(caminhoArquivo);
    }
}

Criamos uma classe que cria um arquivo com o conteúdo JSON. Depois, exclui esse arquivo no método Dispose(). E tem um método específico para começar a nossa integração com o suporte de arquivos JSON.

André: Agora devemos criar o nosso leitor de arquivos JSON. Na nossa classe de negócios, em "Servicos > Arquivos", vamos dar um clique com o botão direito. Em seguida, vamos clicar na opção de "Adicionar > Classe" para criar a classe LeitorDeArquivosJson.

A classe foi criada e precisamos fazer os ajustes necessários. Vamos retirar os use que não estão sendo utilizados, apertando "Ctrl +." e escolher a opção "Remover Usos Desnecessários".

Na linha 2, a classe será pública. Então, deixaremos como public class LeitorDeArquivosJson.

Nós teremos um campos nessa classe, que é o private string caminhoArquivo. Agora, vamos selecionar essa linha, apertar "Ctrl + ." e pedir para "Gerar construtor de LeitorDeArquivosJson".

LeitorDeArquivosJson.cs:

namespace Alura.Adopet.Console.Servicos.Arquivos;

public class LeitorDeArquivosJson
{
        private string caminhoArquivo;
        
        public LeitorDeArquivosJson(string caminhoArquivo)
        {
                this.caminhoArquivo = caminhoArquivo;
        }
}

Daniel: Lembrando que esse caminhoArquivo é uma dependência desse leitor de arquivo, por isso, está o recebendo no construtor.

André: Agora que nossa classe já foi criada, vamos voltar ao método de teste. Agora precisamos implementar o método RealizaLeitura() citado no método QuandoArquivoExistenteDeveRetornarUmaListaDePets(). Com "Ctrl + .", vamos gerar esse método RealizaLeitura() na classe LeitorDeArquivosJson.

Daniel: Esse método deve retornar um IEnumerable de Pet.

André: Exato. Voltando ao LeitorDeArquivosJson, vamos substituir object por IEnumerable<Pet> em RealizaLeitura().

Daniel: Agora, falta substituir o throw new NotImplementedException() pela estratégia que vamos usar para fazer a leitura de arquivos JSON.

André: Para isso, vamos escrever using para criar um stream para fazer a leitura do arquivo a partir de seu caminho e desserializar o JSON para transformar na lista de IEnumerable<Pet>.

Após using, vamos colocar var stream que recebe new FileStream(), passando caminhoArquivo e o modo de leitura como FileMode.Open e FileAcess.Read. Lembrando que existem outras estratégias para criar um arquivo, mas vamos utilizar o FileStream().

Na próxima linha, vamos retornar um JSONSerializer da biblioteca System.Text.Json. Como queremos desserializar, vamos usar o .Deserialize<IEnumerable<Pet>>(), passando o stream que acabamos de criar como parâmetro.

using System.Text.Json

// código omitido…

public IEnumerable<Pet> RealizaLeitura()
{
    using var stream = new FileStream(caminhoArquivo, FileMode.Open, FileAccess.Read);
    return JsonSerializer.Deserialize<IEnumerable<Pet>>(stream);
}

Daniel: Pegamos o caminho do arquivo, transformamos ele em um fluxo de bytes que é um stream. A partir da classe FileStream(), o abrimos como leitura e usamos essa variável stream para desserializar em um IEnumerable de Pet.

André: A IDE avisa que o retorno pode ser nulo, mas podemos resolver essa questão mais adiante. Em Gerenciador de Testes, vamos executar todos os testes novamente.

Perfeito, todos os testes passaram, incluindo o novo teste do JSON.

Conclusão

Daniel: Agora estamos incorporando a leitura de arquivos no formato JSON. Daqui a pouco já poderemos avisar a diretoria que estamos suportando esse novo formato de arquivo. Mas ainda temos alguns passos a serem feitos.

É importante que sempre, a cada nova mudança, rodemos os testes e eles continuem passando. E assim usamos o ciclo do TDD: se o teste falha, devemos fazê-lo passar novamente.

Além disso, aprendemos a como fazer a desserialização de um arquivo, transformando o conteúdo de um arquivo em um objeto do tipo IEnumerable<Pet>.

André: A seguir, vamos continuar com essa implementação do leitor de arquivos JSON.

Importando Json - Princípio da Inversão de Dependências (DIP)

André: Antes de continuar, o Visual Studio indica um possível retorno de referência nula com um traçado verde no método RealizaLeitura() do arquivo LeitorDeArquivosJson. Porque, quando desserializamos este arquivo, é possível que ele retorne nulo.

Então, se ele retornar nulo, colocaremos um operador de coalescência nula (??) e retornaremos uma lista de Pet vazia. Posso fazer isso através do método Empty() da classe estática Enumerable.

LeitorDeArquivosJson.cs:

public IEnumerable<Pet> RealizaLeitura()
{
        using var stream = new FileStream(caminhoArquivo, FileMode.Open, FileAccess.Read);
        return JsonSerializer.Deserialize<IEnumerable<Pet>>(stream)??Enumerable.Empty<Pet>();
}

Daniel: Além de realizarmos a leitura de arquivos JSON, também temos um retorno de lista vazia para o caso de nulos.

Implementando DIP

É necessário que tenhamos em mente que tanto a leitura de arquivos CSV quanto JSON têm o mesmo comportamento. Isto é bem perceptível através da assinatura do método RealizaLeitura() na linha 13 da implementação para JSON.

A assinatura deste método é: se chama RealizaLeitura(), não tem argumentos de entrada e retorna um IEnumerable de Pet. A assinatura é muito similar no CSV, portanto, podemos padronizá-la em ambos já que um List<Pet> implementa um enumerável.

LeitorDeArquivoCsv.cs:

public virtual IEnumerable<Pet> RealizaLeitura()
{
        // código omitido…
}

Assim, ambos terão a mesma assinatura. Quando temos métodos similares, podemos abstrair esse comportamento através de uma interface.

André: Mas, não seria adequado criar essa interface junto dos arquivos. Por isso, vamos criar uma pasta chamada "Abstracoes"? Afinal, a interface é uma abstração, algo mais genérico.

Clicando com o botão direito na pasta "Servicoes", vamos "Adicionar > Nova Pasta" e chamá-la de "Abstracoes".

Daniel: Mais adiante, vamos explicar porque decidimos colocar a interface em uma pasta separada.

Na Abstracoes, vamos clicar com o botão direito e escolher "Adicionar > Classe". No entanto, ela não será uma classe, será uma interface chamada ILeitorDeArquivos, segundo os padrões de nomenclatura.

Vamos fazer as alterações necessárias, apertando "Ctrl + ." para "Remover Usos Desnecessários". Essa interface será uma public interface. Em seguida, vamos criar o método RealizaLeitura(), copiando a assinatura de LeitorDeArquivosCsv.

Por fim, vamos adicionar a referência para os Modelos, usando o atalho "Ctrl + .".

ILeitorDeArquivos.cs:

using Alura.Adopet.Console.Modelos;

namespace Alura.Adopet.Console.Servicos.Abstracoes;
public interface ILeitorDeArquivos
{
        IEnumerable<Pet> RealizaLeitura();
}

Agora, as classes LeitorDeArquivoCsv e LeitorDeArquivosJson vão implementar esta interface. Após public class LeitorDeArquivoCsv, vamos adicionar dois-pontos e o nome da interface ILeitorDeArquivos.

Não vai dar erro, porque já temos o método RealizaLeitura() criado.

LeitorDeArquivoCsv.cs:

public class LeitorDeArquivoCsv: ILeitorDeArquivos
{
        // código omitido…
}

Vamos fazer o mesmo para o arquivo JSON. Após public class LeitorDeArquivosJson, acrescentamos dois-pontos e implementamos a interface ILeitorDeArquivos.

LeitorDeArquivosJson.cs:

public class LeitorDeArquivosJson: ILeitorDeArquivos
{
        // código omitido…
}

Daniel: O principal objetivo é ter um comportamento isolado nesta interface ILeitorDeArquivos para o caso de surgirem novos formatos de leitura.

André: Agora precisamos alterar as referências. Na classe Import e Show e nas demais classes em que utilizamos o leitor de arquivos, ao invés de utilizar a classe concreta agora, vamos substituir pela interface.

Em Import, vamos substituir LeitorDeArquivoCsv por ILeitorDeArquivo tanto na linha 16, quando criamos o campo leitor, e na linha 19, no construtor.

Import.cs:

public class Import:IComando
{
        private readonly IApiService clientPet;

        private readonly ILeitorDeArquivos leitor;

        public Import(IApiService clientPet, ILeitorDeArquivos leitor)
        {
                this.clientPet = clientPet;
                this.leitor = leitor;
        }

        // código omitido…
}

Na classe Show, também não vamos mais depender da classe concentra LeitorDeArquivosCsv, mas da classe que implemente o ILeitorDeArquivo. Essas mudanças serão aplicadas tanto no campo leitor e no construtor.

Show.cs:

public class Show:IComando
{
        private readonly ILeitorDeArquivos leitor;

        public Show(ILeitorDeArquivos leitor)
        {
                this.leitor = leitor;
        }

        // código omitido…
}

Conclusão

Daniel: Feito isto, precisamos discutir sobre a importância deste processo. Antes de ter criado a interface ILeitorDeArquivos, o comando Import dependia diretamente da classe concreta LeitorDeArquivosCsv. O problema com isto, é que só poderíamos realizar a leitura de arquivos CSV.

Agora, com a nossa nova abstração, o que fizemos foi inverter a dependência da classe LeitorDeArquivosCsv. Ela está agora apontando para a abstração, assim como outras classes do projeto, como Import e Show.

Repare nos using do comando Import:

using Alura.Adopet.Console.Atributos;
using FluentResults;
using Alura.Adopet.Console.Results;
using Alura.Adopet.Console.Servicos.Abstracoes;

Agora estamos dependendo das Abstracoes e não mais do arquivo concentro que está no namespace de arquivos.

Este processo de inversão de dependência nos permite depender de abstrações, não de classes concretas — é o principio chamado Dependency Inversion Principle (DIP). A vantagem disso, é que teremos menos impacto nas classes mais importantes do nosso sistema, como por exemplo Import e Show.

André: Na sequência, continuaremos a implementação para conseguir fazer a importação do CSV funcionar adequadamente.

Sobre o curso C#: aplique princípios SOLID

O curso C#: aplique princípios SOLID possui 175 minutos de vídeos, em um total de 67 atividades. Gostou? Conheça nossos outros cursos de .NET em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda .NET acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas