C# - Usando Interfaces (tornando aplicações resilientes a mudanças)


Antes de entrar no artigo vamos entender o significado da palavra resiliente. Você sabe o que ela significa ?

Resiliência ou resilência é um conceito da física, utilizado pela engenharia que se aplica a capacidade de um material sofrer tensão e recuperar seu estado normal, quando suspenso o "estado de risco".

Então quando dizemos que uma aplicação é resiliente a mudanças estamos querendo dizer que a aplicação pode sofrer alterações sem comprometer suas funcionalidades ou deixar de se comportar da forma para a qual foi projetada.

Obs: A resilência também pode ser entendida como a capacidade do sistema voltar ao nível de desempenho anterior a falhas ou comportamento imprevisto de usuários, software ou hardware e recuperar os dados afetados.

Agora vamos falar um pouco sobre o assunto do artigo : interface.

O que é uma interface ?

Uma interface, no paradigma da orientação a objetos, é  um tipo de classe que contém apenas as assinaturas de métodos, propriedades, eventos e indexadores.

A implementação dos membros é feita por uma classe concreta ou struct que implementa a interface.

Na linguagem C# a sintaxe usada para indicar a utilização de uma interface é colocar dois pontos após o nome da classe concreta que vai implementar a interface seguido do nome da interface. Ex:   Produto : Iteste   (Produto = classe concreta   Iteste = interface)

Para definir uma interface usamos a palavra chave interface conforme mostra o exemplo a seguir onde definimos duas interfaces: IControl e ISurface.

interface IControl
{
    void Desenhar();
}

interface ISurface
{
    void Pintar();
}

 

Obs: Geralmente ao declarar uma interface usamos a letra I  como letra inicial do nome da interface.
 

A implementação das interfaces definidas pode ser feita da seguinte forma pela classe concreta ClasseConcreta:

 

class ClasseConcreta : IControl, ISurface
{
    public void Desenhar()
    {
    }
   public void Pintar()
   {
   }
}

fonte: Explicit Interface Implementation (C# Programming Guide)

 

A classes concreta implementa os métodos Desenhar() e Pintar() definidos nas interfaces. Se não fizéssemos a implementação obteríamos os seguintes erros ao compilar o projeto:

  1. UsandoInterfaces.ClasseConcreta' does not implement interface member 'UsandoInterfaces.IControl.Desenhar()

  2. UsandoInterfaces.ClasseConcreta' does not implement interface member 'UsandoInterfaces.ISurface.Pintar()

Conforme mostra a figura abaixo:

 

 

Usando o Visual C# 2010 Express Edition podemos criar uma interface em uma solução clicando na opção Add New Item do menu Project e a seguir selecionar o template Interface e informar o nome da interface e clicar no botão Add;
 

namespace UsandoInterfaces
{

   interface
ITeste
   {
   }
}

 

 

Ao lado vemos o código da interface criado.

 

Resumindo:

 

- Em uma interface nenhum método tem corpo e são implicitamente abstratos e publicos;
- Assim como uma classe abstrata , uma interface não pode ser instanciada;
- Uma classe pode implementar mais de uma interface
;
- Uma interface não pode conter um construtor
;
- Ao criar uma classe usando uma interface devem ser implementados todos os métodos da interface, caso contrario deverá ser criada uma classe abstrata.
- Para implementar uma interface no VB.NET usamos o modificador - Implements .
- Uma interface pode herdar de um ou mais interfaces básicas.
- Quando uma lista tipo base contém uma classe base e interfaces, a classe base deve vir em primeiro lugar na lista.
- Uma classe que implementa uma interface pode explicitamente implementar membros da interface. Um membro implementado explicitamente não pode ser acessado por um instância de classe, mas apenas por uma instância da interface.


Obs: Não confunda interface com interface do usuário ou interface que os objetos expõem

Quais os benefícios em usar uma interface ?

 

Muitas vezes, devido aos prazos cada vez mais curtos, a grande demanda e correria do dia a dia procuramos pelo caminho mais fácil, que aparentemente nos dá menos trabalho no momento, e com isso deixamos de lado as boas práticas criando sistemas 'quebra-galhos' que são feitos para funcionar por um curto período (pois mais a frente a gente vai consertar não é mesmo ???) mas acabam se perpetuando e se transformando em um ajuntamento de código sem padrão algum.

 

Usar interface é uma forma de criar e definir um contrato que ajuda na organização do código.


Um dos pilares da programação orientada a objetos diz: "Programe para uma interface e não para uma implementação." (Erich Gamma)

 

Mas afinal o que há de tão vantajoso assim em usar interfaces ?

 

- Permitem criar sistemas fracamente acoplados e mais flexível a mudanças (ou resiliente a mudanças). Isto é possível pois ao utilizar interfaces, se houver necessidade, podemos simplesmente mudar a implementação para adaptar a aplicação as mudanças;

- Os papeis dos desenvolvedores podem ser bem definidos e segregados. Ao usar interfaces o desenvolvedor não tem que se preocupar como ela será implementada, somente tem que conhecer como invocar as funcionalidades definidas. Já os desenvolvedor da interface visual podem começar a definir a interface com o usuário sem ter a implementação concreta da interface permitindo o desenvolvimento paralelo;

- Programar para interface deixa o código mais flexível, permitindo trocar a implementação de um componente em tempo de execução, pois quando o código depende de uma interface, pode-se decidir em tempo de execução qual a implementação será utilizada;

- Enquanto as definições da interface não forem alteradas, incluir novos recursos ou re-implementar recursos já existentes decorrentes de mudanças nas regras de negócio não irá 'quebrar' o sistema já existente, tornado sua vida útil maior;

- O código gerado é mais limpo e fácil de manter e entender pois existem uma separação de responsabilidades entre os componentes do sistema como a interface do usuário e as regras de negócio e a camada de acesso aos dados;

- A realização de testes unitários fica mais fácil e o código ganha mais qualidade;

 

Da teoria à prática

 

Vamos agora mostrar na prática como usar interfaces usando a linguagem C#. Para ilustrar vou usar um exemplo bem simples onde inicialmente temos um código fortemente acoplado que será melhorado com a utilização de uma interface.

 

Vamos adotar um exemplo bem ingênuo para facilitar o entendimento onde temos uma classe Dicionario que é responsável por procurar palavras, para isso ela usa o componente Buscador. Vejamos a seguir a implementação da classe Dicionario e do componente Buscador;

 

Obs: As definições das classes estão incompletas e as estamos usando apenas para ilustrar a utilização das interfaces.

 

Para implementar as classes acima abra o Visual C# 2010 Express Edition e crie um novo projeto do tipo Windows Application com o nome UInterface;

 

A seguir inclua no projeto usando o menu Project -> Add Class as classes Dicionario e Buscador e defina o código de cada uma delas conforme abaixo:

 

 

1- Classe Buscador
 

using System.Collections.Generic;
using System.Linq;
namespace UInterface
{
    public class Buscador
    {
        public List<string> GetResultado(string criterio)
        {
            List<string> esportes = new List<string> { "Futebol", "Voleibol", "Basquetebol", "Natação", "Rugby", "Handebol" };
            List<string> resultado = new List<string>();
            resultado = (from esp in esportes where esp.EndsWith(criterio) select esp).ToList();
            return resultado;
        }
    }
}

 

2- Classe Dicionario

 

using System.Collections.Generic;
namespace UInterface
{
    public class Dicionario
    {
        private string criterio= null;
        public List<string> Procurar(List<string> Lista)
        {
            Buscador procura = new Buscador();
            return procura.GetResultado(criterio);
       }

       public Dicionario(string _criterio)
       {
            criterio = _criterio;
       }

    }
}

 

O exemplo usado mostra que a classe Dicionario possui um código altamente acoplado caracterizado pela utilização da classe concreta Buscador através da criação de uma instância dessa classe. Existe uma dependência muito forte com o componente Buscador e a forma como ele foi implementado não sendo possível alterar a forma de procurar em tempo de execução.

 

Então como podemos desacoplar este código ?? Quem poderá nos ajudar nesta tarefa e como faremos isso ???

 

Vamos aplicar o conselho de Erich Gamma - "Programe para uma interface e não para uma implementação."  e verificar se realmente a utilização de interfaces

desacopla o código tornando mais flexível a mudanças;

 

Para isso vamos incluir uma interface no projeto via menu Project-> Add New Item selecionando o template Interface e informando o nome IBuscador com o seguinte código:

 

using System.Collections.Generic;
namespace UInterface
{
    interface IBuscador
    {
        List<string> GetResultado(string criterio);
    }
}

 

Neste código definimos a interface IBuscador com o método GetResultado que deverá ser implementado por uma classe concreta.

 

 

A nossa classe Buscador agora irá implementar a interface IBuscador e terá o seguinte código:

 

using System.Collections.Generic;
using System.Linq;
namespace UInterface
{
    public class Buscador : IBuscador
    {
        public List<string> GetResultado(string criterio)
        {
            List<string> esportes = new List<string> { "Futebol", "Voleibol", "Basquetebol", "Natação", "Rugby", "Handebol" };
            List<string> resultado = new List<string>();
            resultado = (from esp in esportes where esp.EndsWith(criterio) select esp).ToList();
            return resultado;
        }
    }
}

 

Como vemos nada mudou no código exceto a implementação da interface IBuscador.

 

O código da classe Dicionario deverá ser alterado para :

 

using System.Collections.Generic;
namespace UInterface
{
    public class Dicionario
    {
        private string criterio = null ;
        public List<string> Procurar(List<string> Lista)
        {
            IBuscador procura = new Buscador();
            return procura.GetResultado(criterio);
       }
       public Dicionario(string _criterio)
       {
            criterio = _criterio;
       }
    }
}

 

E pronto ! Com isso substituímos o forte acoplamento da classe concreta pela interface o que torna mais flexível o código.

 

Você esta duvidando ???

 

Não esta percebendo onde esta o tal baixo acoplamento e em como tornamos o código flexível ???

 

Pois bem , eu  mostro...

 

Com a mudança feita podemos definir em tempo de execução qual implementação de Buscador podemos usar e não dependemos mais da implementação da classe concreta. (Tá bom para você ???)

 

Vamos definir outra implementação de Buscador chamada BuscadorTimes com seguinte código:

 

using System.Collections.Generic;
using System.Linq;
namespace UInterface
{
    public class Buscador : IBuscador
    {
        public List<string> GetResultado(string criterio)
        {
            List<string> esportes = new List<string> { "Futebol", "Voleibol", "Basquetebol", "Natação", "Rugby", "Handbol" };
            List<string> resultado = new List<string>();
            resultado = (from esp in esportes  where esp.EndsWith(criterio)  select esp).ToList();
            return resultado;
        }

  

       public class BuscadorTimes : IBuscador

       {

             public List<string> GetResultado(string criterio)

            {

                 List<string> times = new List<string> { "Santos", "Palmeiras", "Vasco", "Coritiba", "Bahia"};

                 List<string> resultado = new List<string>();

                 resultado = (from esp in times where esp.StartsWith(criterio) select esp).ToList();

                return resultado;
            }

       }

 }
}


Agora podemos alterar a classe Dicionario para se comportar da seguinte forma:

 

using System.Collections.Generic;
namespace UInterface
{
    public class Dicionario
    {
        private string criterio = null;
        IBuscador procura = null;
        public List<string> Procurar(List<string> Lista)
        {
            if(criterio.EndsWith(criterio))
            {
                procura = new Buscador();
            }
            else if(criterio.StartsWith(criterio))
            {
                procura = new BuscadorTimes();
            }
            return procura.GetResultado(criterio);
        }
        public Dicionario(string _criterio)
        {
            criterio = _criterio;
        }
    }
}

 

Observe que neste código temos agora a decisão em tempo de execução sobre qual implementação será usada: Buscador ou BuscadorTimes.(E isso será transparente para nós.)

 

Você deve estar pensando "mas ainda existe um acoplamento, visto que temos que usar o new para criar a instância da implementação a ser usada."

 

Bem observado garoto !!!!

 

Ganhamos flexibilidade com o uso da interface  mas podemos melhorar...

 

A solução para isso seria separar a lógica da criação de objetos em outro componente, dessa forma este outro componente tomaria a decisão de qual implementação usar. ok !.

 

Podemos fazer isso  usando o padrão Factory criando uma nova classe chamada BuscadorFactory e depois alterando o código da classe Dicionario para usar o factory conforme o código abaixo:

 

Dica: Crie uma nova classe chamada BuscadorFactory
namespace UInterface
{
    class BuscadorFactory
    {
        public static string ESP = "ESP";
        public static string TIM = "TIM";
        public IBuscador getBuscador(string tipo)
        {
            IBuscador procura = null;
            if (tipo.Equals(ESP))
            {
                procura = new Buscador();
            }
            else if (tipo.Equals(TIM))
            {
                procura = new BuscadorTimes();
            }
            return procura;
        }         
    }
}
Depois altere a classe Dicionario para usar o factory conforme o
 código ao lado =>
using System.Collections.Generic;
namespace UInterface
{
    public class Dicionario
    {
        private string criterio = null;
        IBuscador procura = null;
               
      public List<string> Procurar(List<string> Lista)
      {
        BuscadorFactory factory = new BuscadorFactory();
        if(criterio.EndsWith(criterio))
        {
            procura = factory.getBuscador(BuscadorFactory.ESP);
        }
        else if(criterio.StartsWith(criterio))
        {
            procura = factory.getBuscador(BuscadorFactory.TIM);
        }
        return procura.GetResultado(criterio);
      }
      public Dicionario(string _criterio)
      {
          criterio = _criterio;
      }
    }
}


Agora para encerrar eu lanço o desafio : Como melhorar ainda mais o código eliminando totalmente o uso do new ????

 

Dica : Você já ouviu falar em "Inversão de controle "...(Martin Fowler)

 

Assim mostrei que a simples utilização de uma interface muda todo o comportamento do código e muda para melhor...

 

Pegue o projeto completo aqui: UInterface.zip

"Aquele que diz: Eu o conheço, e não guarda os seus mandamentos, é mentiroso, e nele não está a verdade; " 1 João 2:4

Referências:


José Carlos Macoratti