Возвратите объект как интерфейс из универсального метода - PullRequest
2 голосов
/ 09 февраля 2012

У меня есть один интерфейс InterfaceBase и некоторые интерфейсы, полученные из него Interface1, Interface2. Далее у меня есть классы, которые реализуют интерфейсы InterfaceX, а не базовый.

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

Interface1 concrete1 = Factory.Get<Interface1>();

Вот моя (примерная) реализация фабрики, которая не работает:

  public static class Factory {

    public static T Get<T>() where T: InterfaceBase{

      Type type = typeof(T);

      //return new Concrete1() as T; // type T cannot be used with the as
      //return new Concrete1() as type; //type not found
      //return new Concrete1(); // cannot implicitly convert
      //return new Concrete1() as InterfaceBase; //cannot convert IBase to T
      //return new Concrete1() as Interface1; //cannot convert Interface1 to T
    }
  }

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

Ответы [ 5 ]

5 голосов
/ 09 февраля 2012

Methinks - это то, что вы ищете, это «инъекция зависимости бедного человека». Полагаю, для этого вам следует использовать настоящий контейнер IoC, существует множество вариантов (Unity, Castle Windsor, Ninject ...).

Но в любом случае, если вы настаиваете на том, чтобы сделать это самостоятельно, следуйте рекомендациям @Sergey Kudriavtsev. Просто убедитесь, что для каждого интерфейса вы возвращаете соответствующий конкретный класс. Примерно так:

public interface InterfaceBase { }
public interface Interface1 : InterfaceBase { }
public interface InterfaceX : InterfaceBase { }

public class Concrete1 : Interface1 { }
public class ConcreteX : InterfaceX { }

public static class Factory
{
    public static T Get<T>()
        where T : InterfaceBase
    {
        if (typeof(Interface1).IsAssignableFrom(typeof(T)))
        {
            return (T)(InterfaceBase)new Concrete1();
        }
        // ...
        else if (typeof(InterfaceX).IsAssignableFrom(typeof(T)))
        {
            return (T)(InterfaceBase)new ConcreteX();
        }

        throw new ArgumentException("Invalid type " + typeof(T).Name, "T"); // Avoids "not all code paths return a value".
    }
}

И вы вызываете это, передавая интерфейсную ссылку на завод:

var instance = factory.Get<Interface1>();
2 голосов
/ 09 февраля 2012

Чтобы ответить на одну часть вашего вопроса:

return new Concrete1() as T; // type T cannot be used with the as 

Тип T нельзя использовать с as, поскольку T не известен как ссылочный тип. Вы можете ограничить класс ссылочным типом с помощью ограничения where T : class [, ...]. Это позволит вам использовать оператор as, если, конечно, вам не нужно будет использовать этот метод с типами значений.

EDIT

Сказав это, я предпочитаю ответ Рсенны. Поскольку во время компиляции вы знаете, что прямое приведение будет работать, более разумно использовать прямое приведение, чем as. . Я также согласен с его рекомендацией исследовать «реальные» контейнеры IoC, но я бы добавил, что глубокое понимание дженериков будет очень полезно для вас, когда вы узнаете о них. Поскольку вы говорите, что вы новичок в дженериках, подобное упражнение, вероятно, является хорошей идеей , потому что оно поможет вам узнать об дженериках и даст вам лучшее понимание ценности, которую могут использовать контейнеры IoC. добавить.

РЕДАКТИРОВАТЬ 2

Я вижу еще одну проблему: вы ограничиваете T как InterfaceBase, а затем приводите Concrete1 к T. Не известно, что Concrete1 является производным от T! Именно поэтому вы используете as приведение. Ответ таков: добавьте ограничение class, и все будет в порядке.

РЕДАКТИРОВАТЬ 3

Как указывает rsenna, вы также можете получить проверку типа во время выполнения с upcast и downcast:

return (T)(object)new Concrete1();

или

return (T)(InterfaceBase)new Concrete1();

Интересно, как это сравнить с точки зрения эффективности с

return new Concrete1() as T;

Я проверю позже сегодня, найду ли я для этого время.

2 голосов
/ 09 февраля 2012

Это должно работать:

return (T)(new Concrete1());

Кроме того, код для вызова метода фабрики должен быть таким:

1 голос
/ 09 февраля 2012

Делая что-то подобное

public static class Factory {

public static T Get<T>() where T: InterfaceBase{

  return (T) new Concrete1();
}

Невозможно набрать безопасно. Вы не можете гарантировать, что метод будет вызван с T == Concrete1. T может быть ЛЮБОМ подтипом InterfaceBase, Concrete1 - это только один из этих подтипов и не обязательно тот же T, поэтому компилятор не позволит вам приводить к T, так же, как он не позволяет приводить к строке или любому другому другой не связанный тип.

Activator.CreateInstance () является одним из способов обработки этого: CreateInstance действительно гарантирует, что построенный экземпляр имеет тип T, который является ожидаемым выходным значением метода.

0 голосов
/ 09 февраля 2012

Если вы знаете, что T является подтипом InterfaceBase (где T: InterfaceBase), тогда вы можете сделать тип возврата метода Get равным InterfaceBase:

public static InterfaceBase Get<T>() where T : InterfaceBase
{        
    return new Concrete1();
}

Этот метод может быть вызван с использованием подчиненного интерфейса InterfaceBase как T:

InterfaceBase ib = Factory.Get<Interface1>();

Если вы передадите что-либо, кроме подчиненного интерфейса InterfaceBase, как T, компилятор будет жаловаться.

Я думаю, что это лучший подход, но, как указывает @phoog, он работает только с конкретными классами для параметра типа T:

public static T Get<T>() where T : InterfaceBase
{
    Type type = typeof(T);
    if (t.IsAbstract || t.IsInterface)
{
        throw new ArgumentException(@"Only non-abstract classes supported as T type parameter.");
}
    return Activator.CreateInstance<T>();
}
...