2012-10-21 89 views
0

我有一组从IConverter继承与字符串或XML反序列化对象的目标***Converter对象:在这种情况下如何避免服务地点? (Ninject)

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

在解析类型,I可以然后查询其将特定类型的转换器中的容器:

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

但是,这意味着我必须在我的类构造函数中取出IKernel实例,并在运行时查询它(我想避免它(我能够查询非IConverter对象的ConverterKernel)。

我可以简单地要求IEnumerable<IConverter>在构造函数中,然后查询ConvertsAttribute每个转换器的类型,但我不知道是否有驾驭Ninject的为我做这个元数据的方式......我能要求在IEnumerable<IConverter>我的构造函数附带元数据?在这种情况下,我想构建一个类型为IDictionary<Type, IConverter>的字典,这意味着我不必查询容器本身。

回答

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

因为你的消费者不必在编译时的任何类型的信息,它仍然是方便的非泛型挂在身边。您可以将此接口的实现定义为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(一班轮或许)要做到这一点,但说实话,一个更简单的方法,我不够好。对于任何阅读此内容的人:如果你知道更好的方法,请更新我的答案。

你可以这样注册您的回退转换器:

kernel.Bind(typeof(IConverter<>)) 
    .To(typeof(DefaultConverter<>)) 
相关问题