2017-02-18 6 views
2

У меня есть несколько методов Get____Factory в моем приложении, и я хотел бы объединить их с дженериками, но я все еще настраиваю C# и a) я не уверен, что генерики - это правильный путь идти и б) я все еще изучаю, как C# обрабатывает дженерики.Использование обобщений для сопоставления типов интерфейсов классам

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

я начал что-то вроде этого:

Примечание: В конце концов будет словарем или способ отображения типов интерфейсов для их реализации - если/иначе условия некрасиво и временный характер, а просто способ контрольная работа.

public T GetFactory<T>() where T : IFactory { 
    var t = typeof(T); 

    if (t.Equals(typeof(IRecipeFactory))) { 
     var factory = new RecipeFactory(); 
     return factory; 
    } 

    else if (t.Equals(typeof(IItemFactory))) { 
     var factory = new ItemFactory(); 
     return factory; 
    } 

    else if (t.Equals(typeof(ITileFactory))) { 
     var factory = new TileFactory(); 
     return factory; 
    } 
} 

Сбой Cannot implicitly convert type 'RecipeFactory' to 'T', так что это не сработает. В конечном итоге у меня не будет условностей, но скорее будет искать класс по типу. Тем не менее, ни одна из них не будет работать, пока я не смогу найти решение для брошенной проблемы.

На основании других ответов, я попробовал двойное литье ((T) (object)), но это ошибки с InvalidCastException: Cannot cast from source type to destination type..

Либо это плохая архитектура, либо я неправильно использую дженерики.

+0

почему вы хотите, чтобы вернуть общий тип, но тогда у вас есть объект определенного типа в ответном заявлении? !! – niceman

+0

Я полагаю, что если вы разместите два или более примеров методов «Get ___ Factory» у вас есть, возможно, мы сможем помочь – niceman

+0

Как упоминалось в моем сообщении, я хочу предоставить сопоставление заводских интерфейсов с их экземплярами. Я могу завершить с дюжиной классов SomethingFactory. Этот метод доступен для плагинов, которые не имеют прямого доступа к реализациям, только интерфейсы. Я добавлю больше примеров – helion3

ответ

1

Вы должны будете отбросить объект до T на выходе, так как метод возвращает T. Для того, чтобы сделать этот бросок вам придется сделать factory в IFactory

public T GetFactory<T>() where T : IFactory 
{ 
    var t = typeof(T); 

    if (t.Equals(typeof(IRecipeFactory))) 
    { 
     IFactory factory = new RecipeFactory(); 
     return (T)factory; 
    } 

    if (t.Equals(typeof(IItemFactory))) 
    { 
     IFactory factory = new ItemFactory(); 
     return (T)factory; 
    } 

    if (t.Equals(typeof(ITileFactory))) 
    { 
     IFactory factory = new TileFactory(); 
     return (T)factory; 
    } 

    throw new InvalidOperationException("Type not supported"); 
} 
+0

Я пробовал это на ранней стадии, и это не удалось, но я не смог понять, что это не удалось из-за разных ошибок, а не потому, что я ошибался. – helion3

+0

Это функционально, но имеет тонну избыточного кода и нуждается в конкретном случае для каждого добавленного типа, который определенно не будет работать (по крайней мере, не изящно), если создание плагина открывается для общего сообщества. Я понимаю, что это всего лишь фиксированная версия кода OP, но см. Мой ответ для более подходящего подхода. –

+0

Вы правы. Подход, который использует OP, может и не быть «лучшим» способом. Однако вопрос ОП заключался не в том, как сделать это лучше, а в том, как заставить использовать этот подход. Выход за пределы этой области лучше всего оставить на другой вопрос, поскольку это совершенно другая тема. –

1

Позвольте мне сначала сказать, что вы на самом деле смотрит на это простой вариант инверсию структуры (МОК) управления. Взгляните на Ninject или что-то подобное, потому что это ядро ​​и фабрика связывания, в точности, именно то, что вы хотите. Он даже позволяет привязывать метаданные, чтобы вы могли иметь один и тот же интерфейс для разных реализаций в зависимости от обстоятельств, что действительно полезно, когда у вас есть уровень данных, который может потребоваться извлечь из источника веб-данных или источника данных кеша, например. Большинство структур МОК также предлагают рекурсивное разрешение зависимостей, а это означает, что когда в некоторых экземплярах есть конструкторы, которые требуют других зависимостей, одно и то же разрешение зависимостей происходит по всей цепочке на основе сопоставлений или сопоставлений по умолчанию, которые могут быть выведены.

Кроме того, чтобы сделать то, что вы за собой, вам нужно использовать Activator.CreateInstance, который берет тип и будет строить на нем новый экземпляр. Вы находитесь на правильном пути со своими словарными сопоставлениями. Когда вы связываете эти два вместе, вам не нужна условная логика, и вам не нужно заранее знать или заботиться о том, какой тип запрашивается. Когда вы чувствуете себя комфортно, вы действительно можете сократить разрешение и создание зависимостей на одну строку, если хотите.

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

using System; 
using System.Collections.Generic; 

namespace Generics 
{ 
    // create some dummy interfaces and implementations. 
    // make sure everything inherits from the same type to allow for 
    // a generic return statement 
    public interface IFactory 
    { 
     void DoStuff(); 
    } 
    public interface IFactory1 : IFactory { } 
    public class Factory1 : IFactory1 
    { 
     public void DoStuff() 
     { 
      Console.WriteLine("Factory1"); 
     } 
    } 
    public interface IFactory2 : IFactory { } 
    public class Factory2 : IFactory2 
    { 
     public void DoStuff() 
     { 
      Console.WriteLine("Factory2"); 
     } 
    } 


    class Program 
    { 
     // create our binding mappings 
     IDictionary<Type, Type> bindings = new Dictionary<Type, Type>() 
      { 
       // expose a way for plugins/etc to add to this. that part is trivial. 
       {typeof(IFactory1), typeof(Factory1) }, 
       {typeof(IFactory2), typeof(Factory2) } 
      }; 

     // a method to actually resolve bindings based on expected types 
     public IFactory ResolveBinding<T>() where T : IFactory 
     { 
      Type requestedType = typeof(T); 
      if (requestedType != null && bindings.ContainsKey(requestedType)) 
      { 
       // use the activator to generically create an instance 
       return (T) Activator.CreateInstance(bindings[requestedType]); 
      } 

      return null; 
     } 

     // test it out 
     static void Main(string[] args) 
     { 
      Program demo = new Program(); 
      // test with two interfaces 
      demo.ResolveBinding<IFactory1>().DoStuff(); // prints out "Factory1" 
      demo.ResolveBinding<IFactory2>().DoStuff(); // prints out "Factory2" 
      Console.ReadKey(); 
     } 
    } 
} 
1

Вот немного решение отличается от решения подкожных в.

public static class FactoryService 
{ 
    private static readonly Dictionary<Type, Func<IFactory>> factories = new Dictionary<Type, Func<IFactory>>() 
    { 
     { typeof(IRecipeFactory),() => new RecipeFactory() }, 
     { typeof(IItemFactory),() => new ItemFactory() }, 
     { typeof(ITileFactory),() => new TileFactory() } 
    }; 

    public static T GetFactory<T>() where T : IFactory 
    { 
     T factory = default(T); 
     Type requestedType = typeof(T); 

     if (factories.ContainsKey(requestedType)) 
     { 
      factory = (T)factories[requestedType].Invoke(); 
     } 

     return factory; 
    } 
} 

public interface IFactory { } 

public interface IRecipeFactory : IFactory { } 

public interface IItemFactory : IFactory { } 

public interface ITileFactory : IFactory { } 

public class RecipeFactory : IRecipeFactory { } 

public class ItemFactory : IItemFactory { } 

public class TileFactory : ITileFactory { } 

Тогда вы использовать его как это:

IRecipeFactory rf = FactoryService.GetFactory<IRecipeFactory>();