Resolving generic interface from generic static method - is using container most simple?
I have a code similar to this:
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);
}
It contains some common processing of the data followed by a call to parser interface which is very different between the specializations. Currently, I am resolving the parser interface from a container - but I'm a bit uncomfortable with using a container from a static method.
Is there a better or alternative way to resolve the interface, besides this and the switch statement?
Edit: In my opinion, the shagginess of this comes from an attempt to marry unit-testable OO programming (IoC, no static classes) to functional programming. Where is the cleanest line of separation? Probably resolving the parser earlier and passing it into the static method.
Edit: I kinda missed what you meant. I quite like using a container for things like that. Switch statements are a bit ugly. So going with a container, some ways you could make it more testable are:
If some code here
is complicated, I tend to make sure it is not static, as its not as easy to unit test static methods when using dependency injection. If its not complicated, your code is fine to me :o)
Option 1: Don't use a static method.
Instead use an instance method and inject the parser into it.
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);
}
}
Option 2: Pass in the container resolver along with the string input. Although if doing this, option 3 is better.
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);
}
Option 3: Pass in the already resolved parser (credit to Mark Seemann for this idea)
public static IEnumerable<T> ParseInput<T>(string input, IParser<T> parser)
{
var xml = XElement.Parse(input);
// some more code here
return parser.Parse(xml);
}
The consensus seems to be that you should resolve the parser first and pass it into the static method. Assuming you already know the T
you are parsing, that seems like a fine approach. It simply pushes the responsibility for obtaining the parser instance up one level, into the class which calls ParseInput
.
I assume this means you will have code like this:
var orderParser = new OrderParser();
var orders = InputParser.ParseInput(input, orderParser);
Again, this assumes that you know the T
you need (Order
in this case). If you are parsing things generically, you won't know T
, and thus won't know that OrderParser
is the class you should use. In this case, you face the same problem: you still need to use either a switch statement or a container to abstract away the various IParser<T>
implementations.
Generic Parsing
If this is your situation, you can combine an instance method with a parser factory to avoid referencing a container directly from the consuming class:
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);
}
}
The implementation of IParserFactory
can handle the communication with the container:
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>>();
}
}
Some containers, such as Autofac, may generate this factory for you or provide other ways to decouple the factory class from directly referencing a container.
Non-generic Parsing
If the code which calls ParseInput
really does know which T
to parse, such as with OrderParser
above, you don't need the complexity of a factory. In this case, I have some suggestions for improving the API.
ParseInput
is a great candidate for an extension method on IParser<T>
. You could also decouple the XML parsing from the other code and T
parsing:
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));
}
精彩评论