2013-05-09 3 views
1

Пусть говорят, у меня есть этот кусок кодаC# создать интерфейс для общего класса с обобщенными методами

public interface IFoo 
{ 
} 

public abstract class FooBase<TModel> : IFoo 
{ 
    public T Create<T>() where T : TModel; 
} 

public class Foo : FooBase<ModelBase> 
{ 
    public TModel Create<TModel>() 
    { 
     return Activator.CreateInstance(typeof(TModel)); 
    } 
} 

public abstract class ModelBase 
{ 
} 

public class ModelFoo : ModelBase 
{ 
} 

public class ModelBar : ModelBase 
{ 
} 

Теперь у меня есть этот класс

public static FooProvider 
{ 
    public IFoo Get<TModel>() 
    { 
     var provider = ...; // find which IFoo class has generic TModel 
     return provider; 
    } 
} 

И я могу управлять, чтобы позвонить это с

IFoo provider = FooProvider.Get<ModelBar>(); // -> instance of Foo 

Но я получаю экземпляр IFoo, следовательно, не имеют доступа к FooBase методов. Можно ли реализовать IFoo (или абстрактный абстрактный класс), чтобы предоставить декларацию метода, которая может быть вызвана без (обязательно) литья возвращаемого значения provider?

В идеале, я хотел бы быть в состоянии сделать

ModelBar bar = FooProvider.Get<ModelBar>().Create<ModelBar>(); 

Возможно ли это?

+2

Что о возвращении '' FooBase вместо 'IFoo'? – Jon

+0

Потому что мне нужно вернуть 'FooBase ' при вызове 'Get '. Как вы можете видеть, 'TModel' является' ModelBar', а не 'ModelBase', и я не могу использовать' Foo' в 'FooBase ' –

ответ

2

Поскольку вопрос возник, я отправляю второй ответ.Вот иерархия классов образец, который:

  • позволяет использовать одного поставщика для многих типов моделей
  • позволяет построить множество различных поставщиков и моделей на постоянной основе
  • проверки поставщика/модель совместимость в время компиляции
  • не нуждается в отливке во время использования

Код:

public interface IWhatever { } 

public class Foo { } 
public class Foo2 : Foo, IWhatever { } 

public class Bar { } 
public class Bar2 : Bar { } 
public class Bar3 : Bar, IWhatever { } 


public interface IModelProvider<T> 
{ 
    U Create<U>() where U : T; 
} 

public class FooProvider : IModelProvider<Foo> 
{ 
    public U Create<U>() where U : Foo 
    { 
     // create a proper "U" - for example Foo or Foo2 
     return Activator.CreateInstance<U>(); // simpliest 
    } 
} 

public class BarProvider : IModelProvider<Bar> 
{ 
    public U Create<U>() where U : Bar 
    { 
     // create a proper "U" - for example Bar, Bar2 or Bar3 
     // more verbose 
     if (typeof(U) == typeof(Bar)) return (U)new Bar(); 
     if (typeof(U) == typeof(Bar2)) return (U)(object)new Bar2(); 
     if (typeof(U) == typeof(Bar3)) return (U)(object)new Bar3(); 

     throw new Exception(); 
    } 
} 

public class WhateverProvider : IModelProvider<IWhatever> 
{ 
    public U Create<U>() where U : IWhatever, new() 
    { 
     // create a proper "U" - for example Foo2 or Bar3 
     return new U(); // really the simpliest 
    } 
} 

общественного класса VeryGenericProvider: IModelProvider { общественного TModel Create(), где TModel: новый() { вернуть новый TModel(); }}

public class ProviderFactory 
{ 
    public static IModelProvider<T> Get<T>() where T : new() 
    { 
     // somehow choose a provider for T, dumb implementation just for example purposes 
     if (typeof(T) == typeof(Foo)) return (IModelProvider<T>)new FooProvider(); 
     if (typeof(T) == typeof(Bar)) return (IModelProvider<T>)new BarProvider(); 
     if (typeof(T) == typeof(IWhatever)) return (IModelProvider<T>)new WhateverProvider(); 

     return VeryGenericProvider<T>(); 
    } 
} 

public static class ProviderTest 
{ 
    public static void test() 
    { 
     Foo foo = ProviderFactory.Get<Foo>().Create<Foo>(); 
     Foo2 foo2 = ProviderFactory.Get<Foo>().Create<Foo2>(); 

     // Bar2 bar2 = ProviderFactory.Get<Foo>().Create<Bar2>(); - compile error 
     Bar2 bar2 = ProviderFactory.Get<Bar>().Create<Bar2>(); // - ok! 

     Bar3 bar3 = ProviderFactory.Get<IWhatever>().Create<Bar3>(); 
    } 
} 

Этот код компилируется, но я не запустить его. Обратите внимание, что окончательное использование в «тесте» выполняет проверки типа и не требует каких-либо отбросов, но внутренняя реализация провайдера наверняка потребует нескольких действий - например, см. BarProvider, который вручную создает объекты. Даже если «мы точно знаем», что U - Bar2, нет способа сказать языку обновить ограничение по U от Bar до Bar2 - следовательно, необходим двойной бросок.

Я имею в виду следующее:

return (U)new Bar(); // is ok because U is constrained to 'Bar' 

против

return (U)new Bar2();   // will not compile, U:Bar is not related to Bar2:Bar 
return (U)(object)new Bar2(); // is ok: temporary cast to object 'erases' type information 
+1

У вас может быть более сильная проверка времени компиляции в вашем коде, чтобы определить, будет ли создание нового экземпляра успешным с помощью 'нового()' общего ограничения: 'U Create () где U: T, new()', который позволит вы просто «возвращаете новый U();», а не «return Activator.CreateInstance ()». –

+0

Да, действительно!Я скопировал код создания объекта из вопроса, я предполагаю, что он будет заменен чем-то более сложным, так как Activator может принимать параметры construciton, а ограничение 'new()' не может (по крайней мере, с .Net4/.5). Я не хотел усложнять образец больше, чем это было абсолютно необходимо. – quetzalcoatl

+0

Фактически, поскольку я уже написал 3 разных поставщика образцов, я обновил 'WhateverProvider', чтобы использовать ограничение' new() '. Спасибо за предложение! – quetzalcoatl

1

вы имели в виду

ModelBar bar = FooProvider.Get<ModelBar>().Create<ModelBar>(); 

или скорее

ModelBar bar = FooProvider.Get<ModelBar>().Create(); 

?

Я считаю, что первое немного странно, если у вас нет некоторых случаев, когда первый ModelBar на самом деле является ModelBarBase или IModelBar, а последний ModelBar фактически является окончательным конструктивным типом.

В любом случае, я также считаю, что ваш класс немного странный, как в вашем примере кода Get() и Create() происходят из одного класса (Foo/FooBase). Я думаю, вы перепутали с SRP здесь.

Я бы рекомендовал:

public class FooProvider 
{ 
    public static Provider<T> Get<T> { return new Provider<T>(); }; 
} 

public class Provider<T> 
{ 
    public T CreateDirect() 
    { 
     return Activator.Create<T>(); 
    } 

    public TDerived CreateDerived<TDerived>() where TDerived : T 
    { 
     return Activator.Create<TDerived>(); 
    } 
} 

и в этой установке вы можете:

class IMyInterf {} 
class MyType : IMyInterf {} 
class MyChildType : MyType {} 

MyType tmp1 = FooProvider.Get<MyType>().CreateDirect(); 

MyChildType tmp1 = FooProvider.Get<MyType>().CreateDerived<MyChildType>(); 

MyChildType tmp1 = FooProvider.Get<IMyInterf>().CreateDerived<MyChildType>(); 

Это только эскиз, написанный вручную без компиляции, но она показывает общую идею.

Если вы удалите «CreateDirect» и переименуете «CreateDerived» только для Create, вы получите нечто похожее на ваш код, но проще. И, конечно же, теперь вы можете смешивать и сквош эти два отдельных класса в один, если вам действительно нужно, но я не вижу никакой реальной точки в нем :)

EDIT:

И конечно вместо Provider<T> вы можете ввести a ProviderBase<T> и несколько OrangeProvider:ProviderBase<Orange> только для упрощения строительства/поиска подходящего поставщика, но все же фактический Get<> должен будет вернуть фактические ProviderBase<T> или IProvider<T>.

+0

. Идея моего дизайна заключается в том, что я хочу вернуть поставщика для данной модели * и * Я хотел бы иметь возможность заменить этого провайдера при тестировании (с помощью поставщика Mock). Ваше предложение не позволяет этого. Кроме того, один поставщик может обслуживать разные (известные) типы моделей. –

+0

Я уже говорил вам, что это эскиз/обзор. Последние две строки показывают, что один провайдер typef с помощью «X» может построить «Y» или «Z», поэтому выполняется одно для многих типов. Отказывание/замена поставщиков для тестирования тривиально: вместо того, чтобы возвращать «Provider » из 'Get ' - вернуть 'IProvider ' - и теперь ваш статический FooProvider может вернуть макет в тестовой среде. – quetzalcoatl

+0

Я имею в виду, даже с поддержкой насмешек, по-прежнему нет необходимости смешивать классы фабрики/поставщика/модели. – quetzalcoatl

Смежные вопросы