【问题标题】:Using Ninject custom instance providers to bind successfully using factory method argument to resolve使用 Ninject 自定义实例提供者绑定成功,使用工厂方法参数解析
【发布时间】:2013-04-07 03:20:48
【问题描述】:

我一直在研究this accepted answer 来解决一个类似的问题,我认为具体工厂根据工厂方法上的字符串参数返回一个实现,该参数与具体实现上的命名绑定相匹配。

当工厂是抽象工厂并且我希望使用基于 Ninject 约定的绑定时,我正在努力让一个稍微复杂的示例正常工作。考虑以下测试:

[Fact]
public void VehicleBuilderFactory_Creates_Correct_Builder_For_Specified_Client()
{
    // arrange
    StandardKernel kernel = new StandardKernel();
    kernel.Bind(typeof (IVehicleBuilderFactory<,>))
        .ToFactory(() => new UseFirstArgumentAsNameInstanceProvider())
        .InSingletonScope();

    kernel.Bind(scanner => scanner
                    .FromThisAssembly()
                    .SelectAllClasses()
                    .WhichAreNotGeneric()
                    .InheritedFrom(typeof(IVehicleBuilder<>))
                    .BindAllInterfaces());

    var bicycleBuilderFactory = 
        kernel.Get<IVehicleBuilderFactory<IVehicleBuilder<BlueBicycle>, BlueBicycle>>();
    string country = "Germany";
    string localizedColor = "blau";

    // act
    var builder = bicycleBuilderFactory.Create<IVehicleBuilder<BlueBicycle>>(country);
    Bicycle Bicycle = builder.Build(localizedColor);

    // assert
    Assert.IsType<BlueBicycleBuilder_Germany>(builder);
    Assert.IsType<BlueBicycle>(Bicycle);
    Assert.Equal(localizedColor, Bicycle.Color);
}

这里是我尝试玩弄手电筒和刀具的地方,因为我曾经在互联网上看到过:

public class UseFirstArgumentAsNameInstanceProvider : StandardInstanceProvider
{
    protected override string GetName(MethodInfo methodInfo, object[] arguments) {
        return methodInfo.GetGenericArguments()[0].Name + "Builder_" + (string)arguments[0];
        // ex: Germany -> 'BlueBicycle' + 'Builder_' + 'Germany' = 'BlueBicyleBuilder_Germany'
    }

    protected override ConstructorArgument[] GetConstructorArguments(MethodInfo methodInfo, object[] arguments) {
        return base.GetConstructorArguments(methodInfo, arguments).Skip(1).ToArray();
    }
}

当我尝试用此错误分配 bicycleBuilderFactory 时,我被刺伤并着火了:

System.InvalidCastException was unhandled by user code
  Message=Unable to cast object of type 'Castle.Proxies.ObjectProxy' to type 'Ninject.Extensions.Conventions.Tests.IVehicleBuilderFactory`2[Ninject.Extensions.Conventions.Tests.IVehicleBuilder`1[Ninject.Extensions.Conventions.Tests.BlueBicycle],Ninject.Extensions.Conventions.Tests.BlueBicycle]'.
  Source=System.Core
  StackTrace:
       at System.Linq.Enumerable.<CastIterator>d__b1`1.MoveNext()
       at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source)
       at Ninject.ResolutionExtensions.Get[T](IResolutionRoot root, IParameter[] parameters) in c:\Projects\Ninject\ninject\src\Ninject\Syntax\ResolutionExtensions.cs:line 37
       at Ninject.Extensions.Conventions.Tests.NinjectFactoryConventionsTests.VehicleBuilderFactory_Creates_Correct_Builder_For_Specified_Client() in C:\Programming\Ninject.Extensions.Conventions.Tests\NinjectFactoryConventionsTests.cs:line 40
  InnerException: 

是否可以使用 ToFactory() 方法和自定义提供程序进行绑定,使用工厂方法参数 ("Germany") 连同泛型类型参数 (IVehicleBiulder&lt;BlueBicycle&gt;, BlueBicycle) 解析类型?

这是测试的其余代码,尽可能简洁易读。

public interface IVehicleBuilderFactory<T, TVehicle> 
    where T : IVehicleBuilder<TVehicle> where TVehicle : IVehicle
{
    T Create<T>(string country);
}

VehicleBuilder 实现

public interface IVehicleBuilder<T> where T : IVehicle { T Build(string localizedColor); }

abstract class BicycleBuilder<T> : IVehicleBuilder<T> where T : Bicycle
{
    public abstract T Build(string localizedColor);
}

public abstract class RedBicycleBuilder : IVehicleBuilder<RedBicycle>
{
    private readonly RedBicycle _Bicycle;
    public RedBicycleBuilder(RedBicycle Bicycle) { _Bicycle = Bicycle; }
    public RedBicycle Build(string localizedColor)
    {
        _Bicycle.Color = localizedColor;
        return _Bicycle;
    }
}
public abstract class GreenBicycleBuilder : IVehicleBuilder<GreenBicycle>
{
    private readonly GreenBicycle _Bicycle;
    public GreenBicycleBuilder(GreenBicycle Bicycle) { _Bicycle = Bicycle; }
    public GreenBicycle Build(string localizedColor)
    {
        _Bicycle.Color = localizedColor;
        return _Bicycle;
    }
}
public abstract class BlueBicycleBuilder : IVehicleBuilder<BlueBicycle>
{
    private readonly BlueBicycle _Bicycle;
    public BlueBicycleBuilder(BlueBicycle Bicycle) { _Bicycle = Bicycle; }
    public BlueBicycle Build(string localizedColor)
    {
        _Bicycle.Color = localizedColor;
        return _Bicycle;
    }
}

public class RedBicycleBuilder_USA : RedBicycleBuilder {
    public RedBicycleBuilder_USA(RedBicycle Bicycle) : base(Bicycle) { }
}

public class RedBicycleBuilder_Germany : RedBicycleBuilder {
    public RedBicycleBuilder_Germany(RedBicycle Bicycle) : base(Bicycle) { }
}
public class RedBicycleBuilder_France : RedBicycleBuilder {
    public RedBicycleBuilder_France(RedBicycle Bicycle) : base(Bicycle) { }
}
public class RedBicycleBuilder_Default : RedBicycleBuilder {
    public RedBicycleBuilder_Default(RedBicycle Bicycle) : base(Bicycle) { }
}

public class GreenBicycleBuilder_USA : GreenBicycleBuilder {
    public GreenBicycleBuilder_USA(GreenBicycle Bicycle) : base(Bicycle) { }
}
public class GreenBicycleBuilder_Germany : GreenBicycleBuilder {
    public GreenBicycleBuilder_Germany(GreenBicycle Bicycle) : base(Bicycle) { }
}
public class GreenBicycleBuilder_France : GreenBicycleBuilder {
    public GreenBicycleBuilder_France(GreenBicycle Bicycle) : base(Bicycle) { }
}
public class GreenBicycleBuilder_Default : GreenBicycleBuilder {
    public GreenBicycleBuilder_Default(GreenBicycle Bicycle) : base(Bicycle) { }
}

public class BlueBicycleBuilder_USA : BlueBicycleBuilder
{
    public BlueBicycleBuilder_USA(BlueBicycle Bicycle) : base(Bicycle) { }
}
public class BlueBicycleBuilder_Germany : BlueBicycleBuilder {
    public BlueBicycleBuilder_Germany(BlueBicycle Bicycle) : base(Bicycle) { }
}
public class BlueBicycleBuilder_France : BlueBicycleBuilder
{
    public BlueBicycleBuilder_France(BlueBicycle Bicycle) : base(Bicycle) { }
}
public class BlueBicycleBuilder_Default : BlueBicycleBuilder
{
    public BlueBicycleBuilder_Default(BlueBicycle Bicycle) : base(Bicycle) { }
}

车辆实现:

public interface IVehicle { string Color { get; set; } }
public abstract class Vehicle : IVehicle { public string Color { get; set; } }
public abstract class Bicycle : Vehicle { }
public class RedBicycle : Bicycle { }
public class GreenBicycle : Bicycle { }
public class BlueBicycle : Bicycle { }

【问题讨论】:

  • 自行车不应该有颜色和位置,而不是包含在类型中的信息吗?那么你只需要一个自行车工厂,它的创建方法需要一个颜色和一个国家?
  • @LukeN 好吧,这可能就是我的简化抽象失败的地方。颜色旨在抽象出这样一个事实,例如,RedBicycle(可以继承自 Bicycle&lt;Red&gt; 之类的东西)除了颜色之外还有许多其他独特的行为或特征。此外,如果它是 GermanyRedBicyle,它的行为将会改变(喇叭听起来像“Achtung!”或其他东西)。我会尝试想出一个更好的简化抽象并重构这个问题......
  • 看看en.m.wikipedia.org/wiki/Entity_component_system。随着它们的增长,使用深层类层次结构变得非常棘手。
  • @LukeN +1 获取链接;我研究了observervisitorstrategy 模式(发布此问题后更多),但没有听说过 Entity Component System 作为一个概念。让我发布对Bicycle 系统的重新设计建议,看看这是否可以解决我的问题。

标签: ninject-extensions abstract-factory open-generics ninject-conventions


【解决方案1】:

基于来自@LukeN 的cmets,我重构了Bicycle 类,以便通过构造函数注入使用IColorSetter 设置其颜色。 IColorSetter 实现有一个通用的Color 类型,每个Color 实现都通过IColorLocalizer&lt;T&gt; 的构造函数注入“本地化”。

这样一来,似乎没有一个班级知道任何超出其逻辑责任范围的知识(我认为)。

但是,我需要更多地考虑这一点,以了解如何使用下面显示的重构类来展示如何使用 Ninject 自定义实例提供程序现在可以用于选择属性 IColorLocalizer&lt;T&gt;,因为它是唯一了解颜色和语言的课程;颜色来自其泛型类型,语言来自实现本身的名称。

自从询问原始帖子以来,我已经不再使用 IoC 容器来做出这样的选择,而是选择以编程方式在代码中放入一个用于选择实现的开关,并为任何未处理的异常情况选择默认实现。但我不确定这是否主要是为了超越让我难过的事情,还是因为以这种方式依赖 IoC 容器是一个糟糕的选择。

我需要更多地更新这个答案。

车辆

public abstract class Vehicle {
    public abstract string Color { get; internal set; }
    public abstract string Move();
}

public class Bicycle : Vehicle {
    public Bicycle(IColorSetter colorSetter) { colorSetter.SetColor(this); }
    public override string Color { get; internal set; }
    public override string Move() { return "Pedaling!"; }
}

颜色设置器

public interface IColorSetter { void SetColor(Vehicle vehicle); }

public class ColorSetter<T> : IColorSetter where T : Color
{
    private readonly T _color;
    public ColorSetter(T color) { _color = color; }

    public void SetColor(Vehicle vehicle) { vehicle.Color = _color.Name; }
}

颜色定位器

public interface IColorLocalizer<in T> where T : Color {
    void LocalizeColor(T color);
}

public class GermanBlueLocalizer : IColorLocalizer<Blue> {
    public void LocalizeColor(Blue color) { color.Name = "blau"; }
}

public class EnglishBlueLocalizer : IColorLocalizer<Blue> {
    public void LocalizeColor(Blue color) { color.Name = "blue"; }
}

颜色

public abstract class Color { public string Name { get; internal set; } }

public class Red : Color {
    public Red(IColorLocalizer<Red> colorLocalizer) {
        colorLocalizer.LocalizeColor(this); }
}

public class Green : Color {
    public Green(IColorLocalizer<Green> colorLocalizer) {
        colorLocalizer.LocalizeColor(this); }
}

public class Blue : Color {
    public Blue(IColorLocalizer<Blue> colorLocalizer) {
        colorLocalizer.LocalizeColor(this); }
}

【讨论】:

  • 为什么调色师需要了解车辆?你希望你的自行车有颜色和语言。因此,您希望能够通过 BicycleFactory.Create(Colour c, Language l) 或仅在创建自行车后设置属性来创建自行车。自行车类可能不需要知道它在指定语言中的颜色名称,但是使用自行车类的其他一些类却知道。在这种情况下,将依赖项注入到该类中。
  • 那么下一个问题是你有一个矩阵,它由一维的颜色和下一维的语言组成。如果您尝试为每个类型构建一个类型,那么当您添加新的语言或颜色时,最终会出现类型的爆炸式增长。您可以将颜色名称解析器的实现隐藏在采用颜色和语言的接口后面,然后您可以将类创建为矩阵、连接表或一系列列表,或者在 ioc 技巧后面。这可以让您避免在向自行车添加第三个属性时发生的组合爆炸。
  • 假设您添加了山地和公路自行车。颜色和类型绝对没有任何关系。如果您遵循上面提出的方案,您最终会得到 GermanRedMountainBike 和 GermanRedRacingBike 等等。如果红色德国自行车的下坡速度提高了 2%,那么您必须将这一规则和所有其他规则推广到不同的类型中。但是,您需要区分 GermanRed21SpeedRacingBike 和 GermanRed18SpeedRacing 自行车!快速维护变得非常困难。这有意义吗?
  • @LukeN 您提出的所有要点。等待编辑包含您的建议的此答案。您关于创建采用颜色和语言的IColorNameResolver 的观点可能有助于将示例引导回我关于利用 IoC 容器解决此问题的原始问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-03
  • 1970-01-01
相关资源
最近更新 更多