When trying to build maintainable, reusable, and flexible C# code, the object oriented nature of C# only gets us 50% of the way there. Programming to interfaces can provide us with the last 50%. Interfaced-based design provides loose coupling, true component-based programming, easier maintainability and it makes code reuse much more accessible because implementation is separated from the interface.
What's an Interface?
An interface is a reference type object with no implementation. You can think of it as an abstract class with all the implementation stripped out and everything that is not public removed. Abstract classes are classes that can not be instantiated. No properties or methods are actually coded in an interface, they are only defined. So the interface doesn't actually do anything but only has a signature for interaction with other classes or interfaces. A good analogy for an interface is a pencil and a pencil sharpener. When we talk of a pencil and a pencil sharpener we generally know what they do and how they interact. We don't have to know the inner workings of a particular sharpener or pencil, only how to use them through their interface.
public interface IPencil
{
void Write();
bool IsSharp { get; }
}
public interface IPencilSharpener
{
void Sharpen(IPencil pencil);
}
* While it is not enforced by the compiler, for consistency with the .NET framework it's a good idea to name interfaces starting with a capitol 'I'.
Why Use Interfaces?
This make for a much easier "code world" in which to navigate. Imagine if instead of learning how to drive a car and then being able to drive any car, we had to learn how to drive each instance of every car we get into. It would be really inefficient if after learning how to drive the Ford Pinto we had to start all over again in order to figure out the Mustang. A much more efficient way is to deal with the cars interface: the steering wheel, turn signals, gas pedal and brake. This way, no matter what is implemented on the backend of the interface, we don't really care because in the end it subscribes to the basic car contract and that is how we will deal with it (through the interface).
Following our car analogy... If we work for a company producing a component of a car, like an engine, it would be good if our engine could be used in multiple types of cars in "code world". You can see that this is crucial to the success of our business if we need to sell the engine to multiple vendors and provide them with a basic manual on how to plug it in and make it work. Our customer does not need to know anything about how we have implemented the interfaces (which is better for us and better for them - we keep our trade secrets and they don't need to hire someone with the knowledge base) so our engine is really just a "black-box". If we change the engine, we keep the contract the same (the interface) and our customers can just plug in any new upgraded engine we develop.
One of the problems we run into relying on classes where the interface is tightly coupled to their interface (which is the case when no interface is defined) is that if there is a change to the implementation there is a strong possibility that the interface will change slightly. This can create cascading breaks through inheritance chains not only in our code, but also for other projects utilizing our classes. As our projects mature and the amount of code increases, the impact of small changes becomes greater. This is where code reuse breaks down with non-interface based development.
To make our code more reusable, we can provide interfaces to our implementation. This ONLY works if we are disciplined about not altering interfaces after they have been deployed (that would cause the same cascading breaks that non-interface development does). We can always add functionality to an interface without breaking things, but if we alter the existing "hooks" into our implementation we loose one of the primary benefits of using the interface. This is the downside of interface based development. If interfaces are poorly designed and have to change or not easy to implement we will not receive the benefits of using them and all they achieve is to make our code more complex. We need to think out the interfaces very carefully at design time before getting too far into the implementation because once they are implemented and made available to other resources they should not change.
Here's a concrete example using the pencil sharpener: The IPencil interface defined earlier could have been an abstract class declared as follows. Imagine if we are working of version 2 of our great pencil-sharpening program and had a directive to modify the class to allow the writing of different strings through the "Write()" method.
public abstract class PencilBase
{
private bool m_isSharp;
private string m_message;
public void Write()
{
Console.Write(m_message);
}
public bool IsSharp { get { return m_isSharp; } }
}
If we change the interface some of our client's code may no longer compile, right? Can you see how the change to the "Write()" interface below is a bad idea:
public abstract class PencilBase
{
private bool m_isSharp;
//private string m_message;
void Write(string message)
{
Console.Write(message);
}
bool IsSharp { get { return m_isSharp; } }
}
Any code referencing the "Write()" method will now be broken by this very simple change and won't compile. I'm sure you can see how, in the context of a very large and complex system, this code is not really maintainable and how any other code referencing it (or reusing it) is taking a big risk because the implementation is not "fixed".
If we see interfaces we know they are there to provide reuse of the underlying implementation and we know we should work around the interface so we don't break referencing code. For example if we look at the code below, we know the PencilBase must adhere to the IPencil interface so we know we can't change the "Write()" implementation without breaking stuff. Now, any developer who understands interface development can also easily maintain our code without breaking it (They could actually re-write all the implementation code as long as they adhere to the interface). The IPencil interface gives everyone involved with the project a roadmap to what can be changed and what should stay the same.
public interface IPencil
{
void Write();
bool IsSharp { get; }
}
public abstract class PencilBase : IPencil
{
private bool m_isSharp;
private string m_message;
public void Write()
{
Console.Write(m_message);
}
public bool IsSharp
{
get { return m_isSharp; }
}
}
Here is one possible way to make the change without breaking the interface by adding a "Message" property to the class.
public abstract class PencilBase : IPencil
{
private bool m_isSharp;
private string m_message;
public string Message
{
get { return m_message; }
set { m_message = value; }
}
public void Write()
{
Console.Write(m_message);
}
public bool IsSharp
{
get { return m_isSharp; }
}
}
A Contract
Another place interfaces really shine when we are defining how two classes should interact. The interfaces for each class act as a contract. This contract should be complete and concise so that anyone reading it can understand and implement the interface and write code that interacts with our interface. Take a look at our IPencil and IPencilSharpener interfaces below. You can see how IPencilSharpenes sharpens an IPencil. This is the "30,000-foot view" and helps us and other developers understand the structure of the code on an abstract level instead of having to dig through thousands of lines of code to understand (or remember) how a PencilSharpener and Pencil class interact and the impacts of any changes to either. If you are trying to understand any code, try getting a handle on the provided interfaces first and you are 90% of the way there.
public interface IPencil
{
void Write();
bool IsSharp { get; }
}
public interface IPencilSharpener
{
void Sharpen(IPencil pencil);
}
The above interfaces are great, but could be improved because they are not really complete. To have a complete interface "contract" between these two objects, we need a way for the IPencil to be notified when it has been sharpened by the IPencilSharpener so the IPencil can set it's "IsSharp" property back to "true". Can you see a way to improve the interface to complete our contracts and still have reusable, maintainable code?
public interface IPencilSharpener
{
void Sharpen(IPencil pencil);
}
public interface IPencil
{
string Message{ get; set; }
bool IsSharp { get; }
void Write();
void OnSharpened();
}
All we really have to do is add a way for the IPencil to be notified when it has been sharpened. (You probably noticed that I've also added a "Message" property to make the IPencil more usable.) Now any other developer could build pencils and/or pencil sharpeners that will interact with ours if they understand the contracts. Here's how we'll implmement the interfaces.
public class Pencil : IPencil
{
#region Constructors
public Pencil(int countBeforeDull)
{
m_countBeforeDull = countBeforeDull;
m_message = string.Empty;
m_charsUsed = 0;
}
#endregion
#region Member Variables
private string m_message;
private int m_countBeforeDull;
private int m_charsUsed;
#endregion
#region IPencil Members
public string Message
{
get { return m_message; }
set { m_message = value; }
}
public bool IsSharp
{
get { return m_countBeforeDull > m_charsUsed; }
}
public void Write()
{
foreach (char c in m_message)
{
if (IsSharp)
Console.Write(c);
else
Console.Write('#');
m_charsUsed++; // used up a character
}
Console.WriteLine();
}
public void OnSharpened()
{
this.m_charsUsed = 0;
}
#endregion
}
public class PencilSharpener: IPencilSharpener
{
#region IPencilSharpener Members
public void Sharpen(IPencil pencil)
{
pencil.OnSharpened();
}
#endregion
}
And here's our executing code:
static voidMain(string[] args)
{
IPencil p = new Pencil(20);
p.Message = "This pencil should not be dulled any time soon";
p.Write();
if (!p.IsSharp)
{
PencilSharpener sharpener = new PencilSharpener();
sharpener.Sharpen(p);
}
p.Message = "That's better";
p.Write();
}
Overcoming a C# Language Limitation
Using C# we can only inherit from one class but can implement any amount of interfaces. Because of this inheritance limitation, we have to be very careful of which class we choose to inherit from because we only get one. This is a place where scalability is negatively impacted. Once we have an inheritance hierarchy set up, it's difficult to change. We can achieve the same results of inheritance through class composition and exposing the functionality of a "wrapped" object through an interface and our classes won't use up our "one shot" inheritance.
For example, if James Bond needed us to develop a pencil, we would have to be very careful how to build it because the requirements will be quite complex: the pencil could possibly be used an underwater breathing apparatus and as a one-shot gun. The pencil also probably blows up if the pencil lead gets too short. Using only inheritance, this would be very difficult without a complex inheritance heriarchy. Using interfaces, it becomes easy. The code below demonstrates how we can indirectly "inherit" from a Pencil through composition and exposing the contained Pencil implementation through an interface.
class BondPencil : IPencil
{
#region Constructors
public BondPencil(Pencil pencil)
{
m_pencil = pencil;
}
#endregion
#region Member Variables
private Pencil m_pencil;
#endregion
#region IPencil Members
public string Message
{
get { return m_pencil.Message; }
set { m_pencil.Message = value; }
}
public bool IsSharp
{
get { return m_pencil.IsSharp; }
}
public void Write()
{
m_pencil.Write();
if (!IsSharp) Console.WriteLine("Blows up killing the bad guy!");
}
public void OnSharpened()
{
m_pencil.OnSharpened();
}
#endregion
}
Now we could also wrap an UnderwaterBeathingApparatus class and it's IUnderwaterBeathingApparatus and a OneShotGun class and its IOneShotGun interface and we effectively have worked around the single inheritance limitation of c#. Notice that we've also added the functionality to the Write() method to blow up bad-guys (the BondPencil is not really meant for writing anyways). Guess what?... Now we're using design patterns and you are looking at the GOF Decorator pattern.
class BondPencil : IPencil, IUnderwaterBreathingApparatus, IOneShotGun
What's Implementing the Interface
When using the BondPencil or regular Pencil, it would be a good idea to do so through the interface. That way we know that our code will not be broken if someone later comes along and changes the classes or if we ever need to us a different type of pencil (like maybe a mechanical pencil). There will be times we want to know what concrete class we are dealing with so we don't blow ourselves up with a BondPencil. There are a couple ways to do this.
1) The 'is' keyword returns a boolean value and is true if the object can be cast as the type.
if (p is BondPencil) return; // GET OUT OF HERE!
2) The 'as' keyword is similar to 'is' but 'as' returns a reference to the object if it can be cast or a null reference if it can not be cast.
if (null != p as BondPencil) return; // GET OUT OF HERE!
3) If we know it is a BondPencil and want to get at some of the other functionality, like the UnderWaterBreathingApparatus, we can force the cast. However, this is not adviseable because if p can not be cast as a BondPencil this operation will throw an InvalidCastException.
(BondPencil) p
Super Pencil
Let's make a mechanical pencil that's self-sharpening. We just need to check that when it needs sharpening, we check that we are sharpening a mechanical pencil, right?
class MechanicalPencil: IPencil, IPencilSharpener
{
#region Constructors
public MechanicalPencil(int countBeforeDull)
{
m_countBeforeDull = countBeforeDull;
m_message = string.Empty;
m_charsUsed = 0;
}
#endregion
#region Member Variables
private string m_message;
private int m_countBeforeDull;
private int m_charsUsed;
#endregion
#region IPencil Members
public string Message
{
get { return m_message; }
set { m_message = value; }
}
public bool IsSharp
{
get { return m_countBeforeDull > m_charsUsed; }
}
public void Write()
{
foreach (char c in m_message)
{
if (IsSharp)
Console.Write(c);
else
{
this.Sharpen(this);
Console.Write("\nSharpening...\n");
}
m_charsUsed++; // used up a character
}
Console.WriteLine();
}
public void OnSharpened()
{
this.m_charsUsed = 0;
}
#endregion
#region IPencilSharpener Members
public void Sharpen(IPencil pencil)
{
if (pencil is MechanicalPencil)
{
pencil.OnSharpened();
}
}
#endregion
}
An even better way is to make sure our MechanicalPencil can only sharpen itself by modifying the "Sharpen()" method:
public void Sharpen(IPencil pencil)
{
if (pencil.Equals(this))
{
pencil.OnSharpened();
}
}
In Summary
As you saw in this article, interfaced based programming is a great way to ensure maintainability, reusability and flexibility in your code. It does take a bit more discipline to carefully define interfaces early in the development cycle, but the payoffs are more than worth it. If the interfaces are kept clear and concise they will not only help other developers understand our code but will also ensure a more stable and flexible code base. Give interfaced-based development a try in your next project, if you follow the basic rules of keeping it simple, you won't regret it.
Until next time,
Happy coding