I have my code which makes a webservice Call based on type of request.
To do that , I have following code;
public class Client
{
IRequest request;
public Client(string requestType)
{
request = new EnrolmentRequest();
if (requestType == "Enrol")
{
request.DoEnrolment();
}
else if (requestType == "ReEnrol")
{
request.DoReEnrolment();
}
else if (requestType == "DeleteEnrolment")
{
request.DeleteEnrolment();
}
else if (requestType == "UpdateEnrolment")
{
request.UpdateEnrolment();
}
}
}
So as per open close principle, I can subclass like:
Class EnrolmentRequest:IRequest
{
CallService();
}
Class ReEnrolmentRequest:IRequest
{
CallService();
}
Class UpdateEnrolmentRequest:IRequest
{
CallService();
}
Now my client class will look something like this:
public class Client
{
public Client(string requestType)
{
IRequest request;
if (requestType == "Enrol")
{
request = new EnrolmentRequest();
request.CallService();
}
else if (requestType == "ReEnrol")
{
request = new REnrolmentRequest();
request.CallService();
}
else if (requestType == "DeleteEnrolment")
{
request = new UpdateEnrolmentRequest();
request.CallService();
}
else if (requestType == "UpdateEnrolment")
{
request = new UpdateEnrolmentRequest();
request.CallService();
}
}
}
Now , I still have to use if and else , and will have to change my code if there are any new request type.
So, it's definitely, not closed to modification.
Am I missing any thing with respect to SOLID?
Can I use dependency injection, to resolve the types at Run time?
Use two if statements if both if statement conditions could be true at the same time. In this example, both conditions can be true. You can pass and do great at the same time. Use an if/else statement if the two conditions are mutually exclusive meaning if one condition is true the other condition must be false.
The need to write new code to handle new requirements is not going to disappear. The goal is to not have to change the old code when handling new requirements, and your class structure deals with it.
You can minimize the changes by replacing your chain of conditionals with some other mechanism of creating new instances. For example, you can build a dictionary, or use a dependency injection framework to associate a type with a string.
Here is an implementation without using DI framework:
private static readonly IDictionary<string,Func<IRequest>> ReqTypeMapper =
new Dictionary<string,Func<IRequest>> {
{"Enrol", () => new EnrolmentRequest() }
, {"ReEnrol", () => new ReEnrolmentRequest() }
, ...
};
Now the call will look like this:
Func<IRequest> maker;
if (!ReqTypeMapper.TryGetValue(requestType, out maker)) {
// Cannot find handler for type - exit
return;
}
maker().CallService();
You can't really remove the list of if
-else
or switch
-case
statements completely, unless you revert to using reflection. Somewhere in the system you will definately have some sort of dispatching (either using a hard-coded list or through reflection).
Your design however might benefit from a more message based approach, where the incomming requests are message, such as:
class DoEnrolment { /* request values */ }
class DoReenrolment { /* request values */ }
class DeleteEnrolment { /* request values */ }
class UpdateEnrolment { /* request values */ }
This allows you to create a single interface defenition for 'handlers' of such request:
interface IRequestHandler<TRequest> {
void Handle(TRequest request);
}
Your handlers will look as follows:
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) { ... }
}
Advantage of this is that applying cross-cutting concerns is a breeze, since it is very straightforward to define a generic decorator for IRequestHandler<T>
that implements something like logging.
This still brings us back to the dispatching of course. Dispatching can be extracted from the client, behind its own abstraction:
interface IRequestDispatcher {
void Dispatch<TRequest>(TRequest request);
}
This allows the client to simply send the request it requires:
// Client
this.dispatcher.Dispatch(new DoEnrolment { EnrolId = id });
An implementation of the request dispatcher 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);
}
}
If you use a DI Container however, you will be able to batch-register your request handlers with something as follows (depending on the library you use of course):
container.Register(typeof(IRequestHandler<>), assemblies);
And your dispatcher might look as follows:
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);
}
}
You can find more information about this type of design here and here.
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