1

给你的脑筋急转弯!

我正在开发一个模块化系统,这样模块 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将导致运行时问题。
  • 从完成的大量研究中,我不想在我的应用程序的域/逻辑中调用容器。不要调用 DI 容器,它会调用你
  • 模块之间的这种交互保持在最低限度,最好在控制器内完成,但有时你无法绕过它(而且在控制器中,我需要一种方法来注入它们并使用它们)。

所以到目前为止我想出了3个选项:

  1. 我从不从模块“Core”调用模块“Ecommerce”,理论上这听起来是最好的方法,但实际上对于高级场景来说它更复杂。不是一个选项
  2. 我可以创建很多虚假的实现,具体取决于配置决定要实现哪一个。但这当然会导致双重代码,当引入新方法时,我必须不断更新假类。所以并不完美。
  3. 我可以通过使用反射和构建一个假实现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);

但是我当然希望这是动态的,所以我如何动态插入返回类型和参数。

我确实理解接口的行为类似于合同,并且应该遵循合同,我也理解这是一种反模式,但是在达到这一点之前已经进行了广泛的研究和谈判,对于我们想要的结果系统,我们认为这是最好的选择,只是缺少一点 :)。

  • 我看过这个问题,我真的不打算将 .dll 排除在外,因为很可能我无法在 .dll 中IOrderBusinessLogic使用任何形式的文件InvoiceBusinessLogic
  • 我看过这个问题,但我并不真正理解如何在我的场景中使用 TypeBuilder
  • 我还研究了模拟接口,但大多数情况下,您需要为要更改的每个方法定义“模拟实现”,如果我错了,请纠正我。
4

2 回答 2

2

即使是艰难的第三种方法(带有ExpandoObject)看起来像一个圣杯,我鼓励你不要走这条路,原因如下:

  • 是什么保证了这种奇特的逻辑在现在和未来的任何时候都不会出错?(想想:在 1 年内你添加一个属性IOrderBusinessLogic
  • 如果不这样做会有什么后果?也许一条意外消息会弹出给用户或导致一些奇怪的“先验无关”行为

我肯定会选择第二个选项(假实现,也称为 Null-Object),是的,它需要编写一些样板代码,但这将为您提供编译时保证,在 rutime 不会发生任何意外!

所以我的建议是做这样的事情:

private static void SetupEcommerceLogic(IServiceCollection services, bool enabled)
{
    if (enabled)
    {
        services.AddTransient<IOrderBusinessLogic, OrderBusinessLogic>();
    }
    else
    {
        services.AddTransient<IOrderBusinessLogic, EmptyOrderBusinessLogic>();
    }
}
于 2019-04-05T09:28:59.743 回答
0

只要我正在寻找的解决方案没有其他答案,我就想出了以下扩展:

using ImpromptuInterface.Build;
public static TInterface IsModuleEnabled<TInterface>(this TInterface obj) where TInterface : class
{
    if (obj is ActLikeProxy)
    {
        return default(TInterface);//returns null
    }
    return obj;
}

然后像这样使用它:

public void UpdateInvoicePaymentStatus(InvoiceModel invoice)
{
    _orderBusinessLogic.IsModuleEnabled()?.UpdateOrderStatus(invoice.OrderId);
   //just example stuff
   int? orderId = _orderBusinessLogic.IsModuleEnabled()?.GetOrderIdForInvoiceId(invoice.InvoiceId);
}

实际上它的优点是(在代码中)很清楚返回类型可以为 null 或者在禁用模块时不会调用该方法。唯一应该仔细记录或以其他方式强制执行的事情,就是必须清楚哪些类不属于当前模块。我现在唯一能想到的就是不using自动包含,而是使用完整的命名空间或向包含的添加摘要_orderBusinessLogic,所以当有人使用它时,很明显它属于另一个模块,并且应该进行空检查执行。

对于那些感兴趣的人,这里是正确添加所有假实现的代码:

private static void SetupEcommerceLogic(IServiceCollection services, bool enabled)
{
    if (enabled) 
    {
        services.AddTransient<IOrderBusinessLogic, OrderBusinessLogic>();
        return;
    }
    //just pick one interface in the correct assembly.
    var types = Assembly.GetAssembly(typeof(IOrderBusinessLogic)).GetExportedTypes();
    AddFakeImplementations(services, types);
}

using ImpromptuInterface;
private static void AddFakeImplementations(IServiceCollection services, Type[] types)
{
    //filtering on public interfaces and my folder structure / naming convention
    types = types.Where(x =>
        x.IsInterface && x.IsPublic &&
        (x.Namespace.Contains("BusinessLogic") || x.Namespace.Contains("Repositories"))).ToArray();
    foreach (Type type in types)
    {
        dynamic expendo = new ExpandoObject();
        var fakeImplementation = Impromptu.DynamicActLike(expendo, type);
        services.AddTransient(type, x => fakeImplementation);

    }
}
于 2019-04-05T10:48:02.057 回答