Н
НеСерьезный шарпист
@serious_seesharp4.0K подп.
4.1Kпросмотров
25 августа 2024 г.
Score: 4.5K
Ковариантность и контрвариантность Наткнулся я тут недавно на очередное извращение в одном из рабочих проектов (ибо несколько официальных работ, по nda никто не притянет 😅). Но об этом чуть ниже, сейчас рассмотрим вообще, что такое ковариантность и контрвариантность, а потом на примере, как данные концепции можно применить на примере паттерна CQRS. Ковариантность позволяет использовать более конкретный производный тип, чем указанный тип-параметр. Ковариантность в интерфейсах применяется с использованием ключевого слова out, которое обозначает, что тип-параметр может использоваться как возвращаемое значение. Если же в приведенном примере не использовать out, получим ошибку. ICovariant<Animal> shelter = new DogShelter(); Animal animal = shelter.GetSomething(); public interface ICovariant<out T> { T GetSomething(); } public class Animal { } public class Dog : Animal { } public class DogShelter : ICovariant<Dog> { public Dog GetSomething() => new Dog(); } Контрвариантность позволяет использовать более базовый тип, чем указанный тип-параметр. Контрвариантность применяется с помощью ключевого слова in, которое указывает, что тип-параметр может использоваться как параметр метода. IContravariant<Dog> handler = new AnimalHandler(); handler.DoSomething(new Dog()); public interface IContravariant<in T> { void DoSomething(T value); } public class Animal { } public class Dog : Animal { } public class AnimalHandler : IContravariant<Animal> { public void DoSomething(Animal value) { Console.WriteLine(value.GetType().Name); } } Таким образом, если еще упростить: 👉 Ковариантность (out) позволяет использовать производные типы для обобщенных параметров на выходе (возвращаемые значения). 👉 Контрвариантность (in) позволяет использовать базовые типы для обобщенных параметров на входе. CQRS (Command Query Responsibility Segregation) разделяет операции с данными приложения на команды и запросы. В CQRS команды (commands) изменяют состояние системы, а запросы (queries) извлекают данные, но не изменяют их. То есть данный паттерн проектирования позволяет удобно поделить данную часть бизнесс-логики, смысла по этому поводу еще что-то говорить, не вижу. Команды и запросы принимают параметры, которые описывают действие, и эти параметры могут быть более общими, чем конкретные типы, с которыми работает система. Поэтому здесь ложится концепция контрвариантности. public interface IAsyncQuery<in TQueryModel, TResult> { Task<TResult> ExecuteAsync(TQueryModel model, CancellationToken ct = default); } public class GetRequestStatus : IAsyncQuery<Guid, RequestStatus> { private readonly ReadOnlyContext _context; public GetRequestStatus(ReadOnlyContext context) { _context = context; } protected async Task<RequestStatus> ExecuteCoreAsync(Guid id, CancellationToken ct) { RequestEntity request = await _context.RequestEntities .FirstAsync(r => r.Id == id, ct); return request.Status; } } Что делать, если у нас появилась команда/кверя, куда не нужно ничего передавать?! Товарищи разрабсы с моего проекта решили это очень "элегантно" - создали класс EmptyModel, которая ничего не содержит и передавали ее объект каждый раз)))) Не надо так делать, просто создаем еще IAsyncQruery без передаваемого значения и реализуем данный интерфейс. public interface IAsyncQuery<in TQueryModel, TResult> { Task<TResult> ExecuteAsync(TQueryModel model, CancellationToken ct = default); } public interface IAsyncQuery<TResult> { Task<TResult> ExecuteAsync(CancellationToken ct = default); } public class GetAllRequests : IAsyncQuery<List<RequestEntity>> { private readonly ReadOnlyContext _context; public GetRequestStatus(ReadOnlyContext context) { _context = context; } protected async Task<List<RequestEntity>> ExecuteCoreAsync(CancellationToken ct) { List<RequestEntity> requests = await _context.RequestEntities .AsNoTracking(
4.1K
просмотров
4000
символов
Да
эмодзи
Нет
медиа

Другие посты @serious_seesharp

Все посты канала →
Ковариантность и контрвариантность Наткнулся я тут недавно н — @serious_seesharp | PostSniper