如何在运行时解析类型以避免multipe if else

时间:2017-02-15 11:03:29

标签: c# .net dependency-injection inversion-of-control solid-principles

我的代码根据请求类型进行Web服务调用。

为此,我有以下代码;

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

}

因此,根据开放式原则,我可以像子类一样进行子类化:

Class EnrolmentRequest:IRequest
{
    CallService();
}
Class ReEnrolmentRequest:IRequest
{
    CallService();
}
Class UpdateEnrolmentRequest:IRequest
{
    CallService();
}

现在我的客户端类看起来像这样:

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

}

现在,我仍然需要使用if和else,如果有任何新的请求类型,则必须更改我的代码。

所以,它绝对不会被修改。

我是否遗漏了与SOLID相关的任何内容?

我可以使用依赖注入来解析运行时的类型吗?

6 个答案:

答案 0 :(得分:8)

编写新代码以处理新需求的需求不会消失。目标是在处理新需求时不必更改旧代码,并且您的类结构处理它。

您可以通过使用其他创建新实例的其他机制替换条件链来最小化更改。例如,您可以构建字典,或使用依赖注入框架将类型与字符串相关联。

这是一个不使用DI框架的实现:

private static readonly IDictionary<string,Func<IRequest>> ReqTypeMapper =
    new Dictionary<string,Func<IRequest>> {
        {"Enrol", () => new EnrolmentRequest() }
    ,   {"ReEnrol", () => new ReEnrolmentRequest() }
    ,   ...
    };

现在,通话将如下所示:

Func<IRequest> maker;
if (!ReqTypeMapper.TryGetValue(requestType, out maker)) {
    // Cannot find handler for type - exit
    return;
}
maker().CallService();

答案 1 :(得分:5)

除非您恢复使用反射,否则您无法完全删除if - elseswitch - case语句列表。在系统的某个地方,您肯定会进行某种调度(使用硬编码列表或通过反射)。

然而,您的设计可能会受益于更多基于消息的方法,其中接收请求是消息,例如:

class DoEnrolment { /* request values */ }
class DoReenrolment { /* request values */ }
class DeleteEnrolment { /* request values */ }
class UpdateEnrolment { /* request values */ }

这允许您为“处理程序”创建单一界面防御。这样的要求:

interface IRequestHandler<TRequest> {
    void Handle(TRequest request);
}

您的处理程序如下所示:

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) { ... }
}

这样做的好处是,应用横切关注点是一件轻而易举的事,因为为IRequestHandler<T>定义一个通用装饰器非常简单,它可以实现类似日志记录的功能。

这仍然使我们回到当然的派遣。调度可以从客户端提取,在它自己的抽象背后:

interface IRequestDispatcher {
    void Dispatch<TRequest>(TRequest request);
}

这允许客户端简单地发送它所需的请求:

// Client
this.dispatcher.Dispatch(new DoEnrolment { EnrolId = id });

请求调度程序的实现可能如下所示:

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

但是,如果您使用DI容器,则可以使用以下内容批量注册请求处理程序(取决于您使用的库当然):

container.Register(typeof(IRequestHandler<>), assemblies);

您的调度员可能如下所示:

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

您可以找到有关此类设计的更多信息herehere

答案 2 :(得分:4)

好问题, 您可以使用一种方法实现目标:

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

反射将帮助您生成类实例。之后你可以不用if / else来调用它。

有关提供的方法的详细信息,请参阅此链接:https://msdn.microsoft.com/it-it/library/3k6dfxfk(v=vs.110).aspx

希望这可以提供帮助

答案 3 :(得分:4)

您可以添加如下的简单工厂类:

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;
    }
}

然后在一个地方注册服务,如:

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

并称之为:

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

还需要添加适当的错误处理

答案 4 :(得分:3)

您可以使用使用RIP的工厂模式(使用多态替换)以避免多个if-else

以下代码是您Client类的示例代码:

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];
    }
}

它将被称为:

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

这里,主要的事情发生在FactoryChoice类构造函数中。这就是有人称之为智能构造函数的原因。这样您就可以避免多重if-elseswitch-case

要了解 RIP 的基本知识,您可以查看我的幻灯片here

答案 5 :(得分:0)

您可以使用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);