Adapter Design Pattern In C#

Contents

What is Adapter Design Pattern?

The Adapter Pattern is a software design pattern that attempts to reconcile the differences between two otherwise-incompatible interfaces. This pattern is especially useful when attempting to adapt to an interface that cannot be refactored.

When you have a class that needs to utilize a particular interface, and you have a library that includes the functionality you need, but it doesn’t use the interface that you require. You can achieve the reuse of that library’s code by creating an Adapter class. This adapter class sits between your client code, and the code that’s in this library, and adapts one interface to the other. The Adapter design pattern is one of the most common, and most useful patterns available to us as software developers.

Adapter Pattern Structure

Adapter Design Pattern Structure

Let’s look at the structure of the Adapter Pattern using this UML diagram. The two basic players within this example are the Client, and the Adaptee, shown above.

Now,

The Client needs some of the logic that exists within the Adaptee. Specifically, there is this AdaptedOperation that has the code that the Client wants to be able to utilize. Unfortunately, the Client has been written in such a way that it cannot directly call this AdaptedOperation because its interface is not the one that the client expects. This is where the Adapter Pattern comes into play.

First,

The Adapter interface is created, exposing an operation that has the interface the client expects.

Next,

For each different implementation required, at a minimum, one, its different ConcreteAdapter is created that takes that Operation and implements it, such that that code calls the AdaptedOperation. In this way, the Client will now be able to call the Operation on the ConcreteAdapter, which in turn will call the AdaptedOperation on the Adaptee.

The Client really wants to use the Adaptee directly, but unfortunately, it can’t due to the incompatible interface. The Adapter Pattern is simply allowing us to achieve this despite this incompatibility.

Adpater Pattern Real World Example

Let’s see the implementation of Adapter pattern in C#, with IDbDataAdapter example,

IDbDataAdapter is one of the .Net built-in interfaces under System.Data namespace.

The IDbDataAdapter interface inherits from the IDataAdapter interface and allows an object to create a DataAdapter designed for use with a relational database.

Now, In the below code, I created a DataRenderer class that takes IDbDataAdapter as a parameter and renders data that comes from data adapters in the form of data Tables.

 1 public class DataRenderer
 2 {
 3     private readonly IDbDataAdapter _dataAdapter;
 4 
 5     public DataRenderer(IDbDataAdapter dataAdapter)
 6     {
 7         _dataAdapter = dataAdapter;
 8     }
 9 
10     public void Render(TextWriter writer)
11     {
12         writer.WriteLine("Rendering Data:");
13         var myDataSet = new DataSet();
14 
15         _dataAdapter.Fill(myDataSet);
16 
17         foreach (DataTable table in myDataSet.Tables)
18         {
19             foreach (DataColumn column in table.Columns)
20             {
21                 writer.Write(column.ColumnName.PadRight(20) + " ");
22             }
23             writer.WriteLine();
24             foreach (DataRow row in table.Rows)
25             {
26                 for (int i = 0; i < table.Columns.Count; i++)
27                 {
28                     writer.Write(row[i].ToString().PadRight(20) + " ");
29                 }
30                 writer.WriteLine();
31             }
32         }
33     }
34 }

Now consider,

If we want to render the below persons list using the above DataRenderer class, in the same format without tweaking its code.

 1 public class Person
 2 {
 3     public string Name { get; set; }
 4     public int Age { get; set; }
 5 }
 6 
 7 static void Main(string[] args)
 8 {
 9     List<Person> persons = new List<Person>() {
10         new Person(){ Name ="Foo", Age = 25} ,
11         new Person(){ Name ="Bar", Age = 25}
12     };
13    
14     Console.ReadLine();
15 }

But, DataRenderer accepts an IDbDataAdapter and thus it is incompatible with persons datatype. However, one thing we can do is that we can create another renderer that accepts the persons list.

What if,

We could convert this List<Person> persons into the format that is compatible with DataRenderer class then we don’t have to write the same repeatable code for rendering data. Let’s see how

 1 class PersonCollectionDbAdapter : IDbDataAdapter
 2 {
 3     private readonly IEnumerable<Person> _persons;
 4 
 5     public PersonCollectionDbAdapter(IEnumerable<Person> persons)
 6     {
 7         _persons = persons;
 8     }
 9 
10     public int Fill(DataSet dataSet)
11     {
12         var myDataTable = new DataTable();
13         myDataTable.Columns.Add(new DataColumn("Name", typeof(string)));
14         myDataTable.Columns.Add(new DataColumn("Description", typeof(int)));
15 
16         foreach (var person in _persons)
17         {
18             var myRow = myDataTable.NewRow();
19             myRow[0] = person.Name;
20             myRow[1] = person.Age;
21             myDataTable.Rows.Add(myRow);
22         }
23         dataSet.Tables.Add(myDataTable);
24         dataSet.AcceptChanges();
25 
26         return myDataTable.Rows.Count;
27     }
28 
29     //Below methods are not implemented because they are useless in our scenario
30 
31     public DataTable[] FillSchema(DataSet dataSet, SchemaType schemaType)
32     {
33         throw new NotImplementedException();
34     }
35 
36     public IDataParameter[] GetFillParameters()
37     {
38         throw new NotImplementedException();
39     }
40 
41     public int Update(DataSet dataSet)
42     {
43         throw new NotImplementedException();
44     }
45 
46     public MissingMappingAction MissingMappingAction
47     {
48         get { throw new NotImplementedException(); }
49         set { throw new NotImplementedException(); }
50     }
51 
52     public MissingSchemaAction MissingSchemaAction
53     {
54         get { throw new NotImplementedException(); }
55         set { throw new NotImplementedException(); }
56     }
57 
58     public ITableMappingCollection TableMappings
59     {
60         get { throw new NotImplementedException(); }
61     }
62 
63     public IDbCommand SelectCommand
64     {
65         get { throw new NotImplementedException(); }
66         set { throw new NotImplementedException(); }
67     }
68 
69     public IDbCommand InsertCommand
70     {
71         get { throw new NotImplementedException(); }
72         set { throw new NotImplementedException(); }
73     }
74 
75     public IDbCommand UpdateCommand
76     {
77         get { throw new NotImplementedException(); }
78         set { throw new NotImplementedException(); }
79     }
80 
81     public IDbCommand DeleteCommand
82     {
83         get { throw new NotImplementedException(); }
84         set { throw new NotImplementedException(); }
85     }
86 }

After creating DbAdapter for our persons list we can render our list using the existing DataRenderer class. Let’s see how

 1 static void Main(string[] args)
 2 {
 3     List<Person> persons = new List<Person>() {
 4         new Person(){ Name ="Foo", Age = 25} ,
 5         new Person(){ Name ="Bar", Age = 25}
 6     };
 7 
 8     var renderer = new DataRenderer(new PersonCollectionDbAdapter(persons));
 9     renderer.Render(Console.Out);
10     Console.ReadLine();
11 }
12 /* 
13 Output:
14 Rendering Data:
15 Name                 Description
16 Foo                  25
17 Bar                  25
18 */

Now from the above code, we can conclude that the List<Person> is an Adaptee, DataRenderer is a Client that depends on IDbDataAdapter our Adapter and PersonCollectionDbAdapter is our Concrete Adapter.

Thus, by working through an adapter, our client could reuse the existing object that provides the needed functionality.

If you yourself are writing a library or a framework, and you want to ensure that it’s useable by future classes that may not even have been written yet, and so you cannot be certain what their interface will be, you can add support for an Adapter as part of your interface for your code, and this will make it easier for other future applications to use your code.

This idea is used within the. NET Framework Library itself, you will find if you look at ADO.NET in the System.Data namespace using a tool such as Reflector, that IDbDataAdapter has several derived types, including a concrete class called DbDataAdapter, also you’ll find the OdbcDataAdapter, OleDbDataAdapter, OracleDataAdapter, and SqlDataAdapter. Each of these implements at its core the IDbDataAdapter interface.

Where To Apply Adapter Pattern?

  • You should consider using the Adapter Pattern whenever you want to use an existing class’s functionality, but its interface is not the one that you require.

  • Another scenario, if you’re trying to create reusable code, and you don’t want to tie it too tightly to a particular implementation, you should use some kind of an Adapter interface as what you’re code depends on, so that future clients could implement their own version of that Adapter and still make use of your code.

  • You should remember the Open/Closed Principle, which states that modules should be open to extension but closed to modification, and by utilizing the Adapter Pattern in your implementations of your code, you allow for your code to better follow the Open/Closed Principle.

Note: You can download the complete solution demo from my github repository.

Further Reading

  • A simple example of the Open/Closed Principle by Joel Abrahamsson - The Open/Closed principle says that we should strive to write code that doesn’t have to be changed every time the requirements change. Here’s a simple example by Joel.

  • Populating a DataSet from a DataAdapter - Microsoft Docs - The ADO.NET DataSet is a memory-resident representation of data that provides a consistent relational programming model independent of the data source. The DataSet represents a complete set of data that includes tables, constraints, and relationships among the tables. Interaction with existing data sources is controlled through the DataAdapter.

Adapter Design Pattern In C#
Share this

Subscribe to Code with Shadman