适合您的脑筋急转弯!
我正在开发一个模块化系统,这样模块A可能需要模块B,而模块B也需要模块A。但是,如果模块B被禁用,它将根本不执行该代码并且不执行任何操作/返回null
更多一些视角:
假设InvoiceBusinessLogic
在模块“ Core”中。我们也有一个带有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。
注意:
IServiceCollection
负责定义实现。IOrderBusinessLogic
定义实现将导致运行时问题。到目前为止,我有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)
,但稍后我将遍历某个程序集中的所有接口。
Impromptu的示例如下:
expando.Meth1 = Return<bool>.Arguments<int>(it => it > 5);
但是我当然希望它是动态的,所以如何动态插入返回类型和参数。
我确实知道接口的作用就像合同一样,应该遵循合同,我也知道这是一种反模式,但是对于结果系统,在达到这一点之前已经进行了广泛的研究和谈判。我们想要,我们认为这是最好的选择,只是缺少了一点:)。
IOrderBusinessLogic
中使用任何形式的InvoiceBusinessLogic
。 答案 0 :(得分:2)
第三种方法(使用ExpandoObject
)即使看起来很棘手,但出于以下原因,我还是鼓励您不要遵循这条路:
IOrderBusinessLogic
中添加了一个物业)我当然会拒绝第二种选择(伪实现,也称为Null-Object),是的,它需要编写一些样板代码,但是这会为您提供编译时保证 rutime不会发生任何意外情况!
所以我的建议是做这样的事情:
private static void SetupEcommerceLogic(IServiceCollection services, bool enabled)
{
if (enabled)
{
services.AddTransient<IOrderBusinessLogic, OrderBusinessLogic>();
}
else
{
services.AddTransient<IOrderBusinessLogic, EmptyOrderBusinessLogic>();
}
}
答案 1 :(得分: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
中,因此当有人使用它时,很明显,这属于到另一个模块,则应执行null检查。
对于那些感兴趣的人,以下是正确添加所有虚假实现的代码:
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);
}
}