views:

155

answers:

5

Hi!

I´m having a little bit of trouble sorting a way to manage automatic resolved and manual dependencies in my classes.

Let´s say I have two classes to calculate prices: one calculates how much I will charge for shipping and the other how much I will charge for the entire order. The second uses the first in order to sum the shipping price to the entire order price.

Both classes have a dependency to a third class that I will call ExchangeRate which gives me the exchange rate I should use for price calculation.

So far we have this chain of dependency:

OrderCalculator -> ShippingCalculator -> ExchangeRate

I´m using Ninject to resolve these dependencies and this was working until now. Now I have a requirement that the rate returned by ExchangeRate class will vary upon a parameter that will be provided in the Constructor (because the object won´t work without this, so to make the dependency explicit it´s placed on the constructor) coming from a user input. Because of that I can no longer resolve my dependencies automatically.

Whenever I want to the OrderCalculator or any other classes that depends on ExchangeRate I cannot ask the Ninject container to resolve it to me since I need to provide the parameter in the constructor.

What do u suggest in this case?

Thanks!

EDIT: Let's add some code

This chain of objects is consumed by a WCF service and I'm using Ninject as the DI container.

public class OrderCalculator : IOrderCalculator
{ 
        private IExchangeRate _exchangeRate; 
        public OrderCalculator(IExchangeRate exchangeRate) 
        { 
                _exchangeRate = exchangeRate; 
        } 
        public decimal CalculateOrderTotal(Order newOrder) 
        { 
                var total = 0m; 
                foreach(var item in newOrder.Items) 
                { 
                        total += item.Price * _exchangeRate.GetRate();
                } 
                return total;               
        } 
} 

public class ExchangeRate : IExchangeRate 
{ 
        private RunTimeClass _runtimeValue; 
        public ExchangeRate(RunTimeClass runtimeValue) 
        { 
                _runtimeValue = runtimeValue; 
        } 
        public decimal GetRate() 
        { 
                //returns the rate according to _runtimeValue
                if(_runtimeValue == 1) 
                        return 15.3m; 
                else if(_runtimeValue == 2) 
                        return 9.9m 
                else 
                        return 30m; 
        } 
} 

//WCF Service
public decimal GetTotalForOrder(Order newOrder, RunTimeClass runtimeValue)
{   
    //I would like to pass the runtimeValue when resolving the IOrderCalculator depedency using a dictionary or something
    //Something like this ObjectFactory.Resolve(runtimeValue);
    IOrderCalculator calculator = ObjectFactory.Resolve();    
    return calculator.CalculateOrderTotal(newOrder);    
}
A: 

Move that initialization out of the constructor.

Steven Sudit
+5  A: 

As always, when you have a partial dependency on a run-time value, the solution is an Abstract Factory.

Something like this should work:

public interface IExchangeRateFactory
{
    ExchangeRate GetExchangeRate(object runTimeValue);
}

Now inject an IExchangeRateFactory into your consumers instead of ExchangeRate and use the GetExchangeRate method to convert the run-time value to an ExchangeRate instance.

Obviously you will also need to provide an implementation of IExchangeRateFactory and configure NInject to map the interface to your implementation.

Mark Seemann
Let´s see if I understood. My OrderCalculator/ShippingCalculator must receive an object (IExchangeRateFactory) that is capable of building the ExchangeRate class and call the GetExchangeRate passing the runtime parameter, right? If yes, then the runTimeValue parameter will become something that OrderCalculator/ShippingCalculator should care about and I would like that to not happpen.
tucaz
If *none* of those should deal with the run-time value, then where would it come from?
Mark Seemann
I understand what you are saying, but if I do like that then something (runTimeValue) that was only a dependency of ExchangeRate will also become a dependency to whatever other classes uses it. I'm looking for a way to pass this runTimeValue to the DI Container and let it take care of the creation of the object.
tucaz
I can't really answer your question when I don't understand from *where* the run-time value comes. Perhaps you can update your question with a code sample...
Mark Seemann
Done! Added code.
tucaz
Okay, that's much better, thanks. Now I understand what the issue is. I would seriously recommend a change in design. Right now you are trying to use the container as a Service Locator from within the GetTotalForOrder method. You would be better off using Constructor Injection with the WCF service as well. This will better expose which dependencies are long-lived services and which are short-lived domain objects. As far as I can see you need an Abstract Factory like this: IOrderCalculator Create(RunTimeClass runtimeValue);
Mark Seemann
I´m not sure I can do that. GetTotalForOrder is my entry point for the application since it´s a method in a WCF service and this call is the very first moment that I have the runTimeValue available. Could you please give an example with some code? Thanks!
tucaz
Here's how to enable Constructor Injection in WCF: http://stackoverflow.com/questions/2454850/how-do-i-pass-values-to-the-constructor-on-my-wcf-service/2455039#2455039. Inject an Abstract Factory into the service and use the factory to create an IOrderCalculator from runTimeValue.
Mark Seemann
+1  A: 

You may find injecting factory delegates or providers to be useful. Both are pretty much impls of Mark's (+1d) answer.

Ruben Bartelink
A: 

Updated based on the code:

//WCF Service
public decimal GetTotalForOrder(Order newOrder, RunTimeClass runtimeValue)
{   
    //I would like to pass the runtimeValue when resolving the IOrderCalculator depedency using a dictionary or something
    //Something like this ObjectFactory.Resolve(runtimeValue);
    IOrderCalculator calculator = ObjectFactory.Resolve();    
    return calculator.CalculateOrderTotal(newOrder);    
}

Notice how this method only wants runtimeValue so that it can pass it to something else? This is happening because object construction and runtime responsibilities are mixed. I think you should be asking for a IOrderCalculator in the constructor for this method.

So this method just becomes:

//WCF Service
public decimal GetTotalForOrder(Order newOrder, IOrderCalculator calculator)
{   
    return calculator.CalculateOrderTotal(newOrder);    
}

Now, when you construct your IOrderCalculator you should pass runtimeValue to it's constructor. It's a bit hard to answer when we do not know what runtimeValue is or where it comes from.

WW
It works, but then this parameter would also become a dependency to whoever is using the ExchangeRate when it is not true.
tucaz
runTimeValue is an INT parameter with a country/state/city ID that is used to determine the tax rate to be used and it comes from the WCF method call. That´s why it´s only available in runtime and also why I can´t use it in the WCF service creation to construct the IOrderCalculator. It´s only available after the WCF is built.
tucaz
In that case, Mark Seemanns answer is right.
WW
A: 

I ended up doing something totally different.

Before I call the ObjectFactory to resolve the dependencies for me I create a new instance of the IExchangeRate using the runTimeValue and tell the IoC/DI container to use it instead of creating a new one. This way the whole chain of objects is preserved and there is no need for factories.


//WCF Service
public decimal GetTotalForOrder(Order newOrder, RunTimeClass runtimeValue)
{   
    IExchangeRate ex = new ExchangeRate(runtimeValue);
    IOrderCalculator calculator = ObjectFactory.With<IExchangeRate>(ex).GetInstance();
    return calculator.CalculateOrderTotal(newOrder);    
}

But since Ninject doesn't have a way to do this (only Rebind which is not what I want) I changed my container to StructureMap.

Thanks guys for all your help! Really appreciate it!

tucaz