Interfaces: Things Tutorials Don’t Tell You

Okay let’s talk about interfaces. Interfaces are one of those things that are a little tricky to get your mind wrapped around but once you do they make a whole lot of sense. Of course there are a bunch of tutorials out there about how to use them and hundreds of posts explaining what they are but the common answer you get on most posts is this:

“An interface is like a contract”

Okay that is 100% a true statement and if you gump it down to it’s lowest level that is an acceptable way of explaining it. But what makes an interface so special then? What makes it different from a base or an abstract class you can just inherit from? Let’s take a look.

So WTF Is An Interface?

Let’s first talk about what an interface can’t do. Interfaces cannot contain any logic. Properties of an interface cannot be directly assigned values. Interfaces cannot be instantiated like a normal object. These reasons are often why people will refer to an interface as a contract. In my opinion an interface could better be described as this:

“An interface is something that is substitutable”

I can take an object that inherits an interface and swap it out with a completely different type of object that inherits the same interface even though they do completely different things.

Getting Started

Let’s use a very real world example here. Let’s say we have a class called phone that will be used to send data to multiple systems:

public class Phone
{
    public string Number { get; set; }
    public string Type { get; set; }
    public bool IsOnDoNotCallList { get; set; }
}

Implementing An Interface

We now have two places in our application that will use that class, one that saves the data to a SQL database and another that saves the data to MongoDB cluster. We need to create a data access layer to save the data in both data stores. This is where interfaces start coming in handy. Let’s create our Phone data access interface:

public interface IPhoneDataAccess
{
    public Phone GetPhone(string phoneNumber);
    public void SavePhone(Phone phone);
}

As you can see we have no code in our interface just yet. Let’s start off by creating our SQL data access class:

public class SqlPhoneDataAccess : IPhoneDataAccess
{
    private string _connectionString;

    public SqlPhoneDataAccess(string connectionString)
    {
        _connectionString = connectionString;
    }

    public void SavePhone(Phone phone)
    {
        using (var sqlConnection = new SqlConnection(_connectionString))
        {
            var cmd = new SqlCommand("mySaveStoredProcedure", sqlConnection);
            sqlConnection.Open();
            cmd.ExecuteNonQuery();
        }
    }

    public Phone GetPhone(string phoneNumber)
    {
        var phone = new Phone();

        using (var sqlConnection = new SqlConnection(_connectionString))
        {
            var cmd = new SqlCommand("myGetStoredProcedure", sqlConnection);
            cmd.Parameters.Add(new SqlParameter("@phoneNumber", phoneNumber));
            sqlConnection.Open();
            var reader = cmd.ExecuteReader();

            // execute sql command, map values to phone object
        }

        return phone;
    }
}

Now let’s create our MongoDB data access class:

It boosts energy production order cheap viagra https://unica-web.com/ENGLISH/2015/GA2015-secretary-report.html in cells and curing fatigue.
public class MongoPhoneDataAccess : IPhoneDataAccess
{
    private IMongoCollection<Phone> _phoneCollection;

    public MongoPhoneDataAccess(IMongoCollection<Phone> phoneCollection)
    {
        _phoneCollection = phoneCollection;
    }

    public Phone GetPhone(string phoneNumber)
    {
        var filterBuilder = Builders<Phone>.Filter;
        var filter = filterBuilder.Eq("phoneNumber", phoneNumber);
        var cursor = _phoneCollection.Find<Phone>(filter);
        var phone = cursor.ToList().FirstOrDefault();

        return phone;
    }

    public void SavePhone(Phone phone)
    {
        _phoneCollection.InsertOne(phone);
    }
}

Okay so check it out. We have our two data access classes above, one for SQL and one for Mongo, both inheriting that IPhoneDataAccess interface. Each class has their own constructor taking in whatever the class needs to interact with the database in its signature but they both have the GetPhone and SavePhone methods. This is where the “contract” term comes from. In order for those classes to inherit that interface they must have methods with the same signatures as defined in the interface. Otherwise the code will not compile. Now the reason I like to call them substitutable is because we can use either of those classes anywhere we reference the IPhoneDataAccess in the code. Let’s create a class that actually implements what we just built:

    public class PhoneHandler
    {
        private IPhoneDataAccess _phoneDataAccess;

        public PhoneHandler(IPhoneDataAccess phoneDataAccess)
        {
            phoneDataAccess = _phoneDataAccess;
        }

        public void SetPhoneDoNotCall(string phoneNumber, bool doNotCall)
        {
            var phone = _phoneDataAccess.GetPhone(phoneNumber);

            phone.IsOnDoNotCallList = doNotCall;

            _phoneDataAccess.SavePhone(phone);
        }
    }

Now let’s finish this up by implementing this code into our Program.cs and seeing how all the pieces fit together:

public static class Program
{
    public static void Main(string[] args)
    {
        IPhoneDataAccess phoneDataAccess;

        phoneDataAccess = new SqlPhoneDataAccess("mySqlConnectionString");

        var phoneHandler = new PhoneHandler(sqlPhoneDataAccess);

        phoneHandler.SetPhoneDoNotCall("(800) 867-5309");

        var phoneCollection = mongoDatabase.GetCollection<Phone>("collectionName");

        phoneDataAccess = new MongoPhoneDataAccess(phoneCollection);

        phoneHandler = new PhoneHandler(mongoPhoneDataAccess);

        phoneHandler.SetPhoneDoNotCall("(800) 867-5309");
    }
}

Let’s Analyze What We Just Did

  1. First we have defined our interface for our classes to inherit.
  2. Next we implemented the interface in two classes that served similar purposes but have very different implementation. Both these classes inherited our interface which means they can be substituted for one another when referencing the interface instead of the class that implements the interface.
  3. We then created the PhoneHandler class to update the phones IsOnDoNotCallList property. Notice the constructor in the class takes in an IPhoneDataAccess interface instead of a SqlPhoneDataAccess or MongoPhoneDataAccess object.
  4. Now when we get to Program.Main where we start to call all the code we just wrote, notice a few things:
    • We created a reference to an IPhoneDataAccess called phoneDataAccess but we did not assign it anything. I did this to help demonstrate the flexibility that the interface gives you here.
    • I first assigned the phoneDataAccess variable a new SqlPhoneDataAccess object using the constructor we defined for that class. The constructor needed a connection string to instantiate the object, then used it to create a new PhoneHandler object.
    • I then updated the called the PhoneHandler.SetPhoneDoNotCall method which in turn will execute the code to update our SQL database.
    • Next I created the MongoCollection object, used it to instantiate a MongoPhoneDataAccess object and assigned that to the phoneDataAccess variable we declared earlier.
    • Then I assigned the phoneHandler variable a reference to a new PhoneHandler object but this time I am passing in a MongoPhoneDataAccess object instead of a SqlPhoneDataAccess object.
    • Once again I called the PhoneHandler.SetPhoneDoNotCall method but this time it updated the MongoDB collection instead of the SQL database because when I instantiated the phoneHandler variable I passed in a MongoPhoneDataAccess.

So what should we take away here?

  • An interface is commonly referred to as a contract. It defines the methods and properties our class need to contain but not how those things should be done.
  • Interfaces give you the ability to substitute classes. In our example, I can assign a SqlPhoneDataAccess object or MongoPhoneDataAccess object to the same IPhoneDataAccess variable.
  • Interfaces buy you flexibility. I could use either a SqlPhoneDataAccess or MongoPhoneDataAccess object when instantiating a new PhoneHandler object since the PhoneHandler constructor takes an IPhoneDataAccess in its signature.

Thanks for reading, now go interface all the things!

Leave a Reply

Your email address will not be published. Required fields are marked *