温莎城堡抽象工厂

Castle Windsor Abstract Factory

我正在尝试了解如何使用 TypedFactoryFacility 创建一个抽象工厂,并且我在基础级别上使用它,但是我不完全了解如何使用 [=82] 对其进行扩展=]时间依赖性

假设我有一个服务需要在运行时间创建:

public interface IRuntimeService {
  void DoThing();
}

使用以下实现

public class RuntimeService : IRuntimeService {
  public void DoThing() {
    // Do some work
  }
}

为了创建我的 IRuntimeService,我创建了一个抽象工厂

public interface IRuntimeServiceFactory {
  IRuntimeService CreateService();
}

在我的 Castle 安装程序中,我使用 TypedFactoryFacility 注册我的 class 和抽象工厂。

public class TypeInstaller : IWindsorInstaller {

  public void Install(IWindsorContainer container, IConfigurationStore store) {
    container.AddFacility<TypedFactoryFacility>();
    container.Register(Component.For<IRuntimeService>().ImplementedBy<RuntimeService>());
    container.Register(Component.For<IRuntimeServiceFactory>().AsFactory());
  }

然后在我的 class 中将使用该服务,我可以使用工厂在 运行 时间创建新的服务实例。

var myService = m_ServiceFactory.CreateService();

上面的一切都完美,但是当我的 RuntimeService class 需要注入时,我 运行 遇到了问题包含 运行 时间参数的依赖链本身。

为了扩展上面的例子,假设我有一个新的运行时间依赖

public interface IRuntimeDependency {
  void DoWork();
}

由 class 实现,它通过构造函数获取 运行 时间字符串值

public class RuntimeDependency : IRuntimeDependency {

  private readonly string m_Param;

  public RuntimeDependency(string param) {
    m_Param = param;
  }

  public void DoWork() {
    // Do work involving the param
  }
}

之前定义的服务class现在需要引用依赖关系

public class RuntimeService : IRuntimeService {

  private readonly IRuntimeDependency m_Dep;

  public RuntimeService(IRuntimeDependency dep) {
    m_Dep = dep;
  }

  public void DoThing() {
    // Do some work involving the dependency
    m_Dep.DoWork();
  }
}

我现在如何使用 TypedFactoryFacility 创建我的服务实例?

我希望能够将我的工厂方法更改为

IRuntimeService CreateService(string param);

但 Windsor 抛出错误“无法解析参数 'param' 类型 'System.String' 的非可选依赖项。

如果我给它一个字符串,Windsor 知道如何创建一个 IRuntimeDependency,如果我给它依赖,它知道如何创建一个 IRuntimeService,为什么它不能直接创建带有字符串参数的 IRuntimeService?

我可以通过两个不同的工厂方法让它工作

IRuntimeService CreateService(IRuntimeDependency dep);
IRuntimeDependency CreateDependency(string param);

我自己手动创建依赖关系

var dep = m_ServiceFactory.CreateDependency(param);
var myService = m_ServiceFactory.CreateService(dep );

^^^这有效,但使用容器的全部意义在于它会为我组装新对象。这是一个相对简单的示例,仅涉及一个依赖项,但如果使用更复杂的对象图,它很容易失去控制。

我当然可以创建自己的工厂实现,但这也抵消了使用 TypedFactoryFacility 的好处,它应该为您创建抽象工厂实现。我很难相信这个问题没有现成的解决方案,但 Windsor 示例不包含任何链式 运行-time 依赖项。

我认为使用 FactoryComponentSelector 不是正确的方法,因为只有一种可能的路径可以创建 RuntimeService 实例。应该可以自动解析。

在许多或大多数情况下,由容器解析的对象依赖于同样由容器解析的其他接口的实现。所以只要所有的接口都注册了实现,容器就可以解析整个依赖链。

但在这种情况下 RuntimeDependency 取决于字符串,这不是容器可以解析的内容。

public RuntimeDependency(string param) {
    m_Param = param;
}

在这种情况下,您可以使用 DependsOn 方法显式提供一个值来实现该依赖关系。

container.Register(Component.For<IRuntimeDependency, RuntimeDependency>()
    .DependsOn(Dependency.OnValue("param","whatEverTheValueIs")));

当然,该值可以来自配置或其他任何地方。我经常使用 SQL 连接字符串。

看来我所要求的是设计上无法实现的。

查看其他 SO 答案。

可以使用 DynamicParameters

container.Register(Component.For<IRuntimeService>()
    .ImplementedBy<RuntimeService>()
    .LifestyleTransient()
    .DynamicParameters((k, d) => {
        d["dep"] = new RuntimeDependency((string)d["param"]);
    }));

请记住,字典键必须与 CreateService 方法和 RuntimeService 构造函数中的参数名称相匹配。

编辑: 如果您打算在每次调用工厂方法时创建一个新实例,您也应该将其设置为 LifestyleTransient。 (默认为单例)