如何在 运行 时间解析类型以避免多个 if else

How to resolve type at run time to avoid multipe if else

我的代码可以根据请求类型进行网络服务调用。

为此,我有以下代码;

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像:

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

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

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,我是否遗漏了什么?

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

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

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

反射将帮助您生成 class 实例。之后你可以在没有 if/else 的情况下调用它。

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

希望对您有所帮助

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

您可以将条件链替换为其他一些创建新实例的机制,从而最大限度地减少更改。例如,你可以构建一个字典,或者使用依赖注入框架将一个类型与一个字符串相关联。

下面是一个不使用 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();

您可以像下面这样添加简单工厂 class:

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

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

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

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

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

这允许您为 'handlers' 此类请求创建单个接口定义:

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

这样做的好处是应用 cross-cutting 关注点是一件轻而易举的事,因为为 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 容器,您将能够 batch-register 您的请求处理程序,如下所示(当然取决于您使用的库):

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

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

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

您可以使用 Factory Pattern With RIP(用多态替换 If) 来避免多个 if-else.

以下代码是根据您的 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];
    }
}

它会这样调用:

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

在这里,主要的事情发生在 FactoryChoice class 构造函数中。这就是为什么有人称它为智能构造函数。这样你就可以避免 multilpe if-elseswitch-case

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