在这种情况下如何避免服务定位? (Ninject)

时间:2012-10-21 14:21:50

标签: c# inversion-of-control ninject metadata

我有一组从***Converter继承的IConverter个对象,目的是从字符串或XML反序列化对象:

public interface IConverter
{
    object Convert(object value, Type targetType);
}

(我不知道编译时的类型,所以我不能在这里使用泛型。)

我使用ConvertsAttribute标记转换器可以转换的类型,然后将其加倍为Ninject ConstraintAttribute

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public class ConvertsAttribute : ConstraintAttribute
{
    public Type TargetType { get; private set; }
    public ConvertsAttribute(Type t)
    {
        TargetType = t;
    }

    public override bool Matches(IBindingMetadata metadata)
    {
        return metadata.Has("Converts") && metadata.Get<Type>("Converts") == this.TargetType;
    }
}

[Converts(typeof(Int32))]
[Converts(typeof(Single))]
[Converts(typeof(String))]
[Converts(typeof(Double))]
public class BasicConverter : IConverter
{
    public object Convert(object value, Type targetType)
    {
        return System.Convert.ChangeType(value, targetType);
    }
}

在序列化模块中绑定转换器时,我在“转换”下附加了类型元数据:

    private void BindConverter(Type typeInfo)
    {
        var converterAttributes = typeInfo.GetCustomAttributes(typeof(ConvertsAttribute), true);

        foreach (var attribute in converterAttributes.Cast<ConvertsAttribute>())
            Bind<IConverter>().To(typeInfo).WithMetadata("Converts", attribute.TargetType);
    }

在解析类型时,我可以在容器中查询转换特定类型的转换器:

    private IConverter GetConverter(Type t)
    {
        return converterKernel.Get<IConverter>(metadata => t == metadata.Get<Type>("Converts"));
    }

然而,这意味着我必须在我的类构造函数中获取IKernel实例并在运行时查询它,我想避免它(它闻起来我能够查询我的converterKernel中的非IConverter对象)。 / p>

我可以在构造函数中简单地请求IEnumerable<IConverter>,然后在每个转换器的类型上查询ConvertsAttribute,但我想知道是否有办法利用Ninject的元数据为我做这个...我可以请求我的构造函数中的IEnumerable<IConverter> 附带了元数据?在这个例子中,我想构建一个类型为IDictionary<Type, IConverter>的字典,这意味着我不必查询容器本身。

1 个答案:

答案 0 :(得分:1)

  

我不知道编译时的类型,所以我不能在这里使用泛型。

在编译时不知道类型,并不意味着你不能使用泛型。相反,我认为你应该使用泛型,因为:

  • 它允许您(通过其通用接口
  • (轻松)批量注册这些类型
  • 它允许您立即请求正确的转换器,而不是 必须请求所有并循环遍历它们以找出要使用的转换器,而不必使用ConvertsAttribute来装饰类型(因为此信息已通过实现该通用接口在元数据中可用),
  • 它允许您为缺少自定义转换器的任何类型定义默认(回退)实现(映射到System.Convert.ChangeType)。

您仍然需要非通用IConverter接口,并且没有编译时类型的使用者可以依赖于该接口。但除了这个非通用接口,您还可以定义以下通用接口:

public interface IConverter<T>
{
    T Convert(object value);
}

您现在可以通过实现该界面来定义自定义转换器:

public class MyTypeConverter : IConverter<MyType>
{
    public MyType Convert(object value) { ... }
}

您可以定义一个可用作“后备”实现的通用实现。当没有为请求的类型注册自定义转换器时,可以使用此实现:

public class DefaultConverter<T> : IConverter<T>
{
    public T Convert(object value)
    {
        return System.Convert.ChangeType(value, targetType);
    }
}

由于您的消费者在编译时没有可用的任何类型信息,因此仍然可以方便地使用非通用IConverter。您可以将此接口的实现定义为composition root的一部分。当您将其置于此引导程序逻辑中时,您可以依赖Kernel,而无需将容器用作Service Locator

public class NinjectConverter : IConverter
{
    private readonly Kernel kernel;

    public NinjectConverter(Kernel kernel)
    {
        this.kernel = kernel;
    }

    public object Convert(object value, Type targetType)
    {
        var converterType =
            typeof(IConverter<>).MakeGenericType(targetType);

        dynamic converter = this.kernel.Get(converterType);

        return converter.Convert(value);
    }
}

此实现使用反射和动态类型。这不是性能最佳的代码,但与Ninject的开销相比,开销可能无关紧要。

您现在可以轻松批量注册所有已定义的自定义转换器,如下所示:

var assembly = typeof(IConverter<>).Assembly;

var converterRegistrations =
    from type in assembly.GetExportedTypes()
    where !type.IsAbstract && !type.IsGenericTypeDefinition
    from service in type.GetInterfaces()
    where service.IsGenericType
    where service.GetGenericTypeDefinition() == 
        typeof(IConverter<>)
    select { service, type };

foreach (var registration in converterRegistrations)
{
    kernel.Bind(registration.service).To(registration.type);
}

我希望Ninject(也许是一个单行)可以更容易地做到这一点,但说实话,我并不是Ninject。对于阅读此内容的任何人:如果您有更好的方法,请更新我的答案。

你可以像这样注册你的后备转换器:

kernel.Bind(typeof(IConverter<>))
    .To(typeof(DefaultConverter<>))