Можно, конечно, ссылаться на закон об вытекающих абстракциях , но это не особенно интересно, поскольку в нем утверждается, что все абстракции являются вытекающими. Можно спорить за и против этой гипотезы, но это не поможет, если мы не разделяем понимание того, что мы подразумеваем под абстракцией , и что мы подразумеваем под утечкой . Поэтому сначала я попытаюсь описать, как я рассматриваю каждое из этих терминов:
Абстракции
Мое любимое определение абстракций взято из APPP Роберта Мартина :
«Абстракция - это усиление существенного и устранение несущественного».
Таким образом, интерфейсы сами по себе не являются абстракциями . Они только абстракции, если они выводят на поверхность то, что имеет значение, и скрывают все остальное.
неплотный
В книге « Принципы, модели и практики внедрения зависимостей» термин « вытекающая абстракция» используется в контексте внедрения зависимостей (DI). Полиморфизм и принципы SOLID играют большую роль в этом контексте.
Из принципа инверсии зависимостей (DIP) следует, снова цитируя APPP, что:
«клиенты [...] владеют абстрактными интерфейсами»
Это означает, что клиенты (вызывающий код) определяют требуемые им абстракции, а затем вы переходите к реализации этой абстракции.
Вытекающей абстракции , на мой взгляд, это абстракция , которая нарушает ДИП по каким - то образом , включая некоторые функции , которые клиент не нужно .
Синхронные зависимости
Клиент, который реализует часть бизнес-логики, обычно использует DI, чтобы отделить себя от определенных деталей реализации, таких как, как правило, базы данных.
Рассмотрим объект домена, который обрабатывает запрос на бронирование ресторана:
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);
}
}
Здесь IReservationsRepository
зависимость определяется исключительно клиентом, MaîtreD
классом:
public interface IReservationsRepository
{
Reservation[] ReadReservations(DateTimeOffset date);
int Create(Reservation reservation);
}
Этот интерфейс является полностью синхронным, поскольку MaîtreD
классу не нужно, чтобы он был асинхронным.
Асинхронные зависимости
Вы можете легко изменить интерфейс на асинхронный:
public interface IReservationsRepository
{
Task<Reservation[]> ReadReservations(DateTimeOffset date);
Task<int> Create(Reservation reservation);
}
MaîtreD
Класс, однако, не нужны эти методы , чтобы быть асинхронными, так что теперь DIP нарушается. Я считаю это утечкой абстракции, потому что детали реализации заставляют клиента меняться. TryAccept
Метод теперь также должен стать асинхронной:
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);
}
Нет логического обоснования асинхронности логики домена, но теперь для поддержки асинхронности реализации это необходимо.
Лучшие варианты
На NDC Sydney 2018 я выступил с докладом на эту тему . В нем я также обрисую альтернативу, которая не протекает. Я также буду выступать на нескольких конференциях в 2019 году, но теперь переименован в новый заголовок Async инъекция .
Я планирую также опубликовать серию постов в блоге, чтобы сопровождать доклад. Эти статьи уже написаны и находятся в моей очереди, ожидая публикации, так что следите за обновлениями.