|
Using the Adapter Pattern
|
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.
|
|
|
|
|
|