【问题标题】:Dynamically creating Objects at runtime with parameters DI vs Factory在运行时使用参数 DI vs Factory 动态创建对象
【发布时间】:2018-08-23 18:48:39
【问题描述】:

我目前有一个案例,我有多个客户,他们对他们想要的通知形式(即电子邮件、传真等)有不同的看法。他们也可能想要一个或多个。所以我创建了一个带有一些基本反射的工厂,它将根据通过客户配置文件发送的一些参数动态创建具体类。我很好奇使用 ASP.Net Core Dependency Injection 是否有更好的方法来做到这一点?我在这里输入了工厂代码,以帮助人们理解我正在尝试做的事情。

客户资料将把他们订阅的服务作为参数发送给 CreateInstances 的字符串数组,这样只会动态创建特定的服务。

    public Dictionary<string, Type> Notifications;

    public NotificationFactory()
    {
        LoadTypes();
    }

    public IEnumerable<INotificationService> CreateInstances(params string[] namesOfServices)
    {
        var servicesToInstantiate = namesOfServices.ToList();
        List<INotificationService> result = new List<INotificationService>();

        foreach (var service in servicesToInstantiate)
        {
            Type serviceName = GetServiceNameToCreate(service.ToLower());
            if (serviceName != null)
            {
                result.Add(Activator.CreateInstance(serviceName) as INotificationService);
            }
        }

        return result;
    }

    private Type GetServiceNameToCreate(string NotificationClassName)
    {
        return Notifications.FirstOrDefault(a => a.Key.Contains(NotificationClassName)).Value;
    }

    protected virtual void LoadTypes()
    {
        Notifications = Assembly.GetExecutingAssembly().GetTypes()
            .Where(t => typeof(INotificationService).IsAssignableFrom(t) && !t.IsInterface)
            .ToDictionary(t => t.Name.ToLower(), t => t);
    }

【问题讨论】:

  • 这可能更适合Code Review
  • 这对我来说还不错。我会考虑将所有服务名称放在一个枚举中,以便将服务名称发送到列表中的手指麻烦的可能性较小。
  • 谢谢,伯图斯!这是一个很好的建议。但我不确定核心中的 DI 引擎在实现目标方面是否会比我编写代码做得更好?
  • DI 要求能够几乎完全基于构造函数中的类型来解析实例。如果您有一些静态逻辑,您可以使用工厂方法注册您的类型,但是,在这种情况下,这将有点难以实现(尽管可能并非不可能),因为它基于个人用户偏好。最好还是留在你的工厂这里。

标签: c# dependency-injection asp.net-core-2.0 factory


【解决方案1】:

采用任意字符串并以您的方式激活实例是一个坏主意。该字符串至少应列入白名单。例如,如果恶意用户能够篡改数据库中的字符串,他们可以选择要激活的类型,并可能触发未经授权的行为或拒绝服务。

相反,定义一个类型的白名单,并将每个类型与构造您需要的服务的委托相关联。

var map = new Dictionary<string,Func<INotificationService>>
{
    { "EMAIL" : () => new EmailService() },
    { "PHONE" : () => new PhoneService() }
};

然后要获得服务,调用

if (!map.ContainsKey(serviceType)) throw new ArgumentException(nameof(serviceType));
INotificationService service = map[serviceType]();

或者,如果您愿意:

Func<INotificationService> factory;
if (!map.TryGetValue(serviceType, out factory)) throw new ArgumentException(nameof(serviceType));
INotificationService service = factory();

这样你就可以确定服务类型是你支持的,并且你不需要使用任何反射。

如果您需要更多的可扩展性

如果您希望能够在不更改填充map 的代码的情况下添加更多交付方式,您当然可以从配置文件中填充地图。尽管我想知道您如何在不更改任何代码的情况下实现新的交付方法,以及您是否拥有需要您能够做到的真正的 NFR。在我看来,新的交付方法不会经常添加,并且在其他方​​面会很重要,所以在这里添加一行代码没有什么意义。

【讨论】:

    猜你喜欢
    • 2020-11-11
    • 2019-01-03
    • 2020-04-12
    • 1970-01-01
    • 2011-02-21
    • 2017-12-28
    • 1970-01-01
    • 1970-01-01
    • 2019-04-11
    相关资源
    最近更新 更多