Извеждане на типове при използване на генерични интерфейси във фабрика

Моите колеги и аз съставяме малка рамка за отчитане за онлайн магазин. Ние изградихме библиотека, следвайки модела на хранилище, използвайки „отчети“ като хранилища и много лек сервизен слой за взаимодействие с тези отчети. Нашият код работи чудесно и е доста лесен за използване. Има обаче едно нещо, което ме притеснява лично: на ниво фабрика за услуги трябва да декларираме нашия тип връщане два пъти (не се извежда). Ето основата на нашия проект:

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

Това е, което се използва като наши „Хранилища“. Те приемат обекти за достъп до данни, като класове обвивки към SQL/Oracle връзка или API на нашия Storefront.

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