How to enable type at runtime to avoid using multipe if else

I have code that makes a Call web service based on a request type.

For this, I have the 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(); } } } 

Thus, by the principle of open closure, I can subclass, for example:

 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 need to use if and else, and I will have to change my code if there is a new request type.

So, it is definitely not closed for modification.

Am I missing any thing regarding SOLID?

Can I use dependency injection to resolve types at runtime?

+5
source share
6 answers

You can add a simple factory class as shown below:

 public class ServiceFactory : Dictionary<string, Type> { public void Register(string typeName, Type serviceType) { if (this.ContainsKey(typeName)) { throw new Exception("Type registered"); } this[typeName] = serviceType; } public IRequest Resolve(string typeName) { if (!this.ContainsKey(typeName)) { throw new Exception("Type not registered"); } var type = this[typeName]; var service = Activator.CreateInstance(type); return service as IRequest; } } 

then register the services in one place, for example:

  var serviceFactory = new ServiceFactory(); serviceFactory.Register("Enrol", typeof(EnrolmentRequest)); serviceFactory.Register("ReEnrol", typeof(REnrolmentRequest)); serviceFactory.Register("DeleteEnrolment", typeof(UpdateEnrolmentRequest)); serviceFactory.Register("UpdateEnrolment", typeof(UpdateEnrolmentRequest)); 

and name it:

 var service = serviceFactory.Resolve(requestType); service.CallService(); 

also need to add proper error handling

+4
source

The need to write new code to handle new requirements will not disappear. The goal is not to change the old code when processing new requirements, and your class structure deals with it.

You can minimize changes by replacing the legend chain with another mechanism for creating new instances. For example, you can create a dictionary or use the dependency injection infrastructure to associate a type with a string.

Here is an implementation without using the 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(); 
+8
source

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 { /* request values */ } class DoReenrolment { /* request values */ } class DeleteEnrolment { /* request values */ } class UpdateEnrolment { /* request values */ } 

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 .

+5
source

Good question, you can achieve your goal using one method:

 var request = (IRequest)Activator.CreateInstance("NameOfYourAssembly", requestType); request.CallService(); 

Reflection helps you instantiate the class. After that, you can call it without if / else.

For more information about the provided method, refer to this link: https://msdn.microsoft.com/it-it/library/3k6dfxfk(v=vs.110).aspx

Hope this helps

+4
source

You can use the Factory pattern with RIP (replace with polymorphism) to avoid a few if-else .

The following code is an example of code according to your Client class:

 public enum RequestType : int { Enrol = 1, ReEnrol, UpdateEnrolment } public interface IRequest { void CallService(); } public class EnrolmentRequest : IRequest { public void CallService() { // Code for EnrolmentRequest } } public class ReEnrolmentRequest : IRequest { public void CallService() { // Code for ReEnrolmentRequest } } public class UpdateEnrolmentRequest : IRequest { public void CallService() { // Code for UpdateEnrolmentRequest } } // Factory Class public class FactoryChoice { private IDictionary<RequestType, IRequest> _choices; public FactoryChoice() { _choices = new Dictionary<RequestType, IRequest> { {RequestType.Enrol, new EnrolmentRequest() }, {RequestType.ReEnrol, new ReEnrolmentRequest()}, {RequestType.UpdateEnrolment, new UpdateEnrolmentRequest()} }; } static public IRequest getChoiceObj(RequestType choice) { var factory = new FactoryChoice(); return factory._choices[choice]; } } 

and it will look like this:

 IRequest objInvoice = FactoryChoice.getChoiceObj(RequestType.ReEnrol); objInvoice.CallService(); 

Here the main things happened in the constructor of the FactoryChoice class. That's why someone called him a smart designer. This way you can avoid multilpe if-else or switch-case .

To find out the basic RIPs , you can check out my slide here .

+3
source

you can use autofac key or named service ..

 public enum OperationType { Enrol, ReEnrol, DeleteEnrolment, UpdateEnrolment } //register types builder.RegisterType<EnrolmentRequest>().Keyed<IRequest>(OperationType.Enrol); builder.RegisterType<ReEnrolmentRequest>().Keyed<IRequest>(OperationType.ReEnrol); builder.RegisterType<UpdateEnrolmentRequest>().Keyed<IRequest>(OperationType.DeleteEnrolment | OperationType.UpdateEnrolment); // resolve by operationType enum var request = container.ResolveKeyed<IRequest>(OperationType.Enrol); 
0
source

Source: https://habr.com/ru/post/1264242/


All Articles