Вывод типов при использовании универсальных интерфейсов на фабрике

#c# #generics #design-patterns #factory-pattern

#c# #универсальные #шаблоны проектирования #заводской шаблон

Вопрос:

Мы с коллегами собираем небольшую платформу отчетности для онлайн-магазина. Мы создали библиотеку по шаблону репозитория, используя «отчеты» в качестве репозиториев и очень легкий сервисный уровень для взаимодействия с указанными отчетами. Наш код отлично работает и довольно прост в использовании. Однако есть одна вещь, которая беспокоит меня лично: на уровне service factory нам нужно дважды объявлять наш возвращаемый тип (это не выводится). Вот основа нашего проекта:

Интерфейс отчета

Это то, что используется в качестве наших «репозиториев». Они принимают объекты доступа к данным, такие как классы-оболочки, к соединению SQL / Oracle или API нашего магазина.

 internal interface IReport<T>
{
    T GetReportData(dynamic options);
}
  

Фабрика репозитория

Это обеспечивает простой способ генерировать эти отчеты, зная тип.

 internal interface IReportFactory
{
    TR GenerateNewReport<T, TR>() where TR : IReport<T>;
}

internal class ReportFactory : IReportFactory
{
    public ReportFactory()
    {
        // some initialization stuff
    }

    public TR GenerateNewReport<T, TR>() where TR : IReport<T>
    {
        try
        {
            return (TR)Activator.CreateInstance(typeof(TR));
        }
        catch(Exception ex)
        {
            // Logging
        }
    }
}
  

Пример отчета (репозиторий)

Вот как будет выглядеть отчет. Обратите внимание, что он возвращает DataTable и объявляется с этим именем в универсальном интерфейсе (это будет воспроизведено в ближайшее время).

 internal class ItemReport : IReport<DataTable>
{
    public DataTable GetReportData(dynamic options)
    {
        return new DataTable();
    }
}
  

Служба отчетов

Облегченный сервис, который принимает отчет (репозиторий) и работает с ним. Легкий, но он позволяет легко проводить модульное тестирование и тому подобное, и если мы когда-нибудь захотим добавить дополнительную обработку после получения отчетов, это было бы легко сделать.

 public interface IReportService<T>
{
    T GetReportData(dynamic options);
}

public class ReportService<T> : IReportService<T>
{
    private readonly IReport<T> _report;

    public ReportService(IReport<T> report)
    {
        _report = report;
    }

    public T GetReportData(dynamic options)
    {
        return _report.GetReportData(options);
    }
}
  

Фабрика сервисов

У нас есть заводская настройка сервиса как абстрактного класса (потому что все фабрики сервисов должны будут создавать фабрику отчетов), принудительно используя конструктор по умолчанию:

 public abstract class ReportServiceFactory
{
    protected IReportFactory ReportFactory;

    protected ReportServiceFactory(connection strings and other stuff)
    {
        ReportFactory = new ReportFactory(connection strings and other stuff);
    }
}
  

Затем мы можем создавать отдельные фабрики сервисов на основе функции. Например, у нас есть фабрика обслуживания «стандартного отчета», а также фабрики обслуживания, специфичные для клиента. Мой вопрос заключается в реализации здесь.

 public class SpecificUserServiceFactory : ReportServiceFactory
{
    public SpecificUserServiceFactory(connection strings and other stuff) : base(connection strings and other stuff){}

    public IReport<DataTable> GetItemReport()
    {
        return new ReportService<DataTable>(ReportFactory.GenerateNewReport<DataTable, ItemReport>());
    }
}
  

Почему я должен быть таким подробным при создании service factory? Я объявляю возвращаемый тип дважды. Почему я не могу сделать что-то подобное:

 return new ReportService(ReportFactory.GenerateNewReport<ItemReport>());
  

Обратите внимание, что я не объявляю DataTable в этом; Я думаю, что это следует вывести из того факта, что ItemReport является iReport . Любые предложения о том, как заставить это работать таким образом, были бы высоко оценены.

Извините за такое длинное объяснение, чтобы задать такой простой вопрос, но я подумал, что весь этот вспомогательный код поможет найти решение. Еще раз спасибо!

Комментарии:

1. В показанном вами коде есть некоторые ошибки. Этот вопрос сильно отличается от обычных вопросов о переполнении стека.

2. Хорошо, я больше изучил код, и этот код пахнет… мне кажется, это неправильное использование универсалий и шаблонного программирования. Не смешивайте отражение (активатор. CreateInstance) с помощью дженериков странными способами… проблема в том, что IReportFactory должен быть универсальным типом, а не предоставлять универсальный метод.

3. Это к вашему исходному сообщению: Во-первых: это был обобщенный код (внутренний материал, которым я не хотел делиться и тому подобное); не уверен, будет ли он скомпилирован полностью. Второе: для этого необходимо возвращать тип iReport<T> , поскольку существует требование домена о наличии определенного конструктора (на самом деле я называю это: активатор возврата (TR). CreateInstance(typeof(TR), _parameterVariable); это требование для внедрения зависимостей. В-третьих: это все еще не решает проблему указания возвращаемого типа DataTable. Если это вообще возможно, я бы хотел даже избежать этого с помощью вывода типа.

4. Кроме того, если я не должен использовать Activator. CreateInstance, что я должен использовать? Нужно ли мне создавать более 30 методов для каждого типа отчета? Похоже, это противоречит цели динамической фабрики классов.

5. Вы просто не можете вывести универсальный тип таким образом, язык не позволяет вам этого делать. Динамическая фабрика дженерики кажется немного странной… универсальные используются для строгого ввода вашего кода, вместо этого dynamic требует, чтобы ваш код был менее типизированным, они кажутся двумя разными вещами.

Ответ №1:

Причина, по которой вы не можете опустить универсальный тип DataTable при вызове GenerateNewReport, заключается в том, что это ограничение для другого универсального типа в этом определении функции. Давайте предположим (для простоты), что ваш iReport<T> на самом деле был интерфейсом с функцией, аннулирующей что-либо (T input). Класс может реализовывать как iReport<int>, так и iReport<string> . Затем мы создаем такой класс с именем Foo : iReport<int>, iReport<string>. Компилятор не смог бы скомпилировать bar.GenerateNewReport<Foo> потому что он не знает, привязан ли он к типу iReport<int> или iReport<string> и, следовательно, не может определить подходящий возвращаемый тип для этого вызова.

Ответ №2:

Я не думаю, что компилятор сможет выводить DataTable из ItemReport . Но вы можете избежать указания DataTable дважды, используя статический универсальный метод в не-универсальном классе.

 ReportService.Create(reportFactory.GenerateNewReport<DataTable, ItemReport>())
  
 public static class ReportService
{
    public static ReportService<T> Create<T>(IReport<T> report)
    {
        return new ReportService<T>(report);
    }
}