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.
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.
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:
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?
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.
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.
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.
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.
É 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…
}
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
eShow
.
André: Na sequência, continuaremos a implementação para conseguir fazer a importação do CSV funcionar adequadamente.
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:
Impulsione a sua carreira com os melhores cursos e faça parte da maior comunidade tech.
1 ano de Alura
Assine o PLUS e garanta:
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
No Discord, você tem acesso a eventos exclusivos, grupos de estudos e mentorias com especialistas de diferentes áreas.
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
Acelere o seu aprendizado com a IA da Alura e prepare-se para o mercado internacional.
1 ano de Alura
Todos os benefícios do PLUS e mais vantagens exclusivas:
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos, corrige exercícios e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com a Luri até 100 mensagens por semana.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Transforme a sua jornada com benefícios exclusivos e evolua ainda mais na sua carreira.
1 ano de Alura
Todos os benefícios do PRO e mais vantagens exclusivas:
Mensagens ilimitadas para estudar com a Luri, a IA da Alura, disponível 24hs para tirar suas dúvidas, dar exemplos práticos, corrigir exercícios e impulsionar seus estudos.
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.