I'm trying to implement generic queries and handlers so that I can make requests with simple syntax, like this:
var query = new HelloQuery("hi");
var result = processor.Process(query);
This post explains a brilliant technique for accomplishing this, but it uses reflection and dynamic
to resolve handlers for queries. I came up with an alternative using generics.
The central idea is the IQuery:
public interface IQuery<TSelf, TResult>
where TSelf : IQuery<TSelf, TResult>
{
}
One such implementation could be:
public class HelloQuery : IQuery<HelloQuery, string>
{
public HelloQuery(string message)
{
this.message = message;
}
private readonly string message;
public string Message
{
get { return this.message; }
}
}
The TSelf type argument is the key difference between my technique and the other post's technique. It allows us to avoid reflection in the HandlerResolver:
public interface IHandlerResolver
{
IQueryHandler<TQuery, TResponse> GetHandlerFor<TQuery, TResult>()
where TQuery: IQuery<TQuery, TResult>;
}
public class HandlerResolver : IHandlerResolver
{
private readonly IContainer container;
public HandlerResolver(IContainer container)
{
this.container = container;
}
public IQueryHandler<TQuery, TResult> GetHandlerFor<TQuery, TResult>()
where TQuery : IQuery<TQuery, TResult>
{
var handler = this.container.Get<IQueryHandler<TQuery, TResult>>();
return handler;
}
}
You would register handlers at the composition root. Here's the interface for a handler, and an implementation for HelloQuery:
public interface IQueryHandler<TQuery, TResult>
where TQuery : IQuery<TQuery, TResult>
{
TResult Handle(TQuery request);
}
public class HelloQueryHandler : IQueryHandler<HelloQuery, string>
{
public string Handle(HelloQuery request)
{
var response = string.Format("Well \"{0}\" yourself!", request.Message);
return response;
}
}
And finally, the actual query processor:
public interface IQueryProcessor
{
TResult Process<TQuery, TResult>(IQuery<TQuery, TResult> request)
where TQuery : IQuery<TQuery, TResult>;
}
public class QueryProcessor : IQueryProcessor
{
private readonly IHandlerResolver handlerResolver;
public QueryProcessor(IHandlerResolver handlerResolver)
{
this.handlerResolver = handlerResolver;
}
public TResult Process<TQuery, TResult>(IQuery<TQuery, TResult> request)
where TQuery : IQuery<TQuery, TResult>
{
var handler = this.handlerResolver.GetHandlerFor<TQuery, TResult>();
var response = handler.Handle((TQuery)request);
return response;
}
}
The interfaces would be simpler without the queries' TSelf type argument, but then the C# compiler can't infer the types, and you end up having to spell out the full Process() call like processor.Process<HelloQuery, string>(query)
. I'm trying to avoid this ugliness.
My question is: Is the TSelf type argument a code smell? Is there a better alternative that doesn't require falling back on reflection?
Aucun commentaire:
Enregistrer un commentaire