【发布时间】:2019-04-05 08:27:35
【问题描述】:
给你的脑筋急转弯!
我正在开发一个模块化系统,这样模块 A 可能需要模块 B,模块 B 也可能需要模块 A。但是如果模块 B 被禁用,它将根本不执行该代码并且什么也不做/返回 null .
稍微深入一点:
假设InvoiceBusinessLogic 在模块“核心”中。我们还有一个“电子商务”模块,它有一个OrderBusinessLogic。 InvoiceBusinessLogic 可能看起来像这样:
public class InvoiceBusinessLogic : IInvoiceBusinessLogic
{
private readonly IOrderBusinessLogic _orderBusinessLogic;
public InvoiceBusinessLogic(IOrderBusinessLogic orderBusinessLogic)
{
_orderBusinessLogic = orderBusinessLogic;
}
public void UpdateInvoicePaymentStatus(InvoiceModel invoice)
{
_orderBusinessLogic.UpdateOrderStatus(invoice.OrderId);
}
}
所以我想要的是:启用“电子商务”模块后,它实际上会在OrderBusinessLogic 处执行某些操作。如果没有,它根本不会做任何事情。在这个例子中,它什么也不返回,所以它可以简单地什么也不做,在其他将返回一些东西的例子中,它会返回 null。
注意事项:
- 您可能知道,我使用的是依赖注入,它是一个 ASP.NET Core 应用程序,因此
IServiceCollection负责定义实现。 - 从逻辑上讲,不定义
IOrderBusinessLogic的实现会导致运行时问题。 - 根据已完成的大量研究,我不想在我的域/应用程序逻辑中调用容器。 Don't call the DI Container, it'll call you
- 模块之间的这种交互保持在最低限度,最好在控制器内完成,但有时你无法绕过它(而且在控制器中,我需要一种方法来注入它们并使用它们)。
到目前为止,我想到了 3 个选项:
- 我从不从模块“Core”调用模块“Ecommerce”,理论上这听起来是最好的方法,但实际上对于高级场景它更复杂。不是一种选择
- 我可以创建很多假实现,具体取决于配置决定要实现哪一个。但这当然会导致双重代码,当引入新方法时,我必须不断更新假类。所以并不完美。
- 我可以使用反射和
ExpandoObject构建一个假实现,并且在调用特定方法时什么也不做或返回 null。
最后一个选项就是我现在所追求的:
private static void SetupEcommerceLogic(IServiceCollection services, bool enabled)
{
if (enabled)
{
services.AddTransient<IOrderBusinessLogic, OrderBusinessLogic>();
return;
}
dynamic expendo = new ExpandoObject();
IOrderBusinessLogic fakeBusinessLogic = Impromptu.ActLike(expendo);
services.AddTransient<IOrderBusinessLogic>(x => fakeBusinessLogic);
}
通过使用Impromptu Interface,我能够成功创建一个假实现。但是我现在需要解决的是动态对象还包含所有的方法(大部分是不需要的属性),但是那些很容易添加。所以目前我能够运行代码并起床,直到它会调用OrderBusinessLogic,然后从逻辑上讲,它会抛出该方法不存在的异常。
通过使用反射,我可以遍历接口中的所有方法,但是如何将它们添加到动态对象中呢?
dynamic expendo = new ExpandoObject();
var dictionary = (IDictionary<string, object>)expendo;
var methods = typeof(IOrderBusinessLogic).GetMethods(BindingFlags.Public);
foreach (MethodInfo method in methods)
{
var parameters = method.GetParameters();
//insert magic here
}
注意:现在直接调用typeof(IOrderBusinessLogic),但以后我会遍历某个程序集中的所有接口。
即兴有一个例子如下:
expando.Meth1 = Return<bool>.Arguments<int>(it => it > 5);
但我当然希望这是动态的,那么如何动态插入返回类型和参数。
我确实理解接口的行为类似于契约,并且应该遵循契约,我也理解这是一种反模式,但在达到这一点之前已经针对结果系统进行了广泛的研究和谈判我们想要,我们认为这是最好的选择,只是缺少一点:)。
- 我查看了this question,我真的不打算将.dll 排除在外,因为我很可能无法在
InvoiceBusinessLogic中使用任何形式的IOrderBusinessLogic。 - 我看过this question,但我并不真正了解如何在我的场景中使用 TypeBuilder
- 我还研究了模拟接口,但大多数情况下,您需要为要更改的每个方法定义“模拟实现”,如果我错了,请纠正我。
【问题讨论】:
-
再想一想。我可以说这是选项 4:在业务逻辑层之上的层中管理模块存在,即如果电子商务模块未启用,则不要调用
UpdateInvoicePaymentStatus。它是适合您的解决方案吗? -
实际上,如果适用的话,这个选项甚至可能是比任何其他选项更好的选项(=不将此功能添加到现有的大型遗留代码库中)。尽可能高(在抽象层中)处理这种关注,这意味着至少在业务逻辑之上,对我来说很有意义。
标签: c# dependency-injection modularity impromptu-interface