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

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

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

Это то, что используется в качестве наших «Репозиториев». Они принимают объекты доступа к данным, такие как классы-оболочки для соединения 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>());
    }
}

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

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

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

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


person Brandon Martinez    schedule 13.11.2011    source источник
comment
В коде, который вы показываете, есть некоторые ошибки. Этот вопрос очень большой по сравнению с обычными вопросами о переполнении стека.   -  person Salvatore Previti    schedule 13.11.2011
comment
Хорошо, я больше изучил код, и этот код пахнет ... мне кажется, что это неправильное использование дженериков и программирования шаблонов. Не смешивайте отражение (Activator.CreateInstance) с дженериками странным образом... проблема в том, что IReportFactory должен быть универсальным типом, а не предоставлять универсальный метод.   -  person Salvatore Previti    schedule 13.11.2011
comment
Это к вашему исходному сообщению: во-первых: это был краткий код (внутренние вещи, которыми я не хотел делиться и тому подобное); не уверен, что он полностью скомпилируется. Во-вторых: должно возвращать тип IReport‹T›, поскольку есть требование домена иметь определенный конструктор (на самом деле я называю это: return (TR)Activator.CreateInstance(typeof(TR), _parameterVariable ); это требование относится к внедрению зависимостей. В-третьих: это все еще не решает проблему указания возвращаемого типа DataTable. Если это вообще возможно, я хотел бы даже избежать этого, выведя тип.   -  person Brandon Martinez    schedule 13.11.2011
comment
Кроме того, если мне не следует использовать Activator.CreateInstance, что мне следует использовать? Нужно ли мне создавать более 30 методов для каждого типа отчета? Кажется, победить цель фабрики динамических классов.   -  person Brandon Martinez    schedule 13.11.2011
comment
Вы просто не можете вывести общий тип таким образом, язык не позволяет вам это делать. Динамическая фабрика + дженерики кажутся немного странными... дженерики используются для строгой типизации вашего кода, а динамические вместо этого требуют, чтобы ваш код был менее типизированным, они кажутся двумя разными вещами.   -  person Salvatore Previti    schedule 13.11.2011
comment
Ваша реализация IReportFactory выглядит подозрительно.   -  person leppie    schedule 13.11.2011
comment
Предложение по Activator.CreateInstance. Вы говорите, что используете его вместе с DI. Вы можете сделать ReportFactory IocReportFactory, где GenerateNewReport вместо этого возвращает _ioc.GetInstance‹TR›(). Это делает много предположений об остальной части вашего кода, но может решить проблему, на которую указал Сальваторе.   -  person Obishawn    schedule 15.11.2011
comment
@BrandonMartinez: Кажется, это было отредактировано. Теперь выглядит нормально :)   -  person leppie    schedule 15.11.2011


Ответы (2)


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

person Obishawn    schedule 15.11.2011

Я не думаю, что компилятор сможет вывести 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);
    }
}
person svick    schedule 13.11.2011