I want to implement a CommandBus
that can Dispatch
some Commands
to CommandHandlers
.
Command
is a simple a DTO describing what should happen. For instance : "Increment counter by 5"CommandHandler
is able to handle a precise type of Command
.CommandBus
takes a Command
and executes the CommandHandler
that is able to handle it.The code I wrote does not compile.
Compiler complains cannot convert from 'IncrementHandler' to 'Handler<Command>'
.
I don't understand why, because IncrementHandler
implements Handler<Increment>
and Increment
implements Command
I've tried both in
and out
modifiers on the generic interfaces, it doesn't solve the problem.
Is there a way to achieve this with only interfaces ?
[TestClass]
public class CommandBusTest
{
[TestMethod]
public void DispatchesProperly()
{
var handler = new IncrementHandler(counter: 0);
var bus = new CommandBus(handler); // <--Doesn't compile: cannot convert from 'IncrementHandler' to 'Handler<Command>'
bus.Dispatch(new Increment(5));
Assert.AreEqual(5, handler.Counter);
}
}
public class CommandBus
{
private readonly Dictionary<Type, Handler<Command>> handlers;
public CommandBus(params Handler<Command>[] handlers)
{
this.handlers = handlers.ToDictionary(
h => h.HandledCommand,
h => h);
}
public void Dispatch(Command commande) { /*...*/ }
}
public interface Command { }
public interface Handler<TCommand> where TCommand : Command
{
Type HandledCommand { get; }
void Handle(TCommand command);
}
public class Increment : Command
{
public Increment(int value) { Value = value; }
public int Value { get; }
}
public class IncrementHandler : Handler<Increment>
{
// Handler<Increment>
public Type HandledCommand => typeof(Increment);
public void Handle(Increment command)
{
Counter += command.Value;
}
// Handler<Increment>
public int Counter { get; private set; }
public IncrementHandler(int counter)
{
Counter = counter;
}
}
I don't understand why, because
IncrementHandler
implementsHandler<Increment>
andIncrement
implementsCommand
Let's fix your misunderstanding, and then the rest will become clear.
Suppose what you wanted to do was legal. What goes wrong?
IncrementHandler ih = whatever;
Handler<Command> h = ih; // This is illegal. Suppose it is legal.
now we make a class
public class Decrement : Command { ... }
And now we pass it to h:
Decrement d = new Decrement();
h.Handle(d);
This is legal, because Handler<Command>.Handle
takes a Command
, and a Decrement
is a Command
.
So what happened? You just passed a decrement command to ih
, via h
, but ih
is an IncrementHandler
that only knows how to handle increments.
Since that is nonsensical, something in here has to be illegal; which line would you like to be illegal? The C# team decided that the conversion is the thing that should be illegal.
More specifically:
Your program is using reflection in an attempted end-run around the type system's safety checks, and then you are complaining that the type system is stopping you when you write something unsafe. Why are you using generics at all?
Generics are (in part) to ensure type safety, and then you are doing a dispatch based on reflection. This doesn't make any sense; don't take steps to increase type safety and then do heroic efforts to work around them.
Plainly you wish to work around type safety, so don't use generics at all. Just make an ICommand
interface and a Handler
class that takes a command, and then have some mechanism for working out how to dispatch commands.
What I don't understand though is why there are two kinds of things at all. If you want to execute a command, then why not simply put the execution logic on the command object?
There are also other design patterns you could use here other than this clunky dictionary lookup based on types. For example:
a command handler could have a method that takes a command and returns a boolean, whether the handler can handle this command or not. Now you have a list of command handlers, a command comes in, and you just run down the list asking "are you my handler?" until you find one. If O(n) lookup is too slow, then build a MRU cache or memoize the result or some such thing, and the amortized behaviour will improve.
the dispatch logic could be put into the command handler itself. A command handler is given a command; it either executes it, or it recurses, calling its parent command handler. You can thus build a graph of command handlers that defer work to each other as necessary. (This is basically how QueryService works in COM.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With