C# -  Usando interfaces (revisitado)


  Retornamos ao assunto do uso de interfaces na linguagem C#.

No C#, uma interface é uma estrutura que define um conjunto de métodos, propriedades e eventos que uma classe deve implementar. Em outras palavras, uma interface é um contrato que uma classe deve seguir para ser considerada compatível com essa interface.



Uma interface pode incluir apenas a declaração de métodos, propriedades e eventos, mas não pode fornecer implementações para eles. As classes que implementam uma interface devem fornecer suas próprias implementações para cada membro da interface.

Interfaces são frequentemente usadas para definir contratos comuns que as classes podem seguir para garantir a interoperabilidade entre diferentes partes de um programa. Por exemplo, uma interface IComparable pode ser usada para definir um contrato que as classes devem seguir para permitir a comparação de instâncias dessas classes.

Para implementar uma interface em uma classe, usamos dois pontos ( : )  após o nome da classe seguido do nome da interface e a seguir fornecemos as implementações necessárias para cada membro da interface na classe.

// Definição da interface
public
interface IAnimal
{
  
void EmitirSom();
}

 

// Implementação da classe que segue a interface
public
class Gato : IAnimal
{
  
public void EmitirSom()
   {
     Console.WriteLine(
"Miau...!");
   }
}

O uso de interfaces é uma boa prática na linguagem C# por várias razões, como:

Permite o desenvolvimento de código mais modular e fácil de manter: ao utilizar interfaces, é possível definir um contrato ou um conjunto de comportamentos que um objeto deve implementar, independentemente de como ele é implementado. Isso significa que é possível alterar a implementação interna de um objeto sem afetar outros objetos que dependem dele, desde que a nova implementação atenda ao contrato definido pela interface.

Facilita a comunicação e a colaboração entre objetos: quando um objeto implementa uma interface, ele se torna compatível com outros objetos que também implementam essa interface. Isso significa que esses objetos podem interagir de forma mais fácil e consistente, mesmo que sejam de tipos diferentes.

Permite a criação de classes genéricas: interfaces podem ser usadas como restrições em parâmetros genéricos, o que permite criar classes genéricas que funcionem com vários tipos diferentes, desde que esses tipos implementem a interface exigida.

Ajuda a prevenir erros de compilação: quando um objeto não implementa todos os membros exigidos por uma interface, o compilador emite um erro, o que ajuda a identificar erros antes mesmo de executar o código.

Exemplo prático

Para ilustrar como o uso de interfaces otimiza o código vejamos um exemplo prático usando um projeto Console criado no VS 2022 usando o C# 11 e o .NET 7.0.

Suponha que temos um sistema que precisa gerar relatórios de diferentes tipos, como relatórios em PDF, relatórios em Excel, relatórios em HTML etc.

Cada tipo de relatório pode ter uma implementação diferente, mas todos eles devem ser gerados a partir de uma fonte de dados comum, como um banco de dados ou um arquivo CSV.

Podemos começar definindo uma interface IGeradorRelatorio que define o método GerarRelatorio para cada tipo de relatório:

public interface IGeradorRelatorio
{
  
void GerarRelatorio(string fonteDados);
}

Em seguida, podemos criar classes para cada tipo de relatório, implementando a interface IGeradorRelatorio de maneira diferente para cada um:

public class GeradorPDF : IGeradorRelatorio
{
  
public void GerarRelatorio(string fonteDados)
   {
      Console.WriteLine(
$"Gerando relatório PDF a partir da fonte de dados: {fonteDados}");
   }
}

public class GeradorExcel : IGeradorRelatorio
{
  
public void GerarRelatorio(string fonteDados)
   {
     Console.WriteLine(
$"Gerando relatório Excel a partir da fonte de dados: {fonteDados}");
   }
}

public class GeradorHTML : IGeradorRelatorio
{
  
public void GerarRelatorio(string fonteDados)
   {
      Console.WriteLine(
$"Gerando relatório HTML a partir da fonte de dados: {fonteDados}");
   }
}

Agora, podemos criar uma classe GerenciadorRelatorios que usa a interface IGeradorRelatorio para gerar relatórios de qualquer tipo, sem se preocupar com a implementação específica de cada um:

public class GerenciadorRelatorios
{
  
private readonly IGeradorRelatorio _gerador;
  
public GerenciadorRelatorios(IGeradorRelatorio gerador)
   {
     _gerador = gerador;
   }
  
public void GerarRelatorio(string fonteDados)
   {
      Console.WriteLine(
$"Gerando relatório usando {_gerador.GetType().Name}");
      _gerador.GerarRelatorio(fonteDados);
      Console.WriteLine();
   }
}

Observe que o construtor da classe GerenciadorRelatorios recebe uma instância da interface IGeradorRelatorio. Isso permite que possamos injetar diferentes implementações da interface, dependendo do tipo de relatório que queremos gerar.

Por exemplo, podemos criar uma instância de cada tipo de gerador de relatório e usá-los com o GerenciadorRelatorios:

var fonteDados = "dados.csv";

var geradorPDF = new GeradorPDF();
var
geradorExcel = new GeradorExcel();
var
geradorHTML = new GeradorHTML();
var
gerenciador = new GerenciadorRelatorios(geradorPDF);

gerenciador.GerarRelatorio(fonteDados);
gerenciador =
new GerenciadorRelatorios(geradorExcel);
gerenciador.GerarRelatorio(fonteDados);
gerenciador =
new GerenciadorRelatorios(geradorHTML);

gerenciador.GerarRelatorio(fonteDados);

Console.ReadKey();

Executando teremos o seguinte resultado :

Observe que o código é o mesmo para cada tipo de relatório, e que o GerenciadorRelatorios pode lidar com qualquer tipo de gerador de relatório, sem se preocupar com a implementação específica de cada um.

Isso torna o código mais flexível e extensível, pois podemos adicionar novos tipos de geradores de relatório simplesmente criando uma nova classe que implementa a interface IGeradorRelatorio.

Além disso, como o GerenciadorRelatorios depende apenas da abstração (ou seja, da interface IGeradorRelatorio), podemos substituir as implementações específicas sem afetar o restante do código. Isso torna o código mais fácil de manter e de testar, pois podemos testar cada implementação da interface IGeradorRelatorio separadamente.

Em resumo, o uso de interfaces nos permite escrever código mais flexível, extensível e fácil de manter, especialmente em sistemas complexos com várias implementações diferentes de uma funcionalidade específica. O exemplo acima ilustra como o uso de interfaces pode otimizar o código ao permitir que dependamos apenas de abstrações em vez de implementações específicas.

Sem o uso de interfaces, o código para o gerenciamento de relatórios pode ficar mais complexo e menos flexível. Veja um exemplo de como seria sem o uso de interfaces:

public class GerenciadorRelatorios
{
  
public void GerarRelatorioPDF(string fonteDados)
   {
     Console.WriteLine(
"Gerando relatório PDF a partir da fonte de dados: " + fonteDados);
   }
  
public void GerarRelatorioExcel(string fonteDados)
   {
     Console.WriteLine(
"Gerando relatório Excel a partir da fonte de dados: " + fonteDados);
   }
  
public void GerarRelatorioHTML(string fonteDados)
   {
     Console.WriteLine(
"Gerando relatório HTML a partir da fonte de dados: " + fonteDados);
   }
}

Parece mais simples , não é mesmo !!!

Nesse caso, o GerenciadorRelatorios possui um método para cada tipo de gerador de relatório, o que pode tornar o código mais difícil de manter e estender. Se quisermos adicionar um novo tipo de gerador de relatório, precisamos criar um novo método no GerenciadorRelatorios alterando o código da classe e ferindo o princípio SOLID Open-Closed.

Além disso, o código que usa o GerenciadorRelatorios precisa chamar o método correto de acordo com o tipo de gerador de relatório desejado. Isso pode resultar em código duplicado e difícil de manter, especialmente em sistemas com muitos pontos de chamada.

Portanto, o uso de interfaces pode otimizar o código ao permitir que dependamos apenas de abstrações em vez de implementações específicas, tornando o código mais flexível e fácil de manter.

E estamos conversados ...

"A estultícia do homem perverterá o seu caminho, e o seu coração se irará contra o Senhor."
Provérbios 19:3
  

Referências:


José Carlos Macoratti