动态实例化类的“按需”依赖注入

时间:2018-05-09 15:16:01

标签: c# events reflection dependency-injection delegates

在我的应用程序中,我有一个名为EventTriggerActionService的类,它负责实例化驻留在不同程序集中的自定义操作。它使用类名和程序集名称通过Reflection加载模块,并执行它。

我的主要问题是:如何将所需的依赖项注入这些自定义操作?我的所有操作都来自一个名为ActionBase的基类。

我的初始解决方案是首先通过构造函数注入提供所有依赖项,只需将它们作为参数添加到(ActionBase)Activator.CreateInstance();中,但这是有问题的,因为我不想强制执行所有派生操作在其他行动的依赖中。

这是我的第二个解决方案:我决定使用事件将“依赖提供者”放在EventTriggerActionService中。使用此方法,将在EventTriggerActionService中注入所有自定义操作的所有依赖项,并且自定义操作将通过触发事件来请求依赖项。

这是我的EventTriggerActionService

public class EventTriggerActionService
{
    private IEmailerFactory _emailerFactory;
    public EventTriggerActionService(IEmailerFactory emailerFactory) 
    {
        _emailerFactory = emailerFactory;
    }

    public void Execute(EventTriggerAction eventTriggerAction, EventContext context)
    {   
        var assemblyName = eventTriggerAction.EventAction.EventActionHandlerAssembly;
        var className = eventTriggerAction.EventAction.EventActionHandlerClass;

        Assembly actionHandlerAssembly = Assembly.Load(assemblyName);
        Type actionHandlerType = actionHandlerAssembly.GetType(className);

        var action = (ActionBase)Activator.CreateInstance(); // Instantiate the action by its base class, ActionBase

        action.Arguments = data.Arguments; // This is a dictionary that contains the arguments of the action

        // Register all the dependency providers here
        GetEmailer emailerHandler = GetEmailer;
        actionHandlerType.GetEvent("GetEmailer").AddEventHandler(action, emailerHandler); // Register the event handler

        action.Execute(); // Execute the Action
    }

    // The custom action "Requests" a dependency by firing this event
    private void GetEmailer(ref IEmailer emailer)
    {
        emailer = _emailerFactory.Create();
    }
}

public delegate void GetEmailer(ref IEmailer emailer);

这是ActionBase

public abstract class ActionBase
{
    public event GetEmailer GetEmailer;

    private IEmailer _emailer;

    protected IEmailer Emailer
    {
        get
        {
            if (_emailer == null)
            {
                GetEmailer(ref _emailer);
            }
            return _emailer;
        }
    }

    protected Dictionary<string,string> Arguments { get; set; }

    public void Execute()
    {
        // Perform some common logic here
        ExecuteAction(); // Execute the custom action
    }

    public abstract ResultBase ExecuteAction();
}

这是我的自定义操作之一:

public class SimpleEmailSender : ActionBase
{
    public override ResultBase ExecuteAction()
    {
        Emailer.Send(Arguments["EmailBody"]);
    }
}

请记住,EventTriggerActionService位于隔离的库中,并将由不同的消费者应用程序使用。消费者应用程序可以选择使用IoC容器,也可以选择穷人的DI。

我的问题是,对我的问题有更优化的解决方案吗?我认为我的解决方案肯定解决了强制依赖于所有派生操作的问题。

2 个答案:

答案 0 :(得分:1)

我认为您的主要问题在于您的基类ActionBase

此类中的Action方法基本上是一个包装器,它在调用实际操作之前执行一些公共逻辑。 常识需要一些外部服务(此处为IEmailer)才能运行,您不希望对派生类强制使用哪种依赖。

使用事件的一种更简单的方法可能是创建一个公共IEmailer属性,并在EventTriggerActionService.Execute方法中创建操作时设置它。

这样可以防止您必须通过构造函数传递引用,同时仍然可以访问IEmailer instance。 你会得到以下内容:

public abstract class ActionBase
{
    protected IEmailer Emailer { get; set; }

    protected Dictionary<string,string> Arguments { get; set; }

    public void Execute()
    {
        // Do something with Emailer.
        ExecuteAction(); // Execute the custom action
    }

    public abstract ResultBase ExecuteAction();
}

public class EventTriggerActionService
{
    // omit for readability 
    public void Execute(EventTriggerAction eventTriggerAction, EventContext context)
    {   
        // omit for readability

        var action = (ActionBase)Activator.CreateInstance(); // Instantiate the action by its base class, ActionBase

        action.Arguments = data.Arguments; // This is a dictionary that contains the arguments of the action
        action.Emailer = _emailerFactory.Create();

        // omit for readability
    }
    // omit for readability
}

但是,由于您可以控制调用操作的方式,为什么要强制公共逻辑进入基类?

您可以轻松提取常用逻辑并将其与操作分开 例如:

public interface IActionBase
{
    ResultBase ExecuteAction();
}

public class SimpleEmailSender : IActionBase
{
    private Dictionary<string, string> arguments;
    private IEmailer Emailer;

    public SimpleEmailSender(Dictionary<string, string> arguments, IEmailer emailer)
    {
        this.arguments = arguments;
        this.emailer = emailer;
    }

    public override ResultBase ExecuteAction()
    {
        this.emailer.Send(Arguments["EmailBody"]);
        return null;
    }
}   

public class EventTriggerActionService
{
    // omit for readability 
    public void Execute(EventTriggerAction eventTriggerAction, EventContext context)
    {   
        // omit for readability

        this.RunPreLogic();
        action.ExecuteAction();
        this.RunPostLogic();

        // omit for readability
    }

    public void RunPreLogic() {}        
    public void RunPostLogic() {}

    // omit for readability
}

从示例中可以看出,逻辑已从基类中删除。从提供这些服务的需要中释放任何派生类。 剩下的唯一事情是将所需的服务注入到创建的类的构造函数中。

我过去使用的DI容器总是提供一些注入服务的方式。如果你不能(或者不想)使用容器,那么提取构造函数信息并自己解决它应该不会太难。

如果您使用的是ASP.NET Core,则默认的Microsoft依赖注入为此提供实用程序类ActivatorUtilitiesmsdn

如果您不想在EventTriggerActionService中使用公共逻辑,则可以始终将其提取到单独的服务中,或者执行处理公共逻辑的特殊操作。

答案 1 :(得分:0)

因为EventTriggerActionService不知道它正在处理什么类型,所以必须让自定义操作决定他们想要什么。所以,我认为服务定位器模式是一个很好的解决方案。假设您在EventTriggerActionService方面定义了服务定位器,如:

public static class ServiceLocator
{
    private static Dictionary<string, Type> container = new Dictionary<string, Type>();

    public static void RegisterService(string name, Type implType)
    {
        container.Add(name, implType);
    }

    public static objcet GetService(string name)
    {
        return Actinator.CreateInstance(container[name]);
    }
}

将您的ActionBase和自定义操作更改为:

public abstract class ActionBase
{
    private static MethodInfo getServiceMI;

    static ActionBase()
    {
        // Find out which assembly implement ServiceLocator.
        var asm = AppDomain.CurrentDomain.GetAssemblies().First(x => x.GetTypes().Any(y => y.Name == "ServiceLocator"));
        // Get MethodInfo of ServiceLocator.GetService.
        getServiceMI = asm.GetType("ServiceLocator").GetMethod("GetService", BindingFlags.Public | BindingFlags.Static);
    }

    protected static object GetService(string name)
    {
        return getServiceMI.Invoke(null, new object[] { name });
    }

    // And your original staff.
}

public class ActionSample : ActionBase
{
    IDependency1 d1 = GetService("name1");
    ...
    IDependencyN dN = GetService("nameN");

    public override ResultBase ExecuteAction()
    {
        d1.DoSomething();
        ...
        dN.DoSomething();
    }
}

最后,在EventTriggerActionService中,在创建自定义操作的实例和实例之前注册所有依赖项。

public class EventTriggerActionService
{
    static EventTriggerActionService()
    {
        ServiceLocator.RegisterService("name1", typeof(dependencyImpl1));
        ...
        ServiceLocator.RegisterService("nameN", typeof(dependencyImplN));
    }

    public void Execute(EventTriggerAction eventTriggerAction, EventContext context)
    {   
        var assemblyName = eventTriggerAction.EventAction.EventActionHandlerAssembly;
        var className = eventTriggerAction.EventAction.EventActionHandlerClass;

        Assembly actionHandlerAssembly = Assembly.Load(assemblyName);  
        var action = (ActionBase)Activator.CreateInstance(); // Instantiate the action by its base class, ActionBase
        action.Arguments = data.Arguments; // This is a dictionary that contains the arguments of the action

        action.Execute(); // Execute the Action
    }
}

那里的所有代码都只是为了展示这个想法。您可以自定义ServiceLocator或其他符合您目的的内容。