Interfaces are a great way to develop applications that are loosely coupled, making it easy to maintain and update your code.  An interface behaves like a contract, meaning that the class implementing the interface and the class using the interface both know exactly what to expect.  A good example of an interface that makes it easier to  understand is the power outlet.  The lamp could care less how it gets its power, if only knows that if you plug it in, it gets power.  Likewise, it doesn’t matter if you’re plugging in a TV, lamp, alarm clock, or anything into the outlet, if you have the right connection you are going to get your power.  In this case, the power outlet is an interface.

Let’s get started with an example.  For the sake of argument, let’s say that we have a bunch of music that we want to store information for in a database.  For the purposes of keeping this simple, I’m just going to store albums, which will contain the artist and a list of songs that make up the album.  In reality, it’s not the best way to store this information, but you get the idea.

   1: using System.Collections.Generic;
   2:  
   3: namespace Jaltiere.Blog.Entities
   4: {
   5:    public class Album
   6:    {
   7:       public string AlbumName;
   8:       public string ArtistName;
   9:       public List<string> Songs;
  10:  
  11:       public Album()
  12:       {
  13:          Songs = new List<string>();
  14:       }
  15:    }
  16: }

Now that I have the basic objects defined, I need to figure out what exactly I want to do with them.  I’ll keep it relatively simple for this example, and I’ll just store the information and retrieve it based on a search string.  My interface looks like this.

   1: using System;
   2: using System.Collections.Generic;
   3:  
   4: using Jaltiere.Blog.Entities;
   5:  
   6: namespace Jaltiere.Blog.Interfaces
   7: {
   8:    public interface IMusicStorage
   9:    {
  10:       bool StoreAlbum(string albumName, string artistName, List<string> songs);
  11:       List<Album> LoadAlbums(string searchName);
  12:    }
  13: }

The next thing I want to do is create a database storage class that implements this interface.  If you telling yourself that we could have skipped the interface creation and just wrote a library that stored the information directly to the database…….hold that thought.  I’ll answer that question a little later.  The next thing I need to do is create my database storage class which will implement my interface.

I chose to use SQL Server 2005 as my backend, and I used stored procedures for all of my data access.  I have provided all of the create scripts for any stored procedures I created, as well as create scripts for the database itself and the tables.  These are available with all of the code if you would like to take a look at it.  My storage class looks like this: 

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Data.SqlClient;
   4: using System.Data.SqlTypes;
   5: using System.Text;
   6:  
   7: using Jaltiere.Blog.Entities;
   8: using Jaltiere.Blog.Interfaces;
   9:  
  10: namespace Jaltiere.Blog
  11: {
  12:    public class DBStorage : IMusicStorage
  13:    {
  14:       /*---------------------------------------------------------------------------------*/
  15:       #region IMusicStorage Members
  16:       /*---------------------------------------------------------------------------------*/
  17:       public bool  StoreAlbum(string albumName, string artistName, List<string> songs)
  18:       {
  19:          SqlConnection connection;
  20:          SqlCommand command;
  21:  
  22:          try
  23:          {
  24:             // Insert the album into the database.
  25:             connection = _GetSQLConnection();
  26:             command = new SqlCommand();
  27:             command.Connection = connection;
  28:             command.CommandType = System.Data.CommandType.StoredProcedure;
  29:             command.CommandText = "piAlbum";
  30:             command.Parameters.Add("@AlbumName", System.Data.SqlDbType.VarChar, 128).Value = albumName;
  31:             command.Parameters.Add("@ArtistName", System.Data.SqlDbType.VarChar, 128).Value = artistName;
  32:             command.ExecuteNonQuery();
  33:  
  34:             // Insert the songs into the database.
  35:             foreach (string song in songs)
  36:             {
  37:                command = new SqlCommand();
  38:                command.Connection = connection;
  39:                command.CommandType = System.Data.CommandType.StoredProcedure;
  40:                command.CommandText = "piSong";
  41:                command.Parameters.Add("@AlbumName", System.Data.SqlDbType.VarChar, 128).Value = albumName;
  42:                command.Parameters.Add("@ArtistName", System.Data.SqlDbType.VarChar, 128).Value = artistName;
  43:                command.Parameters.Add("@SongName", System.Data.SqlDbType.VarChar, 128).Value = song;
  44:                command.ExecuteNonQuery();
  45:             }
  46:          }
  47:          catch
  48:          {
  49:             return false;
  50:          }
  51:          return true;
  52:       }
  53:       /*---------------------------------------------------------------------------------*/
  54:       public List<Album>  LoadAlbums(string searchName)
  55:       {
  56:          SqlConnection connection;
  57:          SqlCommand command;
  58:          SqlDataReader reader;
  59:          List<Album> albums = new List<Album>();
  60:          Album currentAlbum;
  61:  
  62:          try
  63:          {
  64:             connection = _GetSQLConnection();
  65:             command = new SqlCommand();
  66:             command.Connection = connection;
  67:             command.CommandType = System.Data.CommandType.StoredProcedure;
  68:             command.CommandText = "psFindAlbums";
  69:             command.Parameters.Add("@SearchString", System.Data.SqlDbType.VarChar, 128).Value = searchName;
  70:  
  71:             reader = command.ExecuteReader();
  72:             while (reader.Read())
  73:             {
  74:                currentAlbum = new Album();
  75:                currentAlbum.AlbumName = reader.GetString(0);
  76:                currentAlbum.ArtistName = reader.GetString(1);
  77:                albums.Add(currentAlbum);
  78:             }
  79:             reader.Close();
  80:  
  81:             // We have the album information, now load the songs.
  82:             foreach (Album current in albums)
  83:             {
  84:                command = new SqlCommand();
  85:                command.Connection = connection;
  86:                command.CommandType = System.Data.CommandType.StoredProcedure;
  87:                command.CommandText = "psLoadSongs";
  88:                command.Parameters.Add("@AlbumName", System.Data.SqlDbType.VarChar, 128).Value = current.AlbumName;
  89:                command.Parameters.Add("@ArtistName", System.Data.SqlDbType.VarChar, 128).Value = current.ArtistName;
  90:  
  91:                reader = command.ExecuteReader();
  92:                while (reader.Read())
  93:                   current.Songs.Add(reader.GetString(0));
  94:               
  95:                reader.Close();
  96:             }
  97:          }
  98:          catch
  99:          {
 100:             throw;
 101:          }
 102:  
 103:          return albums;
 104:       }
 105:       /*---------------------------------------------------------------------------------*/
 106:       #endregion
 107:       /*---------------------------------------------------------------------------------*/
 108:       #region Helper Functions
 109:       private SqlConnection _GetSQLConnection()
 110:       {
 111:          SqlConnection connection = new SqlConnection();
 112:          try
 113:          {
 114:             connection.ConnectionString = "put_your_connection_string_here";
 115:             connection.Open();
 116:          }
 117:          catch
 118:          {
 119:             throw;
 120:          }
 121:          return connection;
 122:       }
 123:       #endregion
 124:       /*---------------------------------------------------------------------------------*/
 125:    }
 126: }

Now I have all the pieces in place, and I can now work on the user interface to test all of this.  The interface I put together to test this is by no means a thing of beauty, but it will work for this example.  Using this interface, I enter an album to store in the database, as shown in the screenshot below.

storeAlbum

This works the way we expect it to, and the data in our database looks like this:

database  

OK, after all of this, you may be wondering why I went through all the trouble of even creating the interface.  Suppose this application is running on your PC, you show it to one of your friends, and now he would like to run it at his house.  He doesn’t have SQL Server running though, so the current storage option just won’t work.  Your friend would love to have his stuff stored in XML though.  Rather than rewriting the entire application, you can just write an XML storage class, and change a single line of code in the client application to use the new engine. 

This is the implementation in our user interface to store our album in the database. 

   1: private void btnAlbum_Click(object sender, EventArgs e)
   2: {
   3:    string albumName, artistName;
   4:    string[] songList;
   5:    IMusicStorage storage;
   6:    List<string> songs = new List<string>();
   7:      
   8:    // Get the data ready to store.
   9:    artistName = txtArtist.Text;
  10:    albumName = txtAlbumName.Text;
  11:    songList = txtSongs.Text.Split(new char[] { ',' });
  12:  
  13:    // Convert the string array into a generic list for the storage engine.
  14:    foreach (string current in songList)
  15:       songs.Add(current.Trim());
  16:  
  17:    // Instantiate the storage engine.
  18:    storage = new DBStorage();
  19:  
  20:    // Store the album information.
  21:    storage.StoreAlbum(albumName, artistName, songs);
  22: }

The key here is that you implement the same interface you did for the database storage engine when you write the XML storage engine.  It might look something like this:

   1: using System;
   2: using System.Collections.Generic;
   3: using System.IO;
   4: using System.Runtime.Serialization;
   5: using System.Text;
   6: using System.Xml.Serialization;
   7:  
   8: using Jaltiere.Blog.Interfaces;
   9: using Jaltiere.Blog.Entities;
  10:  
  11: namespace Jaltiere.Blog
  12: {
  13:    public class XMLStorage : IMusicStorage
  14:    {
  15:       /*---------------------------------------------------------------------------------*/
  16:       #region IMusicStorage Members
  17:       public bool StoreAlbum(string albumName, string artistName, List<string> songs)
  18:       {
  19:          Album current;
  20:  
  21:          try
  22:          {
  23:             // Set up the album object.
  24:             current = new Album();
  25:             current.AlbumName = albumName;
  26:             current.ArtistName = artistName;
  27:  
  28:             foreach (string song in songs)
  29:                current.Songs.Add(song);
  30:  
  31:             // Serialize the album to an XML file.
  32:             _XMLSerialize(current);
  33:          }
  34:          catch
  35:          {
  36:             throw;
  37:          }
  38:  
  39:          return true;
  40:       }
  41:       /*---------------------------------------------------------------------------------*/
  42:       public List<Album> LoadAlbums(string searchName)
  43:       {
  44:          throw new Exception("Not implemented.");
  45:       }
  46:       /*---------------------------------------------------------------------------------*/
  47:       #endregion
  48:       /*---------------------------------------------------------------------------------*/
  49:       #region Helper Functions
  50:       public static void _XMLSerialize(Album current)
  51:       {
  52:          string fileName;
  53:  
  54:          // Declare our filestream and XMLSerializer.
  55:          FileStream fStream;
  56:          XmlSerializer serializer;
  57:  
  58:          // This could easily come from an app.config file, etc.
  59:          fileName = "storage.xml";
  60:  
  61:          // Create our XML file to hold the serialized data.
  62:          fStream = new FileStream(fileName, FileMode.Create);
  63:  
  64:          // Instantiate our serializer.  Notice that I passed
  65:          // in what kind of object was being serialized.
  66:          serializer = new XmlSerializer(typeof(Album));
  67:  
  68:          // Serialize our object.
  69:          serializer.Serialize(fStream, current);
  70:  
  71:          // Close the file stream.
  72:          fStream.Close();
  73:       }
  74:       #endregion
  75:       /*---------------------------------------------------------------------------------*/
  76:    }
  77: }

Changing a single line of code in the client changes our storage from SQL Server to XML.  The line that you have to change is this one:

   1: // Instantiate the storage engine.
   2: //storage = new DBStorage();
   3: storage = new XMLStorage();

The output using the same album as before looks like this:

   1: <?xml version="1.0"?>
   2: <Album xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   3:   <AlbumName>Ten</AlbumName>
   4:   <ArtistName>Pearl Jam</ArtistName>
   5:   <Songs>
   6:     <string>Once</string>
   7:     <string>Even Flow</string>
   8:     <string>Alive</string>
   9:     <string>Why Go</string>
  10:     <string>Black</string>
  11:     <string>Jeremy</string>
  12:     <string>Oceans</string>
  13:     <string>Porch</string>
  14:     <string>Garden</string>
  15:     <string>Deep</string>
  16:     <string>Release</string>
  17:   </Songs>
  18: </Album>

I hope this example has been helpful in showing how interfaces can be used to promote code maintainability.  Feel free to download the example code.

kick it on DotNetKicks.com