http://www.developer.com/

Back to article

Working With Design Patterns: Adapter


February 6, 2008

The Portfolio class provides the basis for an application that allows users to track stock purchases. Of course, the most desired portfolio functionality is the ability to calculate the worth of all those stock purchases.

The PortfolioTest class, shown in Listing 1, demonstrates use of a simple stub to assist in verifying that the Portfolio can correctly obtain a value. The Portfolio class (see Listing 2) derives the price for each symbol by calling out through a StockLookupService interface. StockLookupService is as simple as it gets: Given a stock symbol, the service returns the symbol's current price (in dollars):

public interface StockLookupService {
   int currentPrice(String symbol);
}

Listing 1: PortfolioTest.

import static org.junit.Assert.*;
import org.junit.*;

public class PortfolioTest {
   private static final String MICROSOFT = "MSFT";
   private static final int MICROSOFT_VALUE = 100;
   private static final String IBM = "IBM";
   private static final int IBM_VALUE = 80;

   private Portfolio portfolio;

   @Before
   public void initialize() {
      StockLookupService service = new StockLookupService() {
         @Override
         public int currentPrice(String symbol) {
            if (symbol.equals(MICROSOFT))
               return MICROSOFT_VALUE;
            if (symbol.equals(IBM))
               return IBM_VALUE;
            return 0;
         }
      };

      portfolio = new Portfolio(service);
   }

   @Test
   public void isEmptyOnCreation() {
      assertSize(0);
      assertEquals(0, portfolio.value());
   }

   @Test
   public void storesSharesPerSymbol() {
      portfolio.purchase(MICROSOFT, 2);
      assertSize(1);
      assertEquals(2, portfolio.shares(MICROSOFT));
      assertEquals(2 * MICROSOFT_VALUE, portfolio.value());
   }

   @Test
   public void sumsSharesPurchasedForSameSymbol() {
      portfolio.purchase(MICROSOFT, 1);
      portfolio.purchase(MICROSOFT, 2);
      assertSize(1);
      assertEquals(3, portfolio.shares(MICROSOFT));
      assertEquals(3 * MICROSOFT_VALUE, portfolio.value());
   }

   @Test
   public void segregatesPurchasesBySymbol() {
      portfolio.purchase(MICROSOFT, 5);
      portfolio.purchase(IBM, 10);
      assertSize(2);
      assertEquals(5, portfolio.shares(MICROSOFT));
      assertEquals(10, portfolio.shares(IBM));
      int expectedValue = 5 * MICROSOFT_VALUE + 10 * IBM_VALUE;
      assertEquals(expectedValue, portfolio.value());
   }

   void assertSize(int expected) {
      assertEquals(0 == expected, portfolio.isEmpty());
      assertEquals(expected, portfolio.size());
   }
}

Listing 2: Portfolio.

import java.util.*;

public class Portfolio {
   private Map<String, Integer> symbols =
      new HashMap<String, Integer>();
   private StockLookupService service;

   public Portfolio(StockLookupService service) {
      this.service = service;
   }

   public boolean isEmpty() {
      return 0 == size();
   }

   public int size() {
      return symbols.size();
   }

   public void purchase(String symbol, int shares) {
      symbols.put(symbol, shares(symbol) + shares);
   }

   public int shares(String symbol) {
      if (!symbols.containsKey(symbol))
         return 0;
      return symbols.get(symbol);
   }

   public int value() {
      int total = 0;
      for (Map.Entry<String, Integer> holding:
         symbols.entrySet()) {
         String symbol = holding.getKey();
         int shares = holding.getValue();
         total += service.currentPrice(symbol) * shares;
      }
      return total;
   }
}

The Portfolio example provides a simple example of calling out to an external API. Often, such APIs are not under your control. In this case, the volatility of calling out to a real API, such as the NASDAQ stock exchange, requires you to provide a test double that fixes the price for a couple known shares. The test double allows you to write unit tests that compare against a fixed answer.

Where does the StockLookupService interface come from? Well, sometimes it will be delivered along with the vendor's code. Other times, you'll have to build the interface yourself. Let me first talk about the interface being delivered as part of the API.

public class JimBobStockExchange implements StockLookupService {
   @Override
   public int currentPrice(String symbol) {
      // Jim Bob's real lookup code
      // goes here...
   }
}

Because JimBobStockExchange implements the StockLookupService and thus provides a currentPrice method, life is very easy for the portfolio application. A client needs simply to create an instance of JimBobStockExchange prior to instantiating a Portfolio:

   StockLookupService service = new JimBobStockExchange();
   Portfolio portfolio = new Portfolio(service);

But, things are rarely so convenient. Often, the vendor will provide no interface:

public class JimBobStockExchange {
   public int currentPrice(String symbol) {
      // Jim Bob's real lookup code
      // goes here...
   }
}

The easiest solution is to create a JimBobStockExchange subclass that implements the StockLookupService interface:

public class JimBobStockExchangeAdapter
      extends JimBobStockExchange
      implements StockLookupService {
   @Override
   public int currentPrice(String symbol) {
      return super.currentPrice(symbol);
   }
}

The JimBobStockExchangeAdapter adapts the public interface for the JimBobStockExchange class into an interface that the client application can use:

   StockLookupService service = new JimBobStockExchangeAdapter();
   Portfolio portfolio = new Portfolio(service);

Sometimes, adapters are this simple.

Other times, adapting a public interface is more work. Even if the vendor provides a Java interface, it will not always be the one you want. Suppose your application needs to support a second lookup service, the AntarcticanStockService:

import java.math.*;
import java.util.*;

public class AntarcticanStockService implements StockService {
   @Override
   public BigDecimal price(List<String> symbols) {
      // really cold code here
   }
}

For whatever odd reason, the Antarctican service requires the symbol to be passed as a list. It also returns the current price as a BigDecimal and not an int. Adapting this interface won't be a one-line delegation.

The second adapter example shows how delegation, not inheritance, is the basis for adaptation (see Listing 3).

Listing 3: AntarcticanStockServiceAdapter.

import java.math.*;
import java.util.*;

public class AntarcticanStockServiceAdapter
   implements StockLookupService {
   private AntarcticanStockService service =
      new AntarcticanStockService();

   @Override
   public int currentPrice(String symbol) {
      List<String> symbols = Collections.singletonList(symbol);
      BigDecimal price = service.price(symbols);
      return price.intValue();
   }
}

Creating the adapter again lets your client application code remain simple:

   AntarcticanStockServiceAdapter service =
      new AntarcticanStockServiceAdapter();
   Portfolio portfolio = new Portfolio(service);

Figure 1: Adapter

About the Author

Jeff Langr is a veteran software developer with over a quarter century of professional software development experience. He's written two books, including Agile Java: Crafting Code With Test-Driven Development (Prentice Hall) in 2005. Jeff is contributing a chapter to Uncle Bob Martin's upcoming book, Clean Code. Jeff has written over 50 published articles on software development, with more than a couple dozen appearing at Developer.com. You can find out more about Jeff at his site, http://langrsoft.com, or you can contact him via email at jeff at langrsoft dot com.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date