When Trying To Build Maintainable
When Trying To Build Maintainable
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.
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.
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:
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;
Here is one possible way to make the change without breaking the interface by adding a "Message"
property to the class.
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; }
}
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?
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.
#endregion
#endregion
Console.WriteLine();
}
#endregion
}
#endregion
}
if (!p.IsSharp)
{
PencilSharpener sharpener = new PencilSharpener();
sharpener.Sharpen(p);
}
}
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.
#endregion
#endregion
#endregion
}
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.
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.
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?
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.