Introduction
Frequently, I encounter practices in software development that defy logical explanation. One of the more common practices is the misuse of interfaces. In this article, I describe the basic purpose for the interface type construct and explore the question of when to use them.
I would like to preface this article by stating that there are exceptions to all of the cases that I will describe, and that some developers may disagree on some points. However, I believe that my methods result in code that is easier to follow, easier to maintain, and avoid increasing the required effort when doing so does not provide a measurable benefit.
What Is An Interface?
An interface is nothing more than an agreement of how two pieces of code will interact. One piece of code implements the interface and another uses the interface. The interface acts as a decoupling layer that enables the replacement of the object implementing the interface.
To illustrate this point, let’s examine a simple interface usage.
public interface IAdder
{
int Add(int a, int b);
}
public class Example
{
public IAdder Adder
{
get;
set;
}
public void DoSomething()
{
var two = Adder.Add(1, 1);
}
}
In the above example, DoSomething
uses an adder to add two numbers, but the Example
class has no knowledge of the implementation of the adder.
Actions/Services
Classes that provide an action or a service are very often candidates for interface abstraction. The classic example is a WCF service. WCF services define service contracts, and communication is performed via the interface without any client dependency on the service implementation.
Let’s look at a variation of the adder interface, and assume the same usage as above.
public interface IAdder
{
double Add(double a, double b);
}
public class DoubleAdder : IAdder
{
public double Add(double a, double b)
{
return a + b;
}
}
public class IntAdder : IAdder
{
public double Add(double a, double b)
{
return (int)a + (int)b;
}
}
The usefulness of defining the interface is obvious when we have a variety of implementations of the action that can be used by a single unchanged piece of code, where the only requirement is that the code be provided with an instance of the desired implementation.
Data Structures
Classes that represent data are unlikely to be candidates for interface abstraction. The reasoning behind this is that the actual structure of the data may be as important to using the data as the members of the data structure. An example of this point is seen when using WCF data contracts, where the serialized form of the data is critical to being able to use the data.
Again, we will modify the adder interface to illustrate this point.
public class Number { }
public interface IAdder
{
Number Add(Number a, Number b);
}
In this case, the Number
is the data passed as arguments to the adder. In essence, the Number
class is as much a part of the adder interface as the Add
method itself. Although a number interface could be used, the benefit of doing so is unclear, and the ability to remotely access the adder may be compromised. Would there really ever be a need to replace the number with a different implementation of the underlying data?
Internal Details
Classes that are used internally to some activity but that are not exposed publicly from the assembly are unlikely to be candidates for interface abstraction. Although there are definitely exceptions to this rule, the classic case of an internal class does not benefit from interface abstraction.
The classic case is illustrated below in an implementation of the adder interface’s method.
public class DoubleAdder : IAdder
{
public double Add(double a, double b)
{
return new MyCalculator().Add(a, b);
}
}
internal class MyCalculator
{
internal double Add(double a, double b)
{
return a + b;
}
}
Here, we can see that the internal MyCalculator
class is used to contain the logic, and the DoubleAdder
class is simply an adapter to the internal calculator implementation. Adding an interface to the internal class would not provide any benefit, and would increase the necessary effort to maintain the class if the internal method signatures were to change.
History
- 27th January, 2010: Initial post