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: