Организация структуры фабрики

Есть структура продуктов (упрощённо):

public class ParentProduct { }

public sealed class ChildProduct1 : ParentProduct { }

public sealed class ChildProduct2 : ParentProduct { }

Отдельно от этих типов есть enum для каждого типа. Нужно так же учесть, что этот enum и все ParentProduct наследники не связаны напрямую

public enum ProductType
{
    Parent = 1,
    Child1 = 2,
    Child2 = 3,
}

Есть примерно такая же структура обработчиков как ниже:

public interface IProductHandler<T> where T : ParentProduct
{
    public Task Add(T product);
}

public sealed class ParentHandler : IProductHandler<ParentProduct>
{
    public async Task Add(ParentProduct product)
    {
        // ...
    }
}

public sealed class Child1Handler : IProductHandler<ChildProduct1>
{
    public async Task Add(ChildProduct1 product)
    {
        // ...
    }
}

public sealed class Child2Handler : IProductHandler<ChildProduct2>
{
    public async Task Add(ChildProduct2 product)
    {
        // ...
    }
}

Теперь появилась необходимость создания фабрики, которая по ProductType будет делать соответствующий обработчик. Но конкретно на этом этапе я не понимаю, как правильно реорганизовать структуру, что бы работало примерно следующим образом:

public sealed class ProductFactory
{
    public IProductHandler<T> GetHandler<T>(ProductType type) where T : ParentProduct =>
        type switch
        {
            ProductType.Parent => new ParentHandler()
        };
}

Так же стоит учесть, что с требований нужно обойтись БЕЗ использования явного приведения типов через as и сравнение через typeof и т.п. Иными словами, вот такой формат не подходит:

public interface IProductHandler
{
    public Task Add<T>(T product) where T : ParentProduct;
}

Или такой

public interface IProductHandler
{
    public Task Add(ParentProduct product);
}

Так как внутри реализации потребуется явно преобразовывать типы.

UPD: В комментариях спросили, как я собираюсь это использовать. В проекте есть несколько таких мест, в основном это экшен контроллера и ViewComponent. Код на примере использования в контроллере:

public sealed class ProductController(ProductFactory factory) : ControllerBase
{
    [HttpGet("{type}/{id:int}")]
    public async Task<IActionResult> DetDetails([FromRoute] ProductType type, [FromRoute] int id)
    {
        IProductHandler handler = factory.GetHandler(type)
        return Ok(await handler.GetDetails(id));
    }
}

Ответы (1 шт):

Автор решения: aepot

проблема в том, что не работает, т.к. нельзя напрямую конвертировать ParentHandler в IProductHandler<T> как сообщает мне ошибка

Ну это легко, контрвариантый интерфейс.

public interface IProductHandler<in T> where T : ParentProduct
{
    public Task Add(T product);
}

И всё, ошибка в фабрике ушла.

public sealed class ProductFactory
{
    public IProductHandler<T> GetHandler<T>(ProductType type) where T : ParentProduct =>
        type switch
        {
            ProductType.Parent => new ParentHandler() // OK
        };
}

Но я бы на вашем месте избавился бы от перечисления.

Например, если учесть, что все продукты имеют уникальный ID, то передача типа - избыточная информация, так как по ID вы получите конкретный экземпляр продукта, который уже имеет свой конкретный тип, по нему и можно будет определить тип хэндлера. Ну там уже насколько фантазии хватит. Хотя мне очень сильно кажется, что вся задумка с этими хэндлерами под сомнением, и продукт должен уметь сам себя сериализовать product.GetDetails(). То есть получать я бы стал некий IProduct, а не IProductHandler и вызывал бы метод сериализации у него.

→ Ссылка