How to resolve type at run time to avoid multipe i

2019-04-28 13:28发布

问题:

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?

回答1:

You can add simple factory class like 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 services in one place like:

 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 call it:

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

also need to add proper error handling



回答2:

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();


回答3:

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.



回答4:

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

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

Reflection will help you generating your class instance. After that you can call it without if/else.

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

Hope this can help



回答5:

You can use Factory Pattern With RIP (Replace If with Polymorphism) to avoid multiple if-else.

Following code is the sample 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 be call like :

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

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

To know the basic of RIP you can check my slide here.



回答6:

you can use autofac keyed 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);