Можна, звичайно, посилатися на закон про проникнення абстракцій , але це не особливо цікаво, оскільки він стверджує, що всі абстракції є герметичними. Можна стверджувати , і проти цієї гіпотези, але це не допоможе , якщо ми не поділяємо розуміння того , що ми маємо в виду абстракції , і що ми маємо в виду Лики . Тому спершу спробую окреслити, як я бачу кожен із цих термінів:
Абстракції
Моє улюблене визначення абстракцій походить із додатка Роберта К. Мартіна :
"Абстракція - це посилення суттєвого та усунення нерелевантного".
Таким чином, інтерфейси самі по собі не є абстракціями . Вони є лише абстракціями, якщо винесуть на поверхню те, що має значення, а приховають решту.
Витік
Книга " Принципи, схеми та практики введення залежності" визначає термін протікання абстракції в контексті вприскування залежностей (DI). Поліморфізм та принципи SOLID відіграють велику роль у цьому контексті.
З Принципу інверсії залежності (DIP) випливає, знову цитуючи APPP, що:
"клієнти [...] володіють абстрактними інтерфейсами"
Це означає, що клієнти (код виклику) визначають необхідні абстракції, а потім ви переходите та впроваджуєте цю абстракцію.
На мою думку, витікаюча абстракція - це абстракція, яка порушує DIP тим чи іншим чином, включаючи певну функціональність, яка клієнту не потрібна .
Синхронні залежності
Клієнт, який реалізує ділову логіку, як правило, використовує 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 .
Планую також опублікувати серію дописів у блозі для супроводу бесіди. Ці статті вже написані і знаходяться в черзі моїх статей, чекаючи їх публікації, тому слідкуйте за новинами.