如何在 运行 时间解析类型以避免多个 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
-else
或 switch
-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);
}
}
你可以使用 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-else
或 switch-case
。
要了解 RIP 的基础知识,您可以查看我的幻灯片 here。
我的代码可以根据请求类型进行网络服务调用。
为此,我有以下代码;
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
-else
或 switch
-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);
}
}
你可以使用 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-else
或 switch-case
。
要了解 RIP 的基础知识,您可以查看我的幻灯片 here。