Pode-se, é claro, invocar a lei das abstrações com vazamento , mas isso não é particularmente interessante porque postula que todas as abstrações são com vazamento. Pode-se argumentar a favor e contra essa conjectura, mas não ajuda se não compartilharmos uma compreensão do que queremos dizer com abstração e do que queremos dizer com vazamento . Portanto, primeiro tentarei delinear como vejo cada um destes termos:
Abstrações
Minha definição favorita de abstrações é derivada do APPP de Robert C. Martin :
"Uma abstração é a amplificação do essencial e a eliminação do irrelevante".
Assim, as interfaces não são, em si mesmas, abstrações . Eles são apenas abstrações se trazem à tona o que importa e ocultam o resto.
Leaky
O livro Princípios, padrões e práticas de injeção de dependência define o termo abstração com vazamento no contexto de injeção de dependência (DI). O polimorfismo e os princípios do SOLID desempenham um grande papel nesse contexto.
A partir do Princípio de Inversão de Dependência (DIP), segue-se, novamente citando APPP, que:
"clientes [...] possuem as interfaces abstratas"
O que isso significa é que os clientes (código de chamada) definem as abstrações necessárias e, em seguida, você implementa essa abstração.
Uma abstração com vazamento , na minha opinião, é uma abstração que viola o DIP, incluindo de alguma forma algumas funcionalidades que o cliente não precisa .
Dependências síncronas
Um cliente que implementa uma parte da lógica de negócios normalmente usa o DI para se dissociar de certos detalhes de implementação, como, geralmente, bancos de dados.
Considere um objeto de domínio que lida com uma solicitação de reserva de restaurante:
public class MaîtreD : IMaîtreD
{
public MaîtreD(int capacity, IReservationsRepository repository)
{
Capacity = capacity;
Repository = repository;
}
public int Capacity { get; }
public IReservationsRepository Repository { get; }
public int? TryAccept(Reservation reservation)
{
var reservations = Repository.ReadReservations(reservation.Date);
int reservedSeats = reservations.Sum(r => r.Quantity);
if (Capacity < reservedSeats + reservation.Quantity)
return null;
reservation.IsAccepted = true;
return Repository.Create(reservation);
}
}
Aqui, a IReservationsRepository
dependência é determinada exclusivamente pelo cliente, a MaîtreD
classe:
public interface IReservationsRepository
{
Reservation[] ReadReservations(DateTimeOffset date);
int Create(Reservation reservation);
}
Essa interface é totalmente síncrona, pois a MaîtreD
classe não precisa ser assíncrona.
Dependências assíncronas
Você pode alterar facilmente a interface para ser assíncrona:
public interface IReservationsRepository
{
Task<Reservation[]> ReadReservations(DateTimeOffset date);
Task<int> Create(Reservation reservation);
}
A MaîtreD
classe, no entanto, não precisa que esses métodos sejam assíncronos; portanto, agora o DIP é violado. Considero isso uma abstração com vazamento, porque um detalhe de implementação força o cliente a mudar. O TryAccept
método agora também deve se tornar assíncrono:
public async Task<int?> TryAccept(Reservation reservation)
{
var reservations =
await Repository.ReadReservations(reservation.Date);
int reservedSeats = reservations.Sum(r => r.Quantity);
if (Capacity < reservedSeats + reservation.Quantity)
return null;
reservation.IsAccepted = true;
return await Repository.Create(reservation);
}
Não há lógica inerente para que a lógica do domínio seja assíncrona, mas para oferecer suporte à assincronia da implementação, isso agora é necessário.
Melhores opções
Na NDC Sydney 2018 , dei uma palestra sobre esse tópico . Nele, também descrevo uma alternativa que não vaza. Também darei essa palestra em várias conferências em 2019, mas agora com o novo título de injeção Async .
Pretendo também publicar uma série de postagens no blog para acompanhar a palestra. Esses artigos já estão escritos e estão na minha fila de artigos, aguardando publicação, portanto, fique atento.