使用Castle Windsor实现依赖注入的多态性

5
如何使用Castle Windsor(使用代码)配置具有多个具体实现的接口。以下是示例代码。
public interface ICostCalculator
{
    double CalculateTotal(Order order);
}

public class DefaultCostCalculator : ICostCalculator
{
    public double CalculateTotal(Order order)
    {
        return
            order.Items.Sum(x => x.Product.Rate * x.Quantity);
    }
}

ServiceTaxCalculator 的实现:

public class ServiceTaxCalculator : ICostCalculator
{
    private readonly ICostCalculator calculator;
    private double serviveTaxRate = 10.2;

    public ServiceTaxCalculator(ICostCalculator calculator)
    {
        this.calculator = calculator;
    }

    public double ServiceTaxRate
    {
        get { return this.serviceTaxRate; }
        set { this.serviceTaxRate = value; }
    }

    public double CalculateTotal(Order order)
    {
        double innerTotal = 
            this.calculator.CalculateTotal(order);
        innerTotal += innerTotal * servieTaxRate / 100;
        return innerTotal;
    }
}

我希望根据服务税的适用性获取具体类的实例。如果适用服务税,则需要 ServiceTaxCalculator,否则需要 DefaultCostCalculator

如何使用Castle Windsor配置此场景。


1
那么你如何知道服务税何时适用? - Steven
3个回答

5
以下是一种方法:

这里介绍一种方法:

container.Register(Component
    .For<ICostCalculator>()
    .UsingFactoryMethod(k => 
        isServiceTaxApplicable ? 
        (ICostCalculator)k.Resolve<ServiceTaxCalculator>() : 
        k.Resolve<DefaultCostCalculator>()));
container.Register(Component.For<DefaultCostCalculator, ICostCalculator>());
container.Register(Component.For<ServiceTaxCalculator>());

请注意,此示例中的 isServiceTaxApplicable 变量是外部变量(未显示),但您可以轻松地用其他布尔值检查替换它。

还要注意,DefaultCostCalculator 将注册转发到 ICostCalculcator 接口。 但是,由于这不是该接口的第一个注册,因此它不是默认注册。

重要的是在工厂方法之后注册 DefaultCostCalculator,因为这将在选择 ServiceTaxCalculator 的情况下启用装饰器模式。


请使用服务覆盖而不是 IHandlerSelector,具体取决于 isServiceTaxApplicable 的来源。 - Krzysztof Kozmic
我不明白服务覆盖是如何在这里工作的...你为什么更喜欢这个? - Mark Seemann

3

由于我们不知道您如何确定服务税是否适用,因此我想在Mark的好答案中添加另一种解决方案。在这里,我使用装饰器模式:

// Decorator
public class ServiceTaxApplicableCostCalculator 
    : ICostCalculator
{
    private readonly ICostCalculator with;
    private readonly ICostCalculator without

    ServiceTaxApplicableCostCalculator(
        ICostCalculator with, ICostCalculator without)
    {
        this.with = with;
        this.without = without;
    }

    public double CalculateTotal(Order order)
    {
        bool withTax = this.IsWithTax(order);

        var calculator = withTax ? this.with : this.without;

        return calculator.CalculateTotal(order);
    }

    private bool IsWithTax(Order order)
    {
        // determine if the order is with or without tax.
        // Perhaps by using a config setting or by querying
        // the database.
    }
}

现在您可以注册此装饰器:
container.Register(Component.For<ServiceTaxCalculator>());
container.Register(
    Component.For<DefaultCostCalculator, ICostCalculator>());

container.Register(Component.For<ICostCalculator>()
    .UsingFactoryMethod(k => 
        new ServiceTaxApplicableCostCalculator(
            k.Resolve<ServiceTaxCalculator>(),
            k.Resolve<DefaultCostCalculator>())
    )
);

2
这样是无法编译通过的。ServiceTaxCalculator构造函数需要一个ICostCalculator的实例。一般来说,在UsingFactoryMethod中最好使用k.Resolve<Foo>()而不是new Foo(),因为这样会尊重其他组件注册方面的配置,比如生命周期配置。使用new会完全覆盖这些问题。 - Mark Seemann
@Mark:好的,没问题:已经修复了。 - Steven
3
+1 这指向了一种通用解决方案,用于那些容器不支持条件解析的情况。在这些情况下,您可以定义一种更专业的“分支”实现作为基础设施组件,并进行注册,而无需注册其他实现。然而,对于Castle Windsor来说这是不必要的。 - Mark Seemann
使用服务覆盖而不是内联构造函数调用。Windsor 在创建对象方面做得很好,让它处理这部分工作。 - Krzysztof Kozmic

2

为了展示@Kryzsztof对服务覆盖的偏好,添加一个答案。 不使用工厂方法:

container.Register(Component.For<ICostCalculator>()
    .UsingFactoryMethod(k => 
        new ServiceTaxApplicableCostCalculator(
            k.Resolve<ServiceTaxCalculator>(),
            k.Resolve<DefaultCostCalculator>())
    )
);

您可以通过 DependsOn 指定依赖项:

container.Register(Component.For<ICostCalculator>()
    .ImplementedBy<ServiceTaxApplicableCostCalculator>()
    .DependsOn(Dependency.OnComponent("with", typeof(ServiceTaxCalculator)))
    .DependsOn(Dependency.OnComponent("without", typeof(DefaultCostCalculator))));

我唯一明显的好处是,如果在ServiceTaxApplicableCostCalculator的构造函数中添加了不同的服务,则服务覆盖情况将继续正常工作(自动解析新服务),而工厂方法则需要再次调用Resolve。除此之外,使用内联依赖关系肯定比使用工厂方法显得更加惯用。
有关更多信息,请参阅文档

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接