Разрешение универсального интерфейса из универсального статического метода - является ли использование контейнера наиболее простым? - PullRequest
2 голосов
/ 01 июня 2011

У меня есть код, подобный этому:

    public static IEnumerable<T> ParseInput<T>(string input)
    {
        var xml = XElement.Parse(input);
// some more code here
        var parser = Container.Current.Resolve<IParser<T>>();
        return parser.Parse(xml);
    }

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

Есть ли лучший или альтернативный способ разрешения интерфейса, кроме этого и оператора switch?

Редактировать: По моему мнению, это связано с попыткой объединить тестируемое модульное OO-программирование (IoC, без статических классов) с функциональным программированием. Где самая чистая линия разделения? Возможно, разрешив парсер раньше и передав его статическому методу.

Ответы [ 2 ]

2 голосов
/ 01 июня 2011

Редактировать : Я немного упустил то, что вы имели в виду. Мне очень нравится использовать контейнер для таких вещей. Операторы Switch немного уродливы. Итак, в случае с контейнером вы можете сделать его более тестируемым:

Если some code here сложно, я стараюсь убедиться, что он не статичен, так как его статические методы не так легко тестировать при использовании внедрения зависимостей. Если это не сложно, ваш код мне подходит: o)

Вариант 1 : не использовать статический метод.

Вместо этого используйте метод экземпляра и вставьте в него анализатор.

public static IEnumerable<T> ParseInput<T>(string input)
{
    var parser = Container.Current.Resolve<IXmlParser<T>>();
    return parser.ParseInput(input);
}

public class XmlParser : IXmlParser<T>
{
  private readonly IParser<T> _parser;
  public XmlParser(IParser<T> parser)
  {
     _parser= parser;
  }
  public IEnumerable<T> ParseInput(string input)
  {
      var xml = XElement.Parse(input);
      // some more code here
      return _parser.Parse(xml);
  }
}

Опция 2 : передать распознаватель контейнера вместе со строкой ввода. Хотя при этом лучше использовать вариант 3.

public static IEnumerable<T> ParseInput<T>(string input, IObjectResolver resolver)
{
    var xml = XElement.Parse(input);
    // some more code here
    var parser = resolver.Resolve<IParser<T>>();
    return parser.Parse(xml);
}

Вариант 3 : передать уже разрешенный синтаксический анализатор (благодарность Марку Симанну за эту идею)

public static IEnumerable<T> ParseInput<T>(string input, IParser<T> parser)
{
    var xml = XElement.Parse(input);
    // some more code here
    return parser.Parse(xml);
}
0 голосов
/ 02 июня 2011

Похоже, консенсус заключается в том, что сначала нужно разрешить анализатор и передать его в статический метод. Предполагая, что вы уже знаете T, который вы анализируете, это кажется хорошим подходом. Это просто возлагает ответственность за получение экземпляра синтаксического анализатора на один уровень вверх до класса, который вызывает ParseInput.

Полагаю, это означает, что у вас будет такой код:

var orderParser = new OrderParser();

var orders = InputParser.ParseInput(input, orderParser);

Опять же, это предполагает, что вы знаете T, который вам нужен (Order в данном случае). Если вы анализируете вещи в общем, вы не будете знать T и, следовательно, не будете знать, что OrderParser - это класс, который вы должны использовать. В этом случае вы сталкиваетесь с той же проблемой: вам все еще нужно использовать оператор switch или контейнер, чтобы абстрагировать различные реализации IParser<T>.

Общий анализ

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

public class InputParser
{
    private readonly IParserFactory _parserFactory;

    public InputParser(IParserFactory parserFactory)
    {
        _parserFactory = parserFactory;
    }

    public IEnumerable<T> ParseInput<T>(string input)
    {
        var xml = XElement.Parse(input);

        // some more code here

        var parser = _parserFactory.CreateParser<T>();

        return parser.Parse(xml);
    }
}

Реализация IParserFactory может обрабатывать связь с контейнером:

public sealed class ResolvedParserFactory : IParserFactory
{
    private readonly IContainer _container;

    public ResolvedParserFactory(IContainer container)
    {
        _container = container;
    }

    public IParser<T> CreateParser<T>()
    {
        return _container.Resolve<IParser<T>>();
    }
}

Некоторые контейнеры, такие как Autofac , могут сгенерировать эту фабрику для вас или предоставить другие способы отделить класс фабрики от прямой ссылки на контейнер .

Неуниверсальный синтаксический анализ

Если код, который вызывает ParseInput, действительно знает, какой T нужно проанализировать, например, с OrderParser выше, вам не нужна сложность фабрики. В этом случае у меня есть несколько предложений по улучшению API.

ParseInput - отличный кандидат на метод расширения в IParser<T>. Вы также можете отделить синтаксический анализ XML от другого кода и T синтаксический анализ:

public static IEnumerable<T> ParseInput(this IParser<T> parser, XElement input)
{
    // some more code here

    return parser.Parse(input);
}

public static IEnumerable<T> ParseInput(this IParser<T> parser, string input)
{
    return parser.ParseInput(XElement.Parse(input));
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...