You cannot completely remove the list of if - else or switch - case unless you return to using reflection. Somewhere in the system, you will definitely have some kind of dispatch (either using a hard-coded list, or through reflection).
However, your design can benefit from a more message-based approach when incoming requests are a message, for example:
class DoEnrolment { } class DoReenrolment { } class DeleteEnrolment { } class UpdateEnrolment { }
This allows you to create a single interface restriction for the "handlers" of such a request:
interface IRequestHandler<TRequest> { void Handle(TRequest request); }
Your handlers will look like this:
class DoEnrolmentHandler : IRequestHandler<DoEnrolment> { public void Handle(DoEnrolment request) { ... } } class DoReenrolmentHandler : IRequestHandler<DoReenrolment> { public void Handle(DoReenrolment request) { ... } } class DeleteEnrolmentHandler : IRequestHandler<DeleteEnrolment> { public void Handle(DeleteEnrolment request) { ... } }
The advantage of this is that applying cross-cutting issues is a breeze, as it is very easy to define a general decorator for IRequestHandler<T> that implements something like logging.
This still brings us back to shipping, of course. A dispatcher can be extracted from a client for its own abstraction:
interface IRequestDispatcher { void Dispatch<TRequest>(TRequest request); }
This allows the client to simply send the requested request:
// Client this.dispatcher.Dispatch(new DoEnrolment { EnrolId = id });
The query manager implementation might look like this:
class ManualRequestDispatcher : IRequestDispatcher { public void Dispatch<TRequest>(TRequest request) { var handler = (IRequestHandler<TRequest>)CreateHandler(typeof(TRequest)); handler.Handle(request); } object CreateHandler(Type type) => type == typeof(DoEnrolment)? new DoEnrolmentHandler() : type == typeof(DoReenrolment) ? new DoReenrolment() : type == typeof(DeleteEnrolment) ? new DeleteEnrolment() : type == typeof(UpdateEnrolment) ? new UpdateEnrolment() : ThrowRequestUnknown(type); object ThrowRequestUnknown(Type type) { throw new InvalidOperationException("Unknown request " + type.Name); } }
However, if you use the DI container, you can register request handlers in the following order (depending on the library you use):
container.Register(typeof(IRequestHandler<>), assemblies);
And your dispatcher might look like this:
class ContainerRequestDispatcher : IRequestDispatcher { private readonly Container container; public ContainerRequestDispatcher(Container container) { this.container = container; } public void Dispatch<TRequest>(TRequest request) { var handler = container.GetInstance<IRequestHandler<TRequest>>(); handler.Handle(request); } }
Further information on this type of design can be found here and here .