繁体   English   中英

使用ExpandoObject创建接口的“伪”实现-动态添加方法

[英]Use ExpandoObject to create a 'fake' implementation of an interface - adding methods dynamically

给您的脑筋急转弯!

我正在开发一种模块化系统,以这种方式模块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执行某些OrderBusinessLogic 如果没有,它什么也不做。 在此示例中,它什么也不返回,因此它什么也做不了,在其他示例中,如果返回某些内容,则返回null。

笔记:

  • 如您所知,我使用的是Dependency Injection,它是一个ASP.NET Core应用程序,因此IServiceCollection负责定义实现。
  • 从逻辑上说,简单地不为IOrderBusinessLogic定义实现将导致运行时问题。
  • 经过大量研究,我不想在应用程序的域/逻辑内调用容器。 不要叫DI容器,它会叫你
  • 模块之间的这种交互作用被最小化,最好在控制器内完成,但是有时您无法绕开它(而且在控制器中,我还需要一种注入或不使用它们的方法)。

到目前为止,我想到了3种选择:

  1. 我从不从模块“ Core”调用模块“ Ecommerce”,从理论上讲,这听起来是最好的方法,但实际上,对于高级方案而言,这更为复杂。 别无选择
  2. 根据配置决定要实施哪个配置,我可以创建很多假的实现。 但这当然会导致重复代码,并且在引入新方法时,我将不得不不断更新伪类。 所以不完美。
  3. 我可以通过使用Reflection和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接口 ,我能够成功创建伪造的实现。 但是我现在需要解决的是,动态对象还包含所有方法(大多数情况下不需要属性),但是这些方法很容易添加。 因此,当前,我能够运行代码并进行启动,直到将其调用OrderBusinessLogic ,然后,从逻辑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) ,但是稍后我将遍历某个程序typeof(IOrderBusinessLogic)所有接口。

即兴的例子如下: expando.Meth1 = Return<bool>.Arguments<int>(it => it > 5);

但是,当然,我希望它是动态的,因此如何动态插入返回类型和参数。

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

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

即使采用第3种方法(使用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>();
    }
}

只要我要的解决方案没有其他答案,我就会想到以下扩展名:

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

    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM