AspAlliance.com LogoASPAlliance: Articles, reviews, and samples for .NET Developers
URL:
http://aspalliance.com/articleViewer.aspx?aId=1265&pId=-1
Using the Adapter Pattern
page
by Brian Mains
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 35714/ 62

Introduction

With a myriad of API's available in business systems, programs (like Microsoft Word), web services, etc., there are many different interfaces a developer has to understand to tie them all together into one business solution that he or she may be developing.  For instance, maybe an application needs to connect to credit card service gateways to verify a customer's credit limit or to run a debit against the account.  An application could connect to a health insurance provider to bill them for services provided.  An application could display maps from varying map providers.

One thing that is certain with tying in various business systems is that the API's a developer uses will be different. There is not any standard interface across the industry for implementing a certain type of system, but there is good reason for you to want to do so. Uniformity in application development makes maintenance and documentation easier and makes the application design better.

However, with these varying systems, how can one tie together all of these interfaces into one common one?  This is where the adapter pattern comes into play, as we will see next.

Example Services

The example services that we will use are two providers within the sample application included with this article.  The provider code exists in the same application, but in reality it would be a reference from a Dynamic Link Library (DLL), a web service reference or some other means.  But, for example purposes, let us look at two of the methods that would be commonly in the service.

Listing 1

namespace BooksExpress
{
  public class BooksProvider
  {
    public BookItem GetByISBN(string isbn){}
    public BookItem[]Search(string searchText, bool anyTerms, bool
      relativeMatching){}
  }
}
 
namespace ILuvBooks
{
  public static class BooksService
  {
    public static DataTable GetBookByISBN(string isbn){}
    public static DataTable GetBooksBySearch(string searchText, bool anyTerms){}
  }
}

The example above shows the code for the two fictitious companies that we will be working with in this example: BooksExpress and ILuvBooks.  As you can see, these two providers implement different approaches. The first uses a domain object to represent the book data, as well as using an instance of the class to provide the book data. The second class uses more of a procedural approach in creating a static class with static methods, returning a DataTable to the caller.  How do we calculate for this?  Welcome the adapter pattern.

Introduction to the Adapter Pattern

The adapter pattern creates a common class interface for various sources that use a variety of interface definitions, as we have seen above. The specific implementations for "Books Express" and "I Luv Books" inherit from this interface, which acts as a connection between your application and the third-party application interfaces. That is the sole purpose of the adapter pattern; using the standardized interface in your application, it contains all the necessary third-party application code to do what it needs to do using the standard interface. Let us look at an example of the adapter classes for the two methods shown above.  Here we have the standard base adapter class for this example.

Listing 2

public abstract class BookAdapter
{
  public abstract Book RetrieveByISBN(string isbn);
  public abstract Book[] RetrieveBySearch(string searchText, bool anyTerms);
}

This base class defines the interface that the adapter classes will use. Let us take a look at the BooksExpressAdapter, which is the adapter used to connect to the "Books Express" repository and pull back book results. Notice that the adapter uses a Book class, not a BookItem or DataTable, to represent the book information. This is something unique to the local application and requires that a transformation of the data take place, which we will see later.

Books Express Adapter

Below is the BooksExpressAdapter class.

Listing 3

public class BooksExpressAdapter: BookAdapter
{
  private BooksProvider _provider = null;
 
  protected BooksProvider Provider
  {
    get
    {
      if (_provider == null)
        _provider = new BooksProvider();
      return _provider;
    }
  }
 
  public override Book RetrieveByISBN(string isbn)
  {
    BookItem item = this.Provider.GetByISBN(isbn);
    if (item != null)
      return this.CreateBook(item);
    else
      return null;
  }
 
  public override Book[]RetrieveBySearch(string searchText, bool anyTerms)
  {
    BookItem[]items = this.Provider.Search(searchText, anyTerms, false);
    List < Book > books = new List < Book > ();
 
    foreach (BookItem item in items)
      books.Add(this.CreateBook(item));
    return books.ToArray();
  }
}

A few things to note is that the provider is lazy loaded, so that it is instantiated when needed.  In addition, these methods use the CreateBook method to perform the conversion, which we will see next. The Searching mechanism loops through the results, creating a Book object for each item found in the collection. The Book object has a slightly different interface, as shown below.

Listing 4

private Book CreateBook(BookItem item)
{
  Book book = new Book();
  book.Author = item.Author;
  book.Description = item.Description;
  book.ISBN = item.ISBN;
  book.Title = item.Title;
  //Nothing done with item.Keyword
  return book;
}

Because the API uses a business object, the parameters come over as a 1-to-1 mapping, however, the keyword parameter is ignored because the new API does not use it.

I Luv Books Adapter

The ILuvBooksAdapter class is shown below.

Listing 5

public class ILuvBooksAdapter: BookAdapter
{
  public override Book RetrieveByISBN(string isbn)
  {
    DataTable results = BooksService.GetBookByISBN(isbn);
 
    if (results != null && results.Rows.Count > 0)
      return this.CreateBook(results.Rows[0]);
    else
      return null;
  }
 
  public override Book[]RetrieveBySearch(string searchText, bool anyTerms)
  {
    DataTable results = BooksService.GetBooksBySearch(searchText, anyTerms);
    List < Book > books = new List < Book > ();
 
    if (results != null && results.Rows.Count > 0)
    {
      foreach (DataRow row in results.Rows)
        books.Add(this.CreateBook(row));
    }
 
    return books.ToArray();
  }
}

There are differences with this approach.  Because the provider is static, it makes it slightly easier to reference. However, there is added code because of some conversions from the data source to the business object, which was built-in with the last provider.  To convert the row to a Book object, the CreateBook method is used here too.

Listing 6

private Book CreateBook(DataRow bookRow)
{
  Book book = new Book();
  book.Author = bookRow["Author"].ToString();
  book.Description = bookRow.IsNull("Description") ? string.Empty: bookRow[
    "Description"].ToString();
  book.ISBN = bookRow["ISBN"].ToString();
  book.Title = bookRow["Title"].ToString();
  return book;
}

As you can see, a certain amount of null checking must be performed for the Description field.  The rest of the fields are considered required.  The resulting book object is returned.

Sample Application

Below are some screen shots of examples taken from a sample application that I developed.  The first example is querying the "I Luv Books" provider by ISBN.

Figure 1

When searching for "ASP.NET," the following results come up.

Figure 2

Switching over to the "Books Express" provider, below are the two screenshots of using it for connecting to the data store.

Figure 3

Figure 4

At the root of this is a BookAdapter object that represents one of the two available book adapters.  The following code gets the correct adapter to use based on the radio button list.

Listing 7

private BookAdapter GetAdapter()
{
  if (this.rbBooksExpress.Checked)
    return new BooksExpressAdapter();
  else
    return new ILuvBooksAdapter();
}

Note that this could pull the provider name/type from a data source or configuration file. For this example, I used the Template Method pattern to retrieve the object.  Next, based on which button was clicked, an event handler runs, passing the book results to another method.

Listing 8

private void btnISBNSearch_Click(object sender, EventArgs e)
{
  BookAdapter adapter = this.GetAdapter();
  Book book = adapter.RetrieveByISBN(this.mtbISBN.Text);
 
  if (book == null)
    this.NotifyNoBooks();
  else
  this.LoadBookResults(new Book[]
  {
    book
  }
  );
}
 
private void btnSearch_Click(object sender, EventArgs e)
{
  BookAdapter adapter = this.GetAdapter();
  Book[]books = adapter.RetrieveBySearch(this.txtSearch.Text, chkAny.Checked);
 
  if (books == null || books.Length == 0)
    this.NotifyNoBooks();
  else
    this.LoadBookResults(books);
}

Because we reference the provider through the BookAdapter abstract class, we do not need to know anything specific about the provider, which makes this portion of the code dynamic.  That is the benefit of inheritance and polymorphism through the adapter pattern.  Derived types do not need to declare their specific type, so we can work with the class interface that BookAdapter provides.  NotifyNoBooks simply shows a message box and so I will not show that, but LoadBookResults takes the array and creates a series of list items for each book.

Listing 9

private void LoadBookResults(Book[]books)
{
  this.lvwBooks.Items.Clear();
 
  foreach (Book book in books)
  {
    ListViewItem item = new ListViewItem(book.Title);
    item.SubItems.Add(book.Author);
    item.SubItems.Add(book.Description);
    item.SubItems.Add(book.ISBN);
    this.lvwBooks.Items.Add(item);
  }
}
Downloads
Conclusion

The adapter pattern is easy to implement and makes it easier to connect to external data stores of varying types.  It does not matter how the interfaces vary; you can easily create a common interface using the adapter pattern as shown in this article.



©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-04-24 6:26:42 PM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search