简体   繁体   English

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

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

A brainteaser for you! 给您的脑筋急转弯!

I am developing a modular system, in such a way that module A could need module B and module B could also need module A. But if module B is disabled, it will simply not execute that code and do nothing / return null. 我正在开发一种模块化系统,以这种方式模块A可能需要模块B,而模块B也需要模块A。但是,如果模块B被禁用,它将根本不执行该代码并且不执行任何操作/返回null。

A little bit more into perspective: 多一点透视:

Let's say InvoiceBusinessLogic is within module "Core". 假设InvoiceBusinessLogic在模块“核心”中。 We also have a "Ecommerce" module which has a OrderBusinessLogic . 我们还有一个带有OrderBusinessLogic的“电子商务”模块。 The InvoiceBusinessLogic could then look like this: InvoiceBusinessLogic可能如下所示:

public class InvoiceBusinessLogic : IInvoiceBusinessLogic
{
    private readonly IOrderBusinessLogic _orderBusinessLogic;

    public InvoiceBusinessLogic(IOrderBusinessLogic orderBusinessLogic)
    {
        _orderBusinessLogic = orderBusinessLogic;
    }

    public void UpdateInvoicePaymentStatus(InvoiceModel invoice)
    {
        _orderBusinessLogic.UpdateOrderStatus(invoice.OrderId);
    }
}

So what I want is: When the module "Ecommerce" is enabled, it would actually do something at the OrderBusinessLogic . 所以我想要的是:启用模块“电子商务”后,它将实际上在OrderBusinessLogic执行某些OrderBusinessLogic When not, it would simply not do anything. 如果没有,它什么也不做。 In this example it returns nothing so it can simply do nothing, in other examples where something would be returned, it would return null. 在此示例中,它什么也不返回,因此它什么也做不了,在其他示例中,如果返回某些内容,则返回null。

Notes: 笔记:

  • As you can probably tell, I am using Dependency Injection, it is a ASP.NET Core application so the IServiceCollection takes care of defining the implementations. 如您所知,我使用的是Dependency Injection,它是一个ASP.NET Core应用程序,因此IServiceCollection负责定义实现。
  • Simply not defining the implementation for IOrderBusinessLogic will cause a runtime issue, logically. 从逻辑上说,简单地不为IOrderBusinessLogic定义实现将导致运行时问题。
  • From a lot of research done, I do not want to make calls to the container within my domain / logic of the app. 经过大量研究,我不想在应用程序的域/逻辑内调用容器。 Don't call the DI Container, it'll call you 不要叫DI容器,它会叫你
  • These kind of interactions between modules are kept to a minimum, preferably done within the controller, but sometimes you cannot get around it (and also in the controller I would then need a way to inject them and use them or not). 模块之间的这种交互作用被最小化,最好在控制器内完成,但是有时您无法绕开它(而且在控制器中,我还需要一种注入或不使用它们的方法)。

So there are 3 options that I figured out so far: 到目前为止,我想到了3种选择:

  1. I never make calls from module "Core" to module "Ecommerce", in theory this sounds the best way, but in practice it's more complicated for advanced scenarios. 我从不从模块“ Core”调用模块“ Ecommerce”,从理论上讲,这听起来是最好的方法,但实际上,对于高级方案而言,这更为复杂。 Not an option 别无选择
  2. I could create a lot of fake implementations, depending on the configuration decide on which one to implement. 根据配置决定要实施哪个配置,我可以创建很多假的实现。 But that would of course result in double code and I would constantly have to update the fake class when a new method is introduced. 但这当然会导致重复代码,并且在引入新方法时,我将不得不不断更新伪类。 So not perfectly. 所以不完美。
  3. I can build up a fake implementation by using reflection and ExpandoObject , and just do nothing or return null when the particular method is called. 我可以通过使用Reflection和ExpandoObject构建一个虚假的实现,并且什么都不做或在调用特定方法时返回null。

And the last option is what I am now after: 最后一个选择就是我现在所追求的:

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

By using Impromptu Interface , I am able to successfully create a fake implementation. 通过使用Impromptu接口 ,我能够成功创建伪造的实现。 But what I now need to solve is that the dynamic object also contains all the methods (mostly properties not needed), but those ones are easy to add. 但是我现在需要解决的是,动态对象还包含所有方法(大多数情况下不需要属性),但是这些方法很容易添加。 So currently I am able to run the code and get up until the point it will call the OrderBusinessLogic , then it will, logically, throw an exception that the method does not exist. 因此,当前,我能够运行代码并进行启动,直到将其调用OrderBusinessLogic ,然后,从逻辑OrderBusinessLogic ,它将引发该方法不存在的异常。

By using reflection, I can iterate over all the methods within the interface, but how do I add them to the dynamic object? 通过使用反射,我可以遍历接口内的所有方法,但是如何将它们添加到动态对象中呢?

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
}

Note: For now directly calling typeof(IOrderBusinessLogic) , but later I would iterate over all the interfaces within a certain assembly. 注意:现在,直接调用typeof(IOrderBusinessLogic) ,但是稍后我将遍历某个程序typeof(IOrderBusinessLogic)所有接口。

Impromptu has an example as follows: expando.Meth1 = Return<bool>.Arguments<int>(it => it > 5); 即兴的例子如下: expando.Meth1 = Return<bool>.Arguments<int>(it => it > 5);

But of course I want this to be dynamic so how do I dynamically insert the return type and the parameters. 但是,当然,我希望它是动态的,因此如何动态插入返回类型和参数。

I do understand that a interface acts like a contract, and that contract should be followed, I also understand that this is an anti-pattern, but extensive research and negotiations have been done prior to reaching this point, for the result system we want, we think this is the best option, just a little missing piece :). 我确实知道接口的作用类似于合同,应该遵循合同,我也理解这是一种反模式,但是在达到这一点之前,已经进行了广泛的研究和协商,对于我们想要的结果系统,我们认为这是最好的选择,只是缺少了一点:)。

  • I have looked at this question , I am not really planning on leaving .dll's out, because most likely I would not be able to have any form of IOrderBusinessLogic usable within InvoiceBusinessLogic . 我已经看过这个问题 ,我并不是真的没有打算将.dll排除在外,因为很可能我将无法在IOrderBusinessLogic使用任何形式的InvoiceBusinessLogic
  • I have looked at this question , but I did not really understand how TypeBuilder could be used in my scenario 我已经看过这个问题 ,但我并没有真正理解如何在我的方案中使用TypeBuilder
  • I have also looked into Mocking the interfaces, but mostly you would then need to define the 'mocking implementation' for each method that you want to change, correct me if I am wrong. 我也研究过模拟接口,但是大多数情况下,您需要为每个要更改的方法定义“模拟实现”,如果我错了,请更正我。

Even tough the third approach (with ExpandoObject ) looks like a holy grail, I foster you to not follow this path for the following reasons: 即使采用第3种方法(使用ExpandoObject )也很棘手,但出于以下原因,我鼓励您不要遵循这条路:

  • What guarantees you that this fancy logic will be error-free now and at every time in the future ? 是什么保证您现在和将来的每个时间都不会出错呢? (think: in 1 year you add a property in IOrderBusinessLogic ) (想想:您在1年内在IOrderBusinessLogic添加了一个属性)
  • What are the consequences if not ? 如果没有,后果是什么? Maybe an unexpected message will pop to the user or cause some strange "a priori unrelated" behavior 也许意外的消息会弹出给用户或导致一些奇怪的“先验无关”行为

I would definitely go down the second option (fake implementation, also called Null-Object) even though, yes it will require to write some boilerplate code but ey this would offer you a compile-time guarantee that nothing unexpected will happen at rutime ! 我肯定会拒绝第二种选择(伪实现,也称为Null-Object),是的,它需要编写一些样板代码,但是这将为您提供编译时保证 ,在rutime不会发生任何意外!

So my advice would be to do something like this: 所以我的建议是做这样的事情:

private static void SetupEcommerceLogic(IServiceCollection services, bool enabled)
{
    if (enabled)
    {
        services.AddTransient<IOrderBusinessLogic, OrderBusinessLogic>();
    }
    else
    {
        services.AddTransient<IOrderBusinessLogic, EmptyOrderBusinessLogic>();
    }
}

For as long as there is no other answer for the solution I am looking for, I came up with the following extension: 只要我要的解决方案没有其他答案,我就会想到以下扩展名:

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

And then use it like: 然后像这样使用它:

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

And actually it has the advantage that it is clear (in the code) that the return type can be null or the method won't be called when the module is disabled. 实际上,它的优点是(在代码中)很清楚,返回类型可以为null或在禁用模块时不会调用该方法。 The only thing that should be documented carefully, or in another way enforced, that is has to be clear which classes do not belong to the current module. 应该仔细记录或以另一种方式实施的唯一记录是,必须清楚哪些类不属于当前模块。 The only thing I could think of right now is by not including the using automatically, but use the full namespace or add summaries to the included _orderBusinessLogic , so when someone is using it, it is clear this belongs to another module, and a null check should be performed. 我现在唯一想到的就是不自动包含using ,而是使用完整的名称空间或将摘要添加到所包含的_orderBusinessLogic ,因此当有人使用它时,很明显这属于另一个模块,并且为空检查应该执行。

For those that are interested, here is the code to correctly add all fake implementations: 对于那些感兴趣的人,以下是正确添加所有虚假实现的代码:

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