Reputation: 2885
How to configure Interface having multiple concrete implementation using Castle Windsor (using code). Below is the sample code.
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);
}
}
The ServiceTaxCalculator
implementation:
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;
}
}
I want the instance of a concrete class based on service tax applicability. If service tax is applicable, I need ServiceTaxCalculator
else DefaultCostCalculator
.
How to configure this scenario using Castle Windsor.
Upvotes: 5
Views: 1634
Reputation: 23747
Adding an answer to demonstrate @Kryzsztof's preference for service overrides. Instead of a factory method:
container.Register(Component.For<ICostCalculator>()
.UsingFactoryMethod(k =>
new ServiceTaxApplicableCostCalculator(
k.Resolve<ServiceTaxCalculator>(),
k.Resolve<DefaultCostCalculator>())
)
);
You would instead specify the dependencies via DependsOn
:
container.Register(Component.For<ICostCalculator>()
.ImplementedBy<ServiceTaxApplicableCostCalculator>()
.DependsOn(Dependency.OnComponent("with", typeof(ServiceTaxCalculator)))
.DependsOn(Dependency.OnComponent("without", typeof(DefaultCostCalculator))));
The only benefit that is obvious to me is that if a different service is added to ServiceTaxApplicableCostCalculator
's constructor, the service override case would continue to work without any changes (resolving the new service automatically), whereas the factory method would require another call to Resolve
. Beyond that, it is certainly more idiomatic than using a factory method to explicitly create an object.
More information is available in the documentation.
Upvotes: 2
Reputation: 172646
Since we don't really know how you need to determine whether the service tax is applicable, I like to add another solution to Mark's nice answer. Here I use the decorator pattern:
// 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.
}
}
Now you can register this decorator:
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>())
)
);
Upvotes: 3
Reputation: 233150
Here's one way to do it:
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>());
Notice that the isServiceTaxApplicable
variable in this example is an outer variable (not shown), but you can easily replace it with some other boolean check.
Also notice that the DefaultCostCalculator forwards the registration to the ICostCalculcator interface. However, since this is not the first registration of that interface, it's not the default registration.
It's important to register the DefaultCostCalculator after the factory method because this enables the Decorator pattern in those cases where the ServiceTaxCalculator is chosen.
Upvotes: 5