Executando verificação de segurança...
4

OOP - Quando usar Interface e quando usar uma Classe Abstrata

Ao ler o título desse artigo, talvez você acredite que vai finalmente encontrar a solução para esse dilema. Ou talvez você nem sequer sabia sobre esse dilema e acabou descobrindo por acaso. De qualquer maneira, se você desenvolve usando Orientação a Objeto, espero que esta leitura te seja útil!

A diferença entre uma Interface e uma Classe Abstrata

Há duas questões importantes que eu aprendi a levar em consideração quando preciso decidir usar uma Interface ou uma Classe Abstrata. A primeira delas é o tipo de relação e a segunda é o princípio Don’t repeat yourself (não se repita).

Uma Interface e uma Classe Abstrata representam tipos de relação diferentes com suas herdeiras. Uma classe que deriva de uma Interface carrega consigo a relação ”pode fazer”, pois está determinado por contrato que aquela classe deverá ter, no mínimo, os métodos e as propriedades da Interface que herdou. No entanto, em uma situação onde duas classes distintas herdam da mesma Interface, não há o que garanta que essas classes tenham qualquer relação uma com a outra. Já uma classe que herda de uma Classe Abstrata carrega consigo a relação ”é um(a)”, que deixa explícito a quem ver o código que duas ou mais classes que herdaram daquela Classe Abstrata fazem parte da mesma hierarquia.

Outra diferença além das relações está na maneira de como o código será aplicado. Uma Interface não permite que se insira nela a implementação dos métodos, mas apenas suas declarações. Isso significa que para cada classe que herdar de uma Interface, será necessário que o desenvolvedor implemente a lógica dos métodos. Obviamente isso não é um problema quando a lógica dos métodos é diferente para cada classe que herdou da Interface, mas se esse não for o caso, haverá duplicação de código e o sistema estará violando o DRY e correndo o risco de ter problemas por causa dessa prática. Em contrapartida, uma Classe Abstrata permite o desenvolvedor que implemente nela a lógica dos métodos, que por sua vez serão reutilizados pelas classes herdeiras sem que seja necessário repetir a implementação e consequentemente mantendo o sistema sem duplicação de código.

Decidir qual usar

Acredito que saber a diferença entre as duas abordagens já seja o suficiente para tomar a decisão. Mas vou listar aqui uma espécie de questionário que faço a mim mesmo quando preciso escolher entre usar uma Interface ou uma Classe Abstrata.

  • Que tipo de relação faz mais sentido aqui, “pode fazer” ou “é um(a)”?
  • As classes herdeiras sempre farão parte da mesma hierarquia?
  • Se eu optar por uma Interface, vou precisar repetir código na hora de implementar os métodos?

Dados essas simples perguntas, eu reflito sobre qual será a melhor escolha para as necessidades do sistema.

Exemplo prático de uso de uma Classe Abstrata

Tudo o que escrevi aqui foi fruto do que aprendi em um curso de DDD (Domain Driven Design), que inclusive é gratuito. Portanto, nada mais justo do que trazer aqui o exemplo de uma Classe Abstrata sendo usada para definir a classe base de Entidade, algo utilizado em sistemas que seguem DDD.

Contextualizando rapidamente: uma classe base pode servir para, além de definir métodos e propriedades, ajudar o desenvolvedor que trabalhar no projeto a identificar rapidamente o que está definido como Entidade e o que está definido como Objeto de Valor.

public abstract class Entity
{
	public long Id { get; private set; }
	
	public override bool Equals(object obj)
	{
		var other = obj as Entity;
		
		if (ReferenceEquals(other, null))
			return false;

		if (ReferenceEquals(this, other))
			return true;

		if (GetType() != other.GetType())
			return false;

		if (Id == 0 || other.Id == 0)
			return false;

		return Id == other.Id;
	}

	public static bool operator ==(Entity a, Entity b)
	{
		if (ReferenceEquals(a, null) && ReferenceEquals(b, null)
			return true;

		if (ReferenceEquals(a, null) || ReferenceEquals(b, null)
			return false;

		return a.Equals(b);
	}

	...
}

Exemplo retirado do curso de DDD da Pluralsight.

Esse exemplo não está completo, mas é o suficiente para ilustrar o que trago nesse artigo. Em Domain Driven Design, uma Entidade para ser igual a outra deve ser igual em referência e em Id. Portanto é daí que surge a necessidade de reescrever o método Equals e o operador de igualdade (==). A escolha de uma Classe Abstrata para esse caso se dá porque é uma relação de ”é uma Entidade”, ****portanto fazem parte da mesma hierarquia, e as lógicas dos métodos definidos já estão feitos e serão os mesmos para todas as classes herdeiras, ou seja, não haverá duplicação de código.

Agradeço a quem leu até aqui, e deixo aqui abaixo alguns links úteis sobre temas que abordei no artigo, mas não aprofundei:

Coloco também mais um artigo meu para quem gosta de Clean Code:

Carregando publicação patrocinada...
2

Complementando o artigo.

Quando você de fato começa a fazer testes unitários da sua aplicação (principalmente unitários), você entenderá a importância de interfaces, uma vez que elas são essenciais para realizar mocks.

Sobre a frase "não há o que garanta que essas classes tenham qualquer relação uma com a outra", eu não concordo. Se temos a seguinte interface:

public interface IConverter<T1, T2>
{
    public T2 Convert(T1 value);
}

Qualquer classe que implemente essa interface será uma classe que sabe converter um tipo T1 em um tipo T2. Dessa forma, já relação entre elas.

A proposta dessas duas entidades (interface e classe abstrata) são muito diferentes, e caso quem leu o artigo ainda não tenha entendido, é super importante procurar mais informações (como no curso sugerido), para não fazer besteira.

1

Olá Arthur, tudo bem?
Eu evito criar Interfaces pra todas as classes, apesar de saber que é um padrão em alguns projetos. Mas na hora de testar realmente as vezes não tem pra onde correr, mockar uma interface é muito mais prático.

Obrigado pelo complemento!

1

Comecei a estudar POO agora (com Java).. e a primeira coisa que me veio a cabeça quando aprendi interface foi "Ta mas que inutil...eu preciso criar uma interface só pra dizer oq outra classe deve ter.. precisando reescrever toda função do zero.. nao seria melhor só extender uma classe mae?"
Mas compreendo que em projetos maiores.. talvez faça mais sentido que existam classes com as mesmas funções porem com coportamentos diferentes.. ainda preciso absorver isso..

3

Um exemplo que me vem a mente pra te dar agora é: imagina que no seu projeto você vai lidar com um banco de dados SQL e também um banco de dados em memória. Você então decide usar o padrão de repositórios para fazer as operações nos bancos. Nesse caso você poderia criar uma interface Repository definindo os métodos de escrita, leitura, atualização e deleção (o CRUD básico). Depois você faria duas classes, uma SqlRepository e uma MemoryRepository que vão extender de sua interface Repository.
Nesse cenário ambas as classes vão ter os mesmos métodos, porém com implementações diferentes, porque a maneira de fazer operações nesses bancos é diferente.

Outros casos onde o uso de interfaces é muito útil é pra realizar testes unitários e para aplicar Injeção de Dependência, Inversão de Dependência, e diversos outros patterns e boas práticas.

Também estou a pouco tempo estudando POO. Trabalho com C# há 8 meses, antes disso programei por pouco mais de 1 ano só com Javascript. Tem sido bem desafiador, pois tem coisas infinitas pra aprender, mas ao mesmo tempo é gratificante toda vez que entendo algo novo da programação orientada a objeto.

Obrigado pelo comentário, e bons estudos!

1

Um exemplo real que apliquei num ecommerce foi a aplicação de um cupom em uma entidade, criamos uma interface que define os métodos get, set, put e delete (ex).
Depois para cada tipo de cupom foram criadas classes que implementaram a interface. Cada classe por sua vez implementaram sua própria lógica de get,set, put e delete.

Nessa implantação usamos o um pattern chamado Strategy Design Pattern.

Para ler mais sobre o Strategy Design Pattern eu recomendo esse site:
Source Making