kamilk
kamilk

Reputation: 4039

How does proper use of IoC containers help you to avoid using factories?

Many IoC containers have a feature of 'auto factories', which generate an implementation of an abstract factory based on its interface. However, it's often a second-class citizen: StructureMap declares only a basic support for the feature, while Simple Injector's author has deliberately omitted auto factories from the library claiming that when applying Dependency Injection correctly, the need for using factories is minimized. This not the only place where I came across a similar view.

However, to me factories seem to be an indispensable element of design when using IoC and writing domain models. Say, a simple made-up example could be:

class Order
{
    private ITaxProvider _taxProvider;

    public decimal Quantity { get; set; }
    public decimal PricePerUnit { get; set; }
    public decimal Cost { get; set; }

    public Order(ITaxProvider taxProvider)
    {
        _taxProvider = taxProvider;
    }

    public decimal GetProfit()
    {
        // Don't nitpick on this example, please, it's just to show some
        // kind of domain object that depends on some kind of service,
        // but also has some logic of its own.
        decimal grossProfit = Quantity * PricePerUnit - Cost;
        decimal netProfit = grossProfit * (1.0m - _taxProvider.GetTaxRate());
        return netProfit;
    }
}

In this case, every time I create an Order, say, inside an MVC controller, I will need to use a factory. Eventually, this will most likely be true about any domain object. Even if they may not have any dependencies right now, it is very likely that somebody at some point will request some functionality to behave differently depending on a configuration value, or that some code will be refactored out to separate classes, which may then be decoupled using DI to be testable in separation.

So I am almost willing to roll out factories to take control over initialization of every single domain object. But more experienced developers than myself are saying that I shouldn't need to use factories very often with IoC containers. What should I change in my code's design to make the factories unnecessary?

Upvotes: 2

Views: 276

Answers (1)

Ric .Net
Ric .Net

Reputation: 5540

In my opinion a domain model should never ever contain dependencies. They should be as close a possible to a POCO. There just messages in my system which can be easily shipped from class to class using method injection.

These patterns are described here and here, as the comments already suggested.

If we would make the Order a POCO, how would we calculate the profit? By making a service which does nothing more than calculating the profit:

public class ProfitCalculator
{
    private readonly ITaxProvider taxProvider;

    public ProfitCalculator(ITaxProvider taxProvider)
    {
        this.taxProvider = taxProvider;
    }

    public decimal GetProfit(Order order)
    {
        decimal grossProfit = order.Quantity * order.PricePerUnit - order.Cost;
        decimal netProfit = grossProfit * (1.0m - this.taxProvider.GetTaxRate());
        return netProfit;
    }
}

Assuming ITaxProvider could be a singleton, ProfitCalculator could be a singleton also. And order would be just a plain object:

class Order
{
    public decimal Quantity { get; set; }
    public decimal PricePerUnit { get; set; }
    public decimal Cost { get; set; }
}

This would remove the need for a factory completely. Using the message based patterns as described in the referenced blogpost will make your life far easier.

Update:

As Steven comment suggests this is not the only solution. Instead of creating a calculator class you could also define a method which takes the dependency as a parameter. I prefer to implement such a method as an extension method like this:

public static class DomainExtensions
{
    public static decimal GetProfit(this Order order, ITaxProvider taxProvider)
    {
        decimal grossProfit = order.Quantity * order.PricePerUnit - order.Cost;
        decimal netProfit = grossProfit * (1.0m - taxProvider.GetTaxRate());
        return netProfit;
    }
}

Upvotes: 3

Related Questions