Reputation:
I can't find a good solution to solve a design issue i'm facing. Currently I'm designing a system supposed to parse query string.
Each parser must implement the interface IQueryParser:
public interface IQueryParser
{
Query Parse();
}
When I receive a HTTP request, I try to find a parser able to parse it using a factory. QueryContext is just a simpler version of HttpRequest
public interface IQueryParserFactory
{
void Register<TQueryParser>(Func<QueryContext, bool> predicate)
where TQueryParser : IQueryParser;
IQueryParser Create(QueryContext context);
}
public class QueryParserFactory : IQueryParserFactory
{
private readonly ConcurrentDictionary<Type, Func<QueryContext, bool>> _parsers = new ConcurrentDictionary<Type, Func<QueryContext, bool>>();
public void Register<TQueryParser>(Func<QueryContext, bool> predicate)
where TQueryParser : IQueryParser
=> _parsers.AddOrUpdate(typeof(TQueryParser), predicate, (key, oldValue) => predicate);
public IQueryParser Create(QueryContext context)
=> (from kpv in _parsers
where kpv.Value.Invoke(context)
select (IQueryParser)Activator.CreateInstance(kpv.Key, context)).FirstOrDefault();
}
Parsers may require extra dependencies and I don't know how to inject them into the parser. Let's say we have the following parser.. How can i know what concrete implementation of IOperatorParser do i need?
public class QueryParser : IQueryParser
{
private readonly QueryContext _context;
public QueryParser(QueryContext context, IOperatorParser parser)
{
_context = context;
}
public Query Parse()
{
(...)
}
}
I would need some kind of dependency injection. The problem is that I instantiate object myself and that I also pass the QueryContext at the runtime. Any hint/idea on how i can redesign my app to handle my use case?
Thank you, Sebastien
Upvotes: 0
Views: 44
Reputation: 27861
As noted in the comments, QueryContext context
is runtime data and shouldn't be injected into your objects.
You can either use the CanParse
solution you mentioned, or you can use this alternative:
public interface IQueryParser
{
Maybe<Query> Parse(QueryContext context);
}
The Maybe<T>
struct is used to support a return value of "no value" (this is more readable and honest that returning a null
Query
for "no value") . See more on this here: http://enterprisecraftsmanship.com/2015/03/13/functional-c-non-nullable-reference-types/
The implementer of this interface is expected to return a "no value" if it cannot parse.
Now, the consumer get's a collection of already built stateless parsers like this:
public class Consumer
{
private readonly IQueryParser[] parsers;
public Consumer(IQueryParser[] parsers)
{
//..
}
}
And each time it is invoked, it tries all parsers until it finds one that can parse the query:
var result =
parsers
.Select(x => x.Parse(context))
.FirstOrDefault(x => x.HasValue); //the default of the `Maybe` struct is a "no value"
if(result.HasValue) //some parse was able to parse
//...
else
//..
Upvotes: 1