Channels ▼
RSS

C/C++

Dependency Injection


Example: Converting Currency

In a sample e-commerce application, a BasketController, which manages the user's shopping basked, retrieves the user's preferred currency. I'll show the currency conversion example by converting a Basket to the user's currency. Currency is an abstraction that models a currency.

public abstract class Currency
{
    public abstract string Code { get; }
    public abstract decimal GetExchangeRateFor(
         string currencyCode);
}

The Code property returns the currency code for the Currency instance. Currency codes are expected to be international currency codes. For example, the currency code for Danish Kroner is DKK, whereas it's USD for US Dollars.

The GetExchangeRateFor method returns the exchange rate between the Currency instance and some other currency. Notice that this is an abstract method, which means that I'm making no assumptions about how that exchange rate is going to be found by the implementer.

In the next section, I'll examine how Currency instances are used to convert prices, and how this abstraction can be implemented and wired up so that you can convert some prices into such exotic currencies as US Dollars or Euros.

I'll use the Currency abstraction as an information-carrying dependency to perform currency conversions of Baskets, so I'll add a ConvertTo method to the Basket class:

  public Basket ConvertTo(Currency currency)

This will loop through all the items in the basket and convert their calculated prices to the provided currency, returning a new Basket instance with the converted items.

Through a series of delegated method calls, the implementation is provided by the Money class:

public Money ConvertTo(Currency currency)
{
    if (currency == null)
    {
        throw new ArgumentNullException("currency");
    }
    var exchangeRate =
        currency.GetExchangeRateFor(this.CurrencyCode);
    return new Money(this.Amount * exchangeRate, currency.Code);
}

The Currency is injected into the ConvertTo method via the currency parameter (line 1) and checked by the ubiquitous guard clause that guarantees the currency instance is available to the rest of the method body.

The exchange rate to the current currency (represented by this.CurrencyCode) is retrieved from the supplied currency and used to calculate and return the new Money instance.

With the implementation of the ConvertTo methods, I can implement the Index method on the BasketController:

BasketController:
public ViewResult Index()
{
    var currencyCode = 
        this.CurrencyProfileService.GetCurrencyCode();
    var currency =
        this.currencyProvider.GetCurrency(currencyCode);
    var basket = this.basketService
        .GetBasketFor(this.User)
        .ConvertTo(currency);
    if (basket.Contents.Count == 0)
    {
        return this.View("Empty");
    }
    var vm = new BasketViewModel(basket);
    return this.View(vm);
}

The BasketController uses an IBasketService instance to retrieve the user's Basket. Once you have the Basket instance, you can convert it to the desired currency by using the ConvertTo method, passing in the currency instance (line 9).

In this case, you're using method injection because the Currency abstraction is information-carrying, but will vary by context (depending on the user's selection).
You could have implemented the Currency type as a concrete class, but that would have constrained your ability to define how exchange rates are retrieved. Now that we've seen how the Currency class is used, it's time to change our viewpoint and examine how it might be implemented.

Implementing Currency

The Currency instance is served by a CurrencyProvider instance that was injected into the BasketController class at class creation by constructor injection. Let's look at this.

private readonly IBasketService basketService;
private readonly CurrencyProvider currencyProvider;

public BasketController(IBasketService basketService,
                        CurrencyProvider currencyProvider)
{
    if (basketService == null)
    {
        throw new
            ArgumentNullException("basketService");
    }

    if (currencyProvider == null)
    {
        throw new
            ArgumentNullException("currencyProvider");
    }

    this.basketService = basketService;
    this.currencyProvider = currencyProvider;
}

The dependencies (saved as read-only fields in lines 1 and 2) are injected into the controller by the call to the constructor (lines 4-5).
To keep the example simple, let's look at how you might implement CurrencyProvider and Currency using a SQL Server database and LINQ to Entities. This assumes that the database has a table with exchange rates that has been populated in advance by some external mechanism. You could also have used a Web service to request exchange rates from an external source.

The CurrencyProvider implementation passes a connection string on to the Currency implementation that uses this information to create an ObjectContext. The heart of the matter is the implementation of the GetExchangeRateFor method:

public override decimal GetExchangeRateFor(string currencyCode)
{
    var rates = (from r in this.context.ExchangeRates
        where r.CurrencyCode == currencyCode
        || r.CurrencyCode == this.code
            select r)
        .ToDictionary(r => r.CurrencyCode);
    return rates[currencyCode].Rate / rates[this.code].Rate;
}

The first thing to do is get the rates from the database. The table contains rates as defined against a single, common currency (DKK), so you need both rates to be able to perform a proper conversion between two arbitrary currencies. You will index the retrieved currencies by currency code so that you can easily look them up in the final step of the calculation.

This implementation potentially performs a lot of out-of-process communication with the database. The ConvertTo method of Basket eventually calls this method in a tight loop, and hitting the database for each call is likely to be detrimental to performance.

Related Patterns

Method injection is mainly used when we already have an instance of the dependency we want to pass on to collaborators, but where we don't know the concrete types of the collaborators at design time (as is the case with add-ins).

Note that with method injection, we're on the other side of the fence compared with other dependency injection patterns: We don't consume the dependency, we supply it.


This article was adapted from Dependency Injection in .NET. The book won the Jolt Productivity Award this year.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video