Interface programming
Creating and using an interface is probably one of the most misused practices of
modern programming. To fully understand them is essential today because they are
used extensively by providers of developer tools and APIs.
The original intent of an interface was as the definition of a “Remote Procedure
Call” and the people that invented them were concerned with how they could
consistently invoke an action on a distant computer. They needed something that
mimicked the syntax of a method call such as those you saw in simple classes but
with the definition of how parameters might be passed and what return value
should be expected.
It is important to remember that not all computers work in the same way and, for
example; a number passed from one machine to another might be totally mixed up
if the standards for transmission were not strictly defined.
A sixteen bit integer might be
represented on machine A with the high-order bits being passed first, called
“big endian.” Conversely, machine B might store it’s numbers with the low-order
bits first, in other words, “little-endian”. The number F3A5 or 62373 would be
seen as A5F3 or 42483 when it got to the other machine.
An interface specifies all these parameters and more such that we can be
guaranteed that the procedure call will be accepted and executed no matter what
the system at the other end thinks is the right way to do things. In other
words, the interface enables us to ignore the details of the implementation.
In its purest form, an interface is a declaration of intent for how a “black
box” mechanism should work. Take a simple radio for example. It would have three
controls:
A switch to turn it on and off.
A rotary control to change the volume of the sound
A second rotary control to select the station to which you might wish to listen.
The stuff that goes on inside the radio, reception and tuning of the signal,
amplification and final output to the speaker is something that, as the user of
the equipment, you have no interest in and no need to know. The radio is just a
box that does something in a well-known way and is functionally identical to any
one of a thousand other radio designs that exist. All the other radios will
function in a similar manner and their controls will all be grossly similar to
the ones on all the others too.
The way that the simple radio controls work is in fact a sort of contract. We
know that if our radio breaks down, catches fire or just gets outmoded, we can
replace it with another one and we don’t have to do any head-scratching to
understand how the new one operates because it is functionally identical to the
old one.
An interface is a contract which guarantees a certain mode of operation and so,
once published,
an interface should never change.
This is the rule which is broken by thousands of programmers every day and which
is the cause of countless problems of incompatibility where absolute
compatibility should be guaranteed.
Understanding how an interface works.
When we design a class, we have absolute free choice as to how we implement the
methods in it. Because the world is a diverse and wonderful place, we can almost
guarantee that our class does things differently to many others that may have
been created elsewhere but which do essentially the same job. We may or may not
design our class to operate in a standard way but at some point in its lifetime
a decision may be taken to render that class compatible with a standard
interface.
Take the following class written in C#:
public class
MyRadio
{
public void
SwitchOn()
{
}
public void
SwitchOff()
{
}
public void
Volume(double level)
{
}
public void
FrequencySet(double Hertz)
{
}
}
We can safely ignore the implementation of the various methods in the class so
nothing is shown. We could also have chosen any other of hundreds of ways to
define our methods or properties. Take a moment then to imagine how the compiler
sees this class is used.
public static
void Main()
{
MyRadio mr =
new MyRadio();
//Create a new radio
mr.SwitchOn();
//Turn it on
mr.FrequencySet(191000);
//Set the frequency
mr.Volume(5);
//And the volume at half
}
When the compiler sees the invocation of the various methods, such as SwitchOn,
FrequencySet and Volume it uses a table in the class to find the correct address
to call and so the program which uses the radio is able to call the correct
program addresses.
Now we’ll imagine that the world’s most important online music company publishes
an interface that enables you to add your radio to their player. They have no
knowledge of your radio and they don’t care. The interface they publish is
called IRadio and looks like this:
public interface
iRadio
{
bool Power { get;
set; } //Used to set
or get the power state of the radio
void Volume(int
v); //Set the playback volume in percentages of
maximum where 0=silent and 100=full volume
void Frequency(double
f); //Set the frequency in kilohertz, eg. 191 for
191000 hertz
}
Now, to be able to participate in their online entertainment system and earn
cash by having your users use their services all you need to do is implement
their interface like so:
public class
MyRadio : IRadio
{
public void
SwitchOn()
{
}
public void
SwitchOff()
{
}
public void
Volume(double level)
{
}
public void
FrequencySet(double Hertz)
{
}
bool powerState;
public bool
Power
{
get
{
return powerState;
}
set
{
powerState = value;
if (value)
SwitchOn();
else
SwitchOff();
}
}
public void
Volume(int v)
{
Volume(1.0 / 100 * v);
}
public void
Frequency(double f)
{
FrequencySet(f * 1000);
}
}
Now, your radio is compatible with their radio specification and will work with
their systems earning you piles of cash.
The compiler effectively sees two sets of method addresses on the same class.
The ones provided by the class in its public declaration and the one added to
the class by the interface definition.
Dangers of modifying interfaces
One day your support department begins to get calls that your radio is useless
and doesn’t work. They will never use your lousy services again and you can go
poke your software where the sun doesn’t shine and oh, by the way, I want my
money back! This is puzzling because you haven’t changed a thing.
A quick diagnostic shows that the world’s biggest online music corporation hired
a novice programmer who worked hard to “improve” their radio system and
changed the IRadio interface.
As a result, your radio is incompatible, as are the radios of all the companies
that participate in the online player so there are a ton of unhappy users and
customers this morning all because the IRadio interface was modified. Here’s the
modification:
public interface
IRadio
{
bool Power { get;
set; } //Used to set
or get the power state of the radio
void Volume(int
v); //Set the playback volume in percentages of
maximum where 0=silent and 100=full volume
void ScanUpToNextStation();
void ScanDownToNextStation();
}
As you can clearly see, this is an IRadio interface but it really isn’t
the IRadio interface so now, not a single radio player on the system is
working.
Needless to say, the novice programmer is roundly chastised with good humour and
the more mature programmers tell the little wag very kindly that the right way
to do it was to add another interface
public interface
IRadioScanControls
{
void ScanUpToNextStation();
void ScanDownToNextStation();
}
Given this new interface you can still participate in the IRadio scheme with
total compatibility. You could, if you wish simply leave your software as it was
and not bother with the new controls. However, you can also implement the new
interface and add the new functionality because adding that interface definition
does nothing more drastic than add another set of method indirections to the
class which the compiler can find if it needs to.
public class
MyRadio : IRadio,
IRadioScanControls
{
public void
SwitchOn()
{
}
public void
SwitchOff()
{
}
public void
Volume(double level)
{
}
public void
FrequencySet(double Hertz)
{
}
bool powerState;
public bool
Power
{
get
{
return powerState;
}
set
{
powerState = value;
if (value)
SwitchOn();
else
SwitchOff();
}
}
public void
Volume(int v)
{
Volume(1.0 / 100 * v);
}
public void
Frequency(double f)
{
FrequencySet(f * 1000);
}
public void
ScanUpToNextStation()
{
//Do up scanning here...
}
public void
ScanDownToNextStation()
{
//Do down scanning here...
}
}
Summary
An interface is an UNBREAKABLE CONTRACT OF COMPATIBILITY between two systems.
Once published, an interface must never change.
An interface adds a new table of method or property indirections to a class. We
can add as many such tables as we like.
Some languages allow one interface to inherit from another in the same way that
classes can inherit from bases. Tough this is used often, you must understand
that inheriting an interface forces the class adopting the interface to fully
implement all methods and properties of all inherited interfaces. This can
become an unnecessarily hefty task and simply defining a new and separate
interface is usually the best option. An example of utterly horrible interface
inheritance is the .Net IBindingList interface which inherits several others and
is an utter pain to use.
|