The Adapter pattern helps provide a better way to leverage existing code. Instead of requiring the developer to redesign components from scratch, the Adapter can integrate work from previous projects or other libraries.
The primary use-case of the adapter is to help transform the properties and methods of an external class into the properties and methods that are compatible with a new application. Similar to an adapter for a screwdriver, the adapter class helps make otherwise incompatible components work well together.
Each adapter has a “Target Interface” (the set of properties and methods required by the new system), and an “Adaptee” (the external control that will be integrated into the project).
There are two primary ways to implement an adapter: object-based and class-based. Object-based adapters inherit from only the Target Interface, and create the Adaptee as a private member variable. Class-based adapters, on the other hand, inherit from both the Target Interface and the Adaptee. The examples below will help to illustrate the concept:
using System;
using System.IO;
using System.Collections;
namespace Program{
public class Program{
public static void Main(){
string[] memlist = new string[]{"A","B","C"};
PrintList(memlist);
PrintList(new FileListAdapter_Ex1("test.txt"));
PrintList(new FileListAdapter_Ex2("test.txt"));
}
public static void PrintList(IEnumerable list){
foreach(object item in list){
Console.WriteLine(item.ToString());
}
}
}
//Object-based adapter
public class FileListAdapter_Ex1 : IEnumerable{
public string FileName = "";
public FileListAdapter_Ex1(string _FileName){
FileName = _FileName;
}
public IEnumerator GetEnumerator(){
StreamReader sr = new StreamReader(FileName);
string line = "";
try{
while((line = sr.ReadLine()) != null){
yield return line;
}
}
finally{
sr.Close();
}
}
}
//Inheritance-based adapter
public class FileListAdapter_Ex2 : StreamReader, IEnumerable{
public string FileName = "";
public FileListAdapter_Ex2(string _FileName): base(_FileName){ }
public IEnumerator GetEnumerator(){
string line = "";
try{
while((line = this.ReadLine()) != null){
yield return line;
}
}
finally{
this.Close();
}
}
}
}
In this example, we are creating an Adapter that can turn any text file on the computer into an IEnumerable collection. This makes it easy to treat any text file as a list, and send it to shared functions such as PrintList for processing.
The first implementation, FileListAdapter_Ex1, is an object-based adapter. When the foreach method is called on FileListAdapter_Ex1, it initializes the StreamReader locally and sends back each line of the text file.
The second implementation, FileListAdapter_Ex2, is a class-based adapter. It inherits from both StreamReader and IEnumerable to create a shared implementation. This provides more flexibility for the target application, however it also opens the door to problems. In this example, for instance, the StreamReader will be closed after the file has been read. The class-based FileListAdapter can only be read once, while the object-based FileListAdapter can be read as many times as necessary, since it will recreate the StreamReader on every traverse. In general, object-based adapters are safer and more robust, while class-based adapters are more flexible since they expose Adaptee internals.
Adapters have a variety of uses in .Net. They are often found when manipulating controls to fit a certain framework, such as the DataGrid framework. The standard C# DataGrid offers only a small subset of controls, and it is often necessary to create adapters to bring additional controls, such as DateTimePickers, into the DataGrid.
There is often confusion between the Adapter and Builder classes. The Builder will be reviewed in a future article, however the primary difference between the Adapter and Builder is that the Builder is focused on defining the Target Interface, while the Adapter is more focused on a single implementation of that Target Interface to an external control. In this regard, an Adapter is often an implementation of a Builder for a single Adaptee.
The Adapter can help improve code by enabling developers to reuse pre-built components and reduce development time. When feasible, instead of recoding components for each new application, developers should use adapters linked to a shared library of controls.
Written by Andrew Palczewski
About the Author
Andrew Palczewski is CEO of apHarmony, a Chicago software development company. He holds a Master's degree in Computer Engineering from the University of Illinois at Urbana-Champaign and has over ten years' experience in managing development of software projects.
Google+