2009-10-20 2 views
4

Я новичок в генериках и пытаюсь выяснить, как я могу вернуть экземпляр класса, базовая база которого основана на заводе. См. Пример кода ниже. Вопросы выделены в заводском классе:Generics and Factory

public abstract class MyGenericBaseClass<T> 
{ 
    public string Foo() 
    {...} 
} 

public sealed class MyDerivedIntClass : MyGenericBaseClass<int> 
{ 

} 

public sealed class MyDerivedStringClass : MyGenericBaseClass<string> 
{ 

} 

public static class MyClassFactory 
{ 
    public static MyGenericBaseClass<T> CreateMyClass<T>() 
    { 
     // ********************************************** 
     if (typeof(T) == typeof(int)) 
     { 
      return new MyDerivedIntClass(); 
     } 

     if (typeof(T) == typeof(string)) 
     { 
      return new MyDerivedStringClass(); 
     } 
     // ********************************************** 
    } 
} 

Как обойти это?

Благодаря тонну заранее

Ohgee

+0

«Отойдите», что? –

+1

Я не понимаю, почему вы используете общий завод, если фактические типы, которые вы используете, вообще не являются общими. Я думаю, вы должны пересмотреть свой дизайн. – Joren

ответ

0

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

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

0

Я не думаю, что это обязательно плохо. В какой-то момент вам нужно объявить тип создаваемого объекта. Скрывая вызов конструктора ваших различных производных классов, фабрика позволяет вам иметь одно место, где известны конкретные классы.

Существует несколько способов сделать это: набор инструкций if, словарь, файл конфигурации xml предприятия, но в конечном итоге вам нужно каким-то образом сопоставить параметр типа int с новым экземпляром MyDerivedIntClass. Это может быть не очень красиво, но, по крайней мере, вы только пишете код один раз.

7

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

public interface INonGenericInterface 
{ 
    string Foo(); 
} 

public abstract class MyGenericBaseClass<T> : INonGenericInterface 
{ 
    public string Foo() 
    {...} 
} 

public static class MyClassFactory 
{ 
    public static INonGenericInterface CreateMyDerivedIntClass() 
    { 
     return new MyDerivedIntClass(); 
    } 

    public static INonGenericInterface CreateMyDerivedStringClass() 
    { 
     return new MyDerivedStringClass(); 
    } 
} 

Таким образом, вы четко, какие типы могут быть созданы и до сих пор разъединить абонента от конкретных типов.

Конечно, для этого сценария вам необязательно нужен не общий интерфейс, но на самом деле вам, скорее всего, понадобится.

+0

Вы избили меня до этого: p Но это правильный путь! – Stormenet

+0

Неправильно с 'public INonGenericInterface CreateOnRequirement () где INonGenericInterface, new() { return new T(); } ' –

+0

В этом вопросе пользователь фабрики не знает возвращаемого типа (например,' MyDerivedIntClass'). Он знает только общий аргумент 'T' (например,' int'). Поэтому 'new T()' не работает. Если он будет знать «MyDerivedIntClass», он может напрямую вызвать сам конструктор и не должен иметь фабрику. –

3

Прежде всего, непонятно, почему вы создаете классы, полученные из вашего общего класса, а не напрямую используя общий класс. Также не понятно, почему вам нужен завод, а не просто создавать любые классы, которые вам нужны напрямую. Эти шаблоны важны и полезны, часто единственный способ решить определенные проблемы, но они не соответствуют требованиям . У вас должны быть веские причины для их реализации.

Я предполагаю, что у вас действительно есть веские причины, по которым вы ушли из-за краткости.

Пункт использования шаблона фабрики заключается в том, чтобы часть кода создавала экземпляр правильного класса без этого кода, зная правильный класс. Обратите внимание, что в вашем примере это разделение отсутствует: тот, кто звонит MyClassFactory.CreateMyClass<T>()должен знать правильный класс, так как этот код должен передать тип как общий параметр, который определяет правильный класс. Если я знаю достаточно, чтобы позвонить CreateMyClass<int>(), то я достаточно знаю, чтобы позвонить new MyDerivedIntClass().

Существует два основных способа реализации шаблона фабрики: статические функции и заводские классы.

С обоими методами, вы будете нуждаться в абстрактный базовый класс или интерфейс «под» дженериков:

public interface IMyInterface 
{ 
    string Foo(); 
} 

public abstract class MyGenericBaseClass<T> : IMyInterface 
{ 
    // ... 
    abstract /* or virtual */ string Foo(); 
} 

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

// note: I'm not sure this is the correct syntax, but I think I'm in the ballpark 
delegate IMyInterface MyInterfaceFactory(); 

и классы могут реализовать их, чтобы вернуть правильный тип.

public sealed class MyDerivedIntClass : MyGenericBaseClass<int> 
{ 
    // ... 
    static IMyInterface CreateObject() { return new MyDerivedIntClass(); } 
} 

public sealed class MyDerivedStringClass : MyGenericBaseClass<string> 
{ 
    // ... 
    static IMyInterface CreateObject() { return new MyDerivedStringClass(); } 
} 

Вы передаете статическую функцию к функции, который создает объект:

// ... somewhere else in the code ... 

// create a IMyInterface object using a factory method and do something with it 
void Bar (MyInterfaceFactory factory) 
{ 
    IMyInterface mySomething = factory(); 
    string foo = mySomething.Foo(); 
} 

// ... somewhere else in the code ... 
void FooBarAnInt() 
{ 
    Bar (MyDerivedIntClass.CreateObject); 
} 

Второй способ заключается в использовании фабричных классов:

public interface IMyInterfaceFactory 
{ 
    IMyInterface CreateObject(); 
} 

public class MyDerivedIntFactory : IMyInterfaceFactory 
{ 
    public IMyInterface CreateObject() { return new MyDerivedIntClass(); } 
} 

public class MyDerivedStringFactory : IMyInterfaceFactory 
{ 
    public IMyInterface CreateObject() { return new MyDerivedStringClass(); } 
} 

// ... somewhere else ... 

// create a IMyInterface object using a factory class and do something with it 
void Bar (IMyInterfaceFactory factory) 
{ 
    IMyInterface mySomething = factory.CreateObject(); 
    string foo = mySomething.Foo(); 
} 

// ... somewhere else in the code ... 
void FooBarAnInt() 
{ 
    Bar (new MyDerivedIntFactory()); 
} 

Обратите внимание, что вы можете (и, вероятно, должен) также сделать заводские классы одиночными, но это другая проблема. Кроме того, вы можете использовать абстрактный базовый класс вместо интерфейса; вам нужно будет добавить abstract (или virtual) и override при необходимости.

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

+0

спасибо за тонну за все ответы. получившее здесь огромное образование. Основная причина для этого маршрута такова: у меня есть базовый класс с свойством «ID», тип данных которого определяется во время выполнения (строка, int или long являются основными обработаны). Заводским классом/методом является возврат производного класса с соответствующим типом данных для идентификатора производного класса. вот почему я думаю, что не могу идти по пути отсутствия базового класса/интерфейса. – OhGee

2

Я обычно использую словарь < строку, тип > для каждого из моих фабрик, где я регистрирующих каждый доступный тип реализации, что завод может вернуться (хмм .. увидеть это для получения дополнительной информации: Is a switch statement applicable in a factory method?

Вы можете адаптировать это к вашим потребностям и держать словарь < типа, типу > из всех производных классов.

... 
Factories.StaticDictionary.Add(typeof(int),typeof(MyDerivedIntClass)); 
Factories.StaticDictionary.Add(typeof(string),typeof(MyDerivedStringClass)); 
... 

(или у каждый из производных классов добавить к этому словарю автоматически)

Тогда ваш завод будет просто:

public static class MyClassFactory 
    { 
     public static MyGenericBaseClass<T> CreateMyClass<T>() 
     { 
      Activator.CreateInstance(
      Factories.StaticDictionary[typeof(T)]); 
     } 

    } 
0

Если вы хотите пойти дальше, чем некоторые из примеров, которые другие выложили, вы должны смотреть на рамках инъекции зависимостей.

0

спасибо тонну за все ответы. получившее здесь огромное образование.

Основная причина для этого маршрута такова: у меня есть базовый класс с свойством «ID», тип данных которого определяется во время выполнения (строка, int или long являются основными обработанными). Заводским классом/методом является возврат производного класса с соответствующим типом данных для идентификатора производного класса. вот почему я думаю, что не могу идти по пути отсутствия базового класса/интерфейса.

нашел эту идею около 30 минут назад, которая делает это в фабричный метод:

public static class MyClassFactory 
{  
    public static MyGenericBaseClass<T> CreateMyClass<T>()  
    {   
     // **********************************************   
     if (typeof(T) == typeof(int))   
     { 
      MyGenericBaseClass<int> typedDerived = new MyDerivedIntClass(); 
      return (MyGenericBaseClass<T>)(object)typedDerived;  
     }   

     if (typeof(T) == typeof(string))   
     {    
      MyGenericBaseClass<string> typedDerived = new MyDerivedStringClass(); 
      return (MyGenericBaseClass<T>)(object)typedDerived;   
     }   
     // **********************************************  
    } 
} 

выглядит Hacky, особенно с двойным броском, но, кажется, работает. что вы думаете в первую очередь с точки зрения ремонтопригодности?