The Adapter Pattern and You
We’ve all heard of design patterns. Some people make regular use of them, while others wish they knew more about them. However, knowing and using patterns doesn't automatically make your code bullet-proof or well designed. Two main benefits to knowing design patterns are the recognition of a problem that has a standard solution, to avoid reinventing the wheel, and the sharing of a common vocabulary with other developers. Still, many of us use design patterns all the time without even realizing it. In this post, we’ll walk through a discussion of the Adapter Pattern, a simple design pattern that you may be using without even knowing it.
Let’s start with an example. The .NET framework has a class called the NameValueCollection. Basically, NameValueCollection works almost like a dictionary of strings, but with one quirky difference. The NVC's Add() method literally adds the passed-in value to any existing value for that key, separated by a comma:
NameValueCollection nvc = new NameValueCollection();
nvc.Add("theKey", "value1");
nvc.Add("theKey", "value2");
if (nvc["theKey"] == "value1,value2")
{
// Win!
}
NameValueCollections are used all over the place in ASP.NET. For instance, on a WebForms page the Request.QueryString property is a NameValueCollection containing all of the key=value pairs in the URL. This collection turns out to be be pretty useful for a number of cool purposes. However, one sort of annoying aspect of the NVC is that it doesn’t implement IDictionary<string,>. At Velir, we’ve got a whole slew of sweet extension methods for IDictionary, and I don’t know about you but I wouldn’t want to rewrite them all just so I can use them on a NVC. Wouldn’t it also be nice to be able to pass the Request.QueryString to any method that can take an IDictionary<string,>? Of course it would!</string,></string,>
Here's one way to get my NameValueCollection into a workable dictionary:
public IDictionary<string,> ToDict(NameValueCollection nvc)
{
Dictionary<string,> dict = new Dictionary<string,>();
// Iterate through all the keys of the NVC,
// copying each key-value pair into the dictionary
foreach (string key in nvc.AllKeys)
{
dict[key] = nvc[key];
}
return dict;
}</string,></string,></string,>
It's not terrible, and it does work, but that’s kind of wasteful, right? I mean, if we’re only trying to pass the NVC into a method that accepts a dictionary, we end up having to make an exact duplicate of the collection. Not only that, but if said method is expected to modify the collection, we’d have to copy all of the new dictionary’s elements back into the original NameValueCollection. All in all, it's not the ideal solution.
You may have been thinking to yourself, “Well, why wouldn’t I just make a class that implements the IDictionary<string,> interface and wraps a NameValueCollection to store and retrieve the key-value pairs?” Congratulations, even if you didn’t realize it, you’ve just applied the Adapter Pattern!</string,>
So what exactly is an adapter? An adapter is just a class that wraps another one. That’s it. That’s all you need to know. But for the sake of completeness, here is the official Gang of Four definition:
Adapter Pattern: Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.
If UML diagrams and formal specification languages are your thing, then Wikipedia has you covered.
The advantage of knowing the actual definition is when you encounter a similiar situation in your day to day work. A problem you encounter may just ring a bell: “I wish this NVC could be used like an IDictionary<string,>, the behavior is so close... it just needs a little bit of... wait a minute, this sounds familiar...”</string,>
OK, enough talk let’s see some code.
// A class that adapts a NameValueCollection to make it
// look like an IDictionary<string,>
public class NameValueCollectionDictionaryAdapter : IDictionary<string,>
{
// We'll use this internal NameValueCollection
// reference as the instance we adapt
private NameValueCollection _nvc;
// The constructor takes the adapted NVC as a parameter and stores
// it so we can use it as the backing store for all dictionary ops
public NameValueCollectionDictionaryAdapter(NameValueCollection nvc)
{
_nvc = nvc;
}
// The basic operations of setting and retrieving simply forward
// the information on the the internal NVC
public string this[string key]
{
get { return _nvc[key]; }
set { _nvc[key] = value; }
}
// The NVC 'AllKeys' property is a string[], which maps nicely to
// an ICollection
public ICollection Keys
{
get { return _nvc.AllKeys; }
}
// Since NameValueCollection doesn't have an equivalent method
// we need to iterate through all the values and return them
// as a List
public ICollection Values
{
get { return Keys.Select(key => _nvc[key]).ToList(); }
}
// Use the indexer of the NVC to make sure we wipe out
// any values that already exist for this key
public void Add(string key, string value)
{
_nvc[key] = value;
}
// Most of the time we can just forward the message
// right on to the NVC -- no magic needed!
public int Count
{
get { return _nvc.Count; }
}
public void Clear()
{
_nvc.Clear();
}
public bool ContainsKey(string key)
{
return Keys.Contains(key);
}
// The NVC's Remove() method doesn't have a return value,
// so we first need to check if the key exists in the collection
// before doing anything.
public bool Remove(string key)
{
if (!ContainsKey(key))
{
return false;
}
_nvc.Remove(key);
return true;
}
// ... Insert the rest of the IDictionary methods here ...
} </string,></string,>
That’s just about all there is to it, though I’ve left out some methods and error-checking for the sake of a readable example. Additionally, a real-world adapter would follow the IDictionary spec a bit closer by doing things such as throwing an exception when trying to call Add() with an existing key. By using the adapter, we totally avoid the extra time and space overhead of the “just make a copy” method from earlier. Plus, we can edit the adapted IDictionary and our edits will be reflected in the NVC. Sweet!
Notice in the implementation of Add() how we're not simply forwarding on to the NameValueCollection's Add() method. The reason is due to the multiple-value behavior that we discussed earlier. So, instead of using the NVC's Add() we just use its indexer, which doesn't have the behavior of appending values to existing ones. The adapter is, well... adapting the NVC behavior to be consistent with IDictionary.
So we’ve covered the part about recognizing common problems, but what about the whole shared vocabulary thing that I mentioned earlier? This post was inspired by Dave’s post about sorting IList instances. If you missed it, I’ll pause for a moment while you go read it... Back? Notice how the final implementation of Sort() uses this mysterious ArrayList.Adapter() method. What’s up with that?
Now that we know all about the Adapter Pattern, some light is starting to shine on what this method is doing. ArrayList.Adapter() instantiates a new instance of an internal class which uses the Adapter Pattern. This makes any IList look and act just like an ArrayList. It’s that simple, and the hint is right there in the name of the method, if you know about adapters.
In fact, the framework is full of this kind of stuff, and if you know your design patterns you’re already familiar with how these classes are supposed to be used, and what they’re doing. By using a shared vocabulary, everyone wins!