Beyond IS_A
and HAS_A:
The C# interface
Creating your own interface or using one provided by .NET
Unifying separate class hierarchies with interfaces
Hiding part of your class's public interface behind an interface
Managing software change — flexibility via interfaces
A class can contain a reference to another class; this statement describes the simple HAS_A
relationship. One class can extend another class by way of the marvel of inheritance — that's the IS_A
relationship. The C# interface implements another, equally important association: the CAN_BE_USED_AS
relationship.
This chapter introduces C# interfaces and shows some of the numerous ways they increase the power and flexibility of object-oriented programming.
If you want to jot a note, you can scribble it with a pen, type it into your smartphone, or pound it out on your laptop's keyboard. You can fairly say that all three objects — pen, smartphone, and computer — implement the TakeANote
operation. Suppose that you use the magic of inheritance to implement this concept in C#:
abstract class ThingsThatRecord // The base class { abstract public void TakeANote(string note); } public class Pen : ThingsThatRecord // A subclass { override public void TakeANote(string note) { // ... scribble a note with a pen ... }
} public class PDA : ThingsThatRecord // Another subclass { override public void TakeANote(string note) { // ... stroke a note on the PDA ... } } public class LapTop : ThingsThatRecord // A third subclass { override public void TakeANote(string note) { // ... tap, tap, tap ... } }
If the term abstract has you stumped, see Chapter 7 of this minibook and read the discussion later in this chapter. If the whole concept of inheritance is a mystery, check out Chapter 6 of this minibook.
The following simple method shows the inheritance approach working just fine:
void RecordTask(ThingsThatRecord
recorder)// Parameter type is base class.
{ // All classes that extend ThingsThatRecord have a TakeANote method. recorder.TakeANote("Shopping list"); // ... and so on. }
The parameter type is ThingsThatRecord
, so you can pass any subclasses to this method, making the method quite general.
That might seem like a good solution, but it has two big drawbacks:
A fundamental problem: Do Pen, PDA
, and LapTop
truly have an IS_A relationship? Are those three items all the same type in real life? I don't think so, do you? All I can say is that ThingsThatRecord
makes a poor base class here.
A purely technical problem: You might reasonably derive both LapTop
and PDA
as subclasses of Computer
. But nobody would say that a Pen
IS_A Computer
. You have to characterize a pen as a type of MechanicalWritingDevice
or DeviceThatStainsYourShirt
. But a C# class can't inherit from two different base classes at the same time — a C# class can be only one type of item.
So the Pen, PDA
, and LapTop
classes have in common only the characteristic that they CAN_BE_USED_AS
recording devices. Inheritance doesn't apply.
An interface in C# resembles a class with no data members and nothing but abstract methods, almost like an abstract class — almost:
interface IRecordable { void TakeANote(string note); }
The interface begins with the interface
keyword. It contains nothing but abstract methods. It has no data members and no implemented methods.
Interfaces can contain a few other features, including properties (covered in Chapter 5 of this minibook), events (covered in Chapter 9 of this minibook), and indexers (covered at the end of Book I).
Among the elements that a C# interface cannot exhibit are
All members of a C# interface are public (you can't even mention access specifiers in defining interface methods), and a C# interface isn't involved in normal inheritance; hence, it has none of those keywords. (An interface itself can be public, protected, internal
, or private
.)
Unlike an abstract class, a C# interface isn't a class. It can't be subclassed, and none of the methods it contains can have bodies.
To put a C# interface to use, you implement it with one or more classes. The class heading looks like this:
class Pen : IRecordable // Looks like inheritance, but isn't
A C# interface specifies that classes which implement the interface must provide specific implementations. They must. For example, any class that implements the IRecordable
interface must provide an implementation for the TakeANote
method. The method that implements TakeANote
doesn't use the override
keyword. Using an interface isn't like overriding a virtual method in classes.
Class Pen
might look like this:
class Pen : IRecordable { public void TakeANote(string note) // Interface method implementations { // MUST be declared public. // ... scribble a note with a pen ... } }
This example fulfills two requirements: Note that the class implements IRecordable
, and provide a method implementation for TakeANote()
.
The syntax indicating that a class inherits a base class, such as ThingsThatRecord
, is essentially no different from the syntax indicating that the class implements a C# interface such as IRecordable
:
public class PDA : ThingsThatRecord ... public class PDA : IRecordable ...
Visual Studio can help you implement an interface. Hover the mouse pointer over the interface name in the class heading. A little underline appears underneath the first character of the interface name. Move the mouse until a menu opens and choose Implement Interface <name>. Presto! A skeleton framework appears — you fill in the details.
The .NET naming convention for interfaces precedes the name with the letter I. Interface names are typically adjectives, such as IRecordable
.
The bottom line of interfaces is that an interface describes a capability, such as Swim Safety Training or Class A Driver's License. As a class, I earn my IRecordable
badge when I implement the TakeANote
ability.
More than that, an interface is a contract. If you agree to implement every method defined in the interface, you get to claim its capability. Not only that, but a client using your class in her program is guaranteed to be able to call those methods. Implementing an interface is a promise — enforced by the compiler. (Enforcing promises through the compiler reduces errors.)
Unlike some languages, such as C++, C# doesn't allow multiple inheritance — a class inheriting from two or more base classes. Think of class HouseBoat
inheriting from House
and Boat
. Just don't think of it in C#.
But although a class can inherit from only one base class, it can in addition implement as many interfaces as needed. After I treated recordability as an interface, a couple of the recording devices looked like this:
public class Pen : IRecordable // Base class is Object.
{
public void TakeANote(string note)
{
// Record the note with a pen.
}
}
public class PDA : ElectronicDevice, IRecordable
{
public void TakeANote(string note)
{
// Record the note with your thumbs or a stylus.
}
}
Class PDA
inherits from a base class and implements an interface.
To begin to see the usefulness of an interface such as IRecordable
, consider this example:
public class Program { static public void RecordShoppingList(IRecordable recorder
) { // Jot it down, using whatever device was passed in.recorder.TakeANote
(...); } public static void Main(string[] args) { PDA pda = new PDA(); RecordShoppingList(pda); // Oops, battery's low ... RecordShoppingList(pen); } }
The IRecordable
parameter is an instance of any class that implements the IRecordable
interface. RecordShoppingList()
makes no assumptions about the exact type of recording object. Whether the device is a PDA
or a type of ElectronicDevice
isn't important, as long as the device can record a note.
That concept is immensely powerful because it lets the RecordShopping List()
method be highly general — and thus possibly reusable in other programs. The method is even more general than using a base class such as ElectronicDevice
for the argument type, because the interface lets you pass almost arbitrary objects that don't necessarily have anything in common other than implementing the interface. They don't even have to come from the same class hierarchy, which truly simplifies the designing of hierarchies, for example.
Overworked word alert: Programmers use the term interface in more than one way. You can see the C# keyword interface
and how it's used. People also talk about a class's public interface, or the public methods and properties that it exposes to the outside world. I keep the distinction clear by saying C# interface most of the time when that's what I mean, and saying public interface when I refer to a class's set of public methods.
C# structures can implement interfaces just as classes can.
In addition to your being able to use a C# interface for a parameter type, an interface is useful as
A method return type
The base type of a highly general array or collection
A more general kind of object reference for variable types
I explain the advantage of using a C# interface as a method parameter type in the previous section. Now I can tell you about other interfaces.
I like to farm out to a factory method the task of creating the key objects I need. Suppose that I have a variable like this one:
IRecordable recorder = null; // Yes, you can have interface-type variables.
Somewhere, maybe in my constructor, I call a factory method to deliver a particular kind of IRecordable
object:
recorder = MyClass.CreateRecorder("Pen"); // A factory method is often static.
where CreateRecorder()
is a method, often on the same class, that returns not a reference to a Pen
but, rather, an IRecordable
reference:
static IRecordable
CreateRecorder(string recorderType)
{
if(recorderType == "Pen") return new Pen();
...
}
I say more about the factory idea later in this chapter. But note that the return type for CreateRecorder()
is an interface type.
Suppose that you have two classes, Animal
and Robot
, and that both are abstract. You want to set up an array to hold both thisCat
(an Animal
) and thatRobot
(a cute droid). The only way is to fall back on type Object
, the ultimate base class in C#, and the only base class that's common to both Animal
and Robot
as well as to their subclasses:
object[] things = new object[] { thisCat, thatRobot };
That's poor design for lots of reasons. But suppose that you're focused on the objects' movements. You can have each class implement an IMovable
interface:
interface IMovable { void Move(int direction, int speed, int distance); }
and then set up an array of IMovables
to manipulate your otherwise incompatible objects:
IMovable[]
movables = { thisCat, thatRobot };
The interface gives you a commonality that you can exploit in collections.
The following variable declaration refers to a specific, physical, concrete object (see the later section "Abstract or concrete: When to use an abstract class and when to use an interface"):
Cat thisCat = new Cat();
One alternative is to use a C# interface for the reference:
IMovable
thisMovableCat = (IMovable)new Cat(); // Note the required cast.
Now you can put any object into the variable that implements IMovable
. This practice has wide, powerful uses in object-oriented programming, as you can see later in this chapter.
Because interfaces are extremely useful, you find more interfaces in the .NET class library than gun racks at an NRA convention. I counted dozens in Help before I got tired and stopped. Among the dozen or more interfaces in the System
namespace alone are IComparable, IComparable<T>, IDisposable
, and IFormattable
. The System.Collections.Generics
namespace includes IEnumerable<T>, IList<T>, ICollection<T>
, and IDictionary<TKey, TValue>
. And there are many more. Those with the <T>
notation are generic interfaces. I explain the <T>
notation in the discussion of collection classes in Book I, Chapter 6.
The Help files show all the ISomething<T>
types with little tick marks added (IList`1
), but look for "IList<T>" in the Help index.
Two interfaces that are commonly used are IComparable
and IEnumerable
— largely superseded now by their generic versions IComparable<T>
(read as "IComparable of T") and IEnumerable<T>
.
I show you the IComparable<T>
interface in this chapter. It makes possible a comparison of all sorts of objects, such as Student
s, to each other, and enables the Sort()
method that all arrays and most collections supply. IEnumerable<T>
makes the powerful foreach
loop work — most collections implement IEnumerable<T>
, so you can iterate the collections with foreach
. You can find an additional major use for IEnumerable<T>
in Book I, as the basis for the new C# 3.0 query expressions.
The following SortInterface
program is a special offer. The capabilities brought to you by two different interfaces cannot be matched in any inheritance relationship. Interface implementations are standing by.
However, I want to break the SortInterface
program into sections to demonstrate various principles. (Pfft! As though I have any principles. I just want to make sure that you can see exactly how the program works.)
The following IDisplayable
interface is satisfied by any class that contains a Display()
method (and declares that it implements IDisplayable
, of course). Display()
returns a string representation of the object that can be displayed using WriteLine()
.
// IDisplayable -– Any object that implements the Display() method interface IDisplayable { // Return a description of yourself. string Display(); }
The following Student
class implements IDisplayable
:
class Student :IDisplayable
{ public Student(string name, double grade) { Name = name; Grade = grade; } public string Name { get; private set; } public double Grade { get; private set; } public stringDisplay()
{ string padName = Name.PadRight(9); return String.Format("{0}: {1:N0}", padName, Grade); } }
Display()
uses String
's PadRight()
and Format()
methods, covered in Book I, Chapter 3, to return a neatly formatted string.
The following DisplayArray()
method takes an array of any objects that implement the IDisplayable
interface. Each of those objects is guaranteed (by the interface) to have its own Display()
method (the entire program appears in the later section "Putting it all together"):
// DisplayArray -– Display an array of objects that implement // the IDisplayable interface. public static void DisplayArray(IDisplayable[]
displayables) { foreach(IDisplayable
disp in displayables) { Console.WriteLine("{0}, disp.Display()); } }
The following example shows the output from DisplayArray()
:
Homer : 0 Marge : 85 Bart : 50 Lisa : 100 Maggie : 30
C# defines the interface IComparable<T>
this way:
interface IComparable<T> { // Compare the current T object to the object 'item'; return a // 1 if larger, −1 if smaller, and 0 if the same. int CompareTo(T item); }
A class implements the IComparable<T>
interface by implementing a CompareTo()
method. Notice that CompareTo()
takes an argument of type T
, a type you supply when you instantiate the interface for a particular data type — as in this example:
class SoAndSo : IComparable<SoAndSo
> // Make me comparable.
When you implement IComparable<T>
for your class, its CompareTo()
method should return 0 if the two items (of your class type) being compared are "equal" in a way that you define. If not, it should return 1 or −1, depending on which object is "greater."
It seems a little Darwinian, but you could say that one Student
object is "greater than" another Student
object if his grade-point average is higher. (Okay, either a better student or a better apple-polisher — it doesn't matter.)
Implementing the CompareTo()
method implies that the objects have a sorting order. If one student is "greater than" another, you must be able to sort the students from "least" to "greatest." In fact, most collection classes (including arrays but not dictionaries) supply a Sort()
method something like this:
void Sort(IComparable<T>[]
objects);
This method sorts a collection of objects that implement the IComparable <T>
interface. It doesn't even matter which class the objects belong to. For example, they could even be Student
objects. Collection classes such as arrays or List<T>
could even sort this version of Student
:
// Student -- Description of a student with name and grade class Student :IComparable<Student>
, IDisplayable// Instantiation
{ // Constructor -- initialize a new student object. public Student(double grade) { Grade = grade; } public double Grade { get; private set; } // Implement the IComparable<T> interface: // CompareTo -- Compare another object (in this case, Student objects) and // decide which one comes after the other in the sorted array. public intCompareTo
(Student
rightStudent) { // Compare the current Student (call her 'left') against the other // student (call her 'right'). Student leftStudent = this; // Now generate a −1, 0 or 1 based on the Sort criteria (the student's // grade). I could use class Double's CompareTo() method instead). if (rightStudent.Grade < leftStudent.Grade) { return −1; }
if (rightStudent.Grade > leftStudent.Grade) { return 1; } return 0; } }
Sorting an array of Student
s is reduced to this single call:
void MyMethod(Student[] students) // Where Student implements IComparable<T>
{
Array.Sort(students);
// Sort array of IComparable<Student>s
}
You provide the comparator (CompareTo()
), and Array
does all the work — sounds fair to me.
This is the moment you've been waiting for: the complete SortInterface
program that uses the features described earlier in this chapter:
// SortInterface -- Demonstrates how the interface concept can be used // to provide an enhanced degree of flexibility in factoring // and implementing classes. using System; namespace SortInterface { // IDisplayable -- An object that can convert itself into a displayable // string format (duplicates what you can do by overriding // ToString(), but helps me make a point)interface IDisplayable
{
// Display -- return a string representation of yourself.
string Display();
}
class Program { public static void Main(string[] args) { // Sort students by grade... Console.WriteLine("Sorting the list of students"); // Get an unsorted array of students. Student[] students = Student.CreateStudentList(); // Use the IComparable<T> interface to sort the array.Array.Sort(students);
// Now the IDisplayable interface to display the results DisplayArray(students); // Now sort an array of birds by name using the same routines even // though the classes Bird and Student have no common base class. Console.WriteLine(" sorting the list of birds"); Bird[] birds = Bird.CreateBirdList(); // Notice that it's not necessary to cast the objects explicitly // to an array of IDisplayables (and wasn't for Students, either) ...
Array.Sort(birds);
DisplayArray(birds); // Wait for user to acknowledge the results. Console.WriteLine("Press Enter to terminate..."); Console.Read(); } // DisplayArray -- Display an array of objects that // implement the IDisplayable interface. public static void DisplayArray(IDisplayable[] displayables
) { foreach(IDisplayable displayable in displayables) { Console.WriteLine("{0}", displayable.Display()); } } } // ----------- Students -- Sort students by grade ------- // Student -- Description of a student with name and gradeclass Student : IComparable<Student>, IDisplayable
{ // Constructor -- Initialize a new student object. public Student(string name, double grade) { Name = Name; Grade = grade; } // CreateStudentList -- To save space here, just create // a fixed list of students. static string[] names = {"Homer", "Marge", "Bart", "Lisa", "Maggie"}; static double[] grades = {0, 85, 50, 100, 30}; public static Student[] CreateStudentList() { Student[] students = new Student[names.Length]; for (int i = 0; i < names.Length; i++) { students[i] = new Student(names[i], grades[i]); } return students; } // Access read-only properties. public string Name { get; private set; } public double Grade { get; private set; } // Implement the IComparable interface: // CompareTo -- Compare another object (in this case, Student objects) // and decide which one comes after the other in the sorted array.public int CompareTo(Student rightStudent)
{ // Compare the current Student (call her 'left') against // the other student (call her 'right'). Student leftStudent = this; // Now generate a −1, 0 or 1 based on the Sort criteria (the student's // grade). Double's CompareTo() method would work, too. if (rightStudent.Grade < leftStudent.Grade) { return −1; } if (rightStudent.Grade > leftStudent.Grade) { return 1; } return 0; } // Display -- Implement the IDisplayable interface:
public string Display()
{ string padName = Name.PadRight(9); return String.Format("{0}: {1:N0}", padName, Grade); } } // -----------Birds -- Sort birds by their names-------- // Bird -- Just an array of bird names.class Bird : IComparable<Bird>, IDisplayable
{ // Constructor -- initialize a new Bird object. public Bird(string name) { Name = name; } // CreateBirdList -- Return a list of birds to the caller; // Use a canned list here to save space. static string[] birdNames = { "Oriole", "Hawk", "Robin", "Cardinal", "Bluejay", "Finch", "Sparrow"}; public static Bird[] CreateBirdList() { Bird[] birds = new Bird[birdNames.Length]; for(int i = 0; i < birds.Length; i++) { birds[i] = new Bird(birdNames[i]); } return birds; } public string Name { get; private set; } // Implement the IComparable interface: // CompareTo -- Compare the birds by name; use the // built-in String class compare method.public int CompareTo(Bird rightBird)
{ // Compare the "current" bird to the "right hand object" bird. Bird leftBird = this; return String.Compare(leftBird.Name, rightBird.Name); } // Display -- Implement the IDisplayable interface.public string Display() { return Name; }
} }
The Student
class (it's in the middle of the program listing) implements the IComparable<T>
and IDisplayable
interfaces, as described earlier. The CompareTo()
method compares the students by grade, which results in the students being sorted by grade. Student
's Display()
method returns the name and grade of the student.
The other methods of Student
include the read-only Name
and Grade
properties, a simple constructor, and a CreateStudentList()
method. This method just returns a fixed list of students for the code to work on.
The Bird
class at the bottom of the listing also implements the interfaces IComparable<T>
and IDisplayable
. The class implements CompareTo()
by comparing the names of the birds using String.Compare()
. So one bird is greater than another if its name is greater. Bird.CompareTo()
alphabetizes the list. Bird
's Display()
method just returns the name of the bird.
If you've followed along with us so far, you're set up for the good part, back in Main()
. The CreateStudentList()
method is used to return an unsorted list, which is stored in the array students
.
You might think it necessary to cast the array of students into an array of comparableObjects
so that you can pass the students to Array
's Sort()
method:
IComparable<Student>[] comparables = (IComparable<Student>[])students;
But not so, my friend. Sort()
sees that the array passed in consists of objects that implement IComparable<something>
and simply calls CompareTo()
on each Student
object to sort them. Great, eh?
The sorted array of Student
objects is then passed to the locally defined DisplayArray()
method. DisplayArray()
uses foreach
to iterate through an array of objects that implement a Display()
method (guaranteed by the objects' having implemented IDisplayable
). In the loop, it calls Display()
on each object and displays the result to the console using WriteLine()
.
The program in Main()
continues by sorting and displaying birds! I think we can agree that birds have nothing to do with students. Yet the same Sort()
and DisplayArray()
methods work on Bird
as on Student
.
The output from the program appears:
Sorting the list of students Lisa : 100 Marge : 85 Bart : 50 Maggie : 30 Homer : 0 Sorting the list of birds Bluejay Cardinal Finch Hawk Oriole Robin Sparrow Press Enter to terminate...
Figure 8-1 shows the Robot
and Animal
hierarchies. Some, but not all, of the classes in each hierarchy not only inherit from the base classes, Robot
or Animal
, but they also implement the IPet
interface (not all animals are pets, you see), as shown in the following code — I skipped lots of details:
// Two abstract base classes and one interface abstract class Animal { abstract public void Eat(string food); abstract public void Sleep(int hours); abstract public int NumberOfLegs { get; } public void Breathe() { ... } // Nonabstract, implementation not shown. } abstract class Robot { public virtual void Speak(string whatToSay) { ... } // Impl not shown abstract public void LiftObject(object o); abstract public int NumberOfLegs { get; } }interface IPet
{ void AskForStrokes(); void DoTricks(); int NumberOfLegs { get; } // Properties in interfaces look like this. string Name { get; set; } // get/set must be public in implementations. } // Cat -- This concrete class inherits (and partially implements) // class Animal and also implements interface IPet. class Cat : Animal,IPet
{ public Cat(string name) { Name = name; } // 1. Overrides and implements Animal members (not shown). // 2. Provides additional implementation for IPet. #region IPet Members public void AskForStrokes() ... public void DoTricks() ... public string Name { get; set; } // Inherits NumberOfLegs property from base class, thus meeting // IPet's requirement for a NumberOfLegs property. #endregion IPet Members public override string ToString() { return Name; } } class Cobra : Animal { // 1. Inherits or overrides all Animal methods only (not shown). } class Robozilla : Robot // Not IPet { // 1. Override Speak. public override void Speak(string whatToSay) { Console.WriteLine("DESTROY ALL HUMANS!
"); } // 2. Implement LiftObject and NumberOfLegs, not all shown. public override void LiftObject(object o) ... public override int NumberOfLegs { get { return 2; } } } class RoboCat : Robot, IPet { public RoboCat(string name) { Name = name; } // 1. Override some Robot members, not all shown: #region IPet Members public void AskForStrokes() ... public void DoTricks() ... public string Name { get; set; } #endregion IPet Members }
(Notice the properties in IPet
— that's how you specify properties in interfaces. If you need both getter and setter, just add set;
after get;
.)
I've shown you two concrete classes that inherit from Animal
and two that inherit from Robot
. However, you can see that neither class Cobra
nor class Robozilla
implements IPet
— probably for good reasons. I have no plans to watch TV with my pet cobra beside me on the couch, and a robozilla sounds nasty too. Some of the classes in both hierarchies exhibit what you might call "petness" and some don't.
The InterfacesBridgingHierarchies
example on this book's Web site puts these items through their paces.
The point of this section is that any class can implement an interface, as long as it provides the right methods and properties. Robotcat
and Robodog
can carry out the AskForStrokes()
and DoTricks()
actions and have the NumberOfLegs
property, as can Cat
and Dog
in the Animal
hierarchy — all while other classes in the same hierarchies don't implement IPet
.
You can add support for an interface to any class — but only if you're free to modify the source code.
Often in this book, I discuss code that (a) you write but (b) someone else (a client) uses in her programs (you may be the client yourself, of course). Sometimes, you have a complex or tricky class for which you would truly rather not expose the whole public interface to clients. For various reasons, it includes some dangerous operations that nonetheless have to be public. Ideally, you would expose a safe subset of your class's public methods and properties and hide the dangerous ones. C# interfaces can do that too.
Here's a different Robozilla
class, with several methods and properties that amateurs can use safely and enjoyably. But Robozilla
also has some advanced features that can be, well, scary:
public class Robozilla // Doesn't implement IPet! { public void ClimbStairs(); // Safe public void PetTheRobodog(); // Safe? Might break it. public void Charge(); // Maybe not safe public void SearchAndDestroy(); // Dangerous public void LaunchGlobalThermonuclearWar(); // Catastrophic }
You want to expose only the two safer methods while hiding the last three dangerous ones. Here's how you can do that by using a C# interface:
Design a C# interface that exposes only the safe methods:
public interface IRobozillaSafe { void ClimbStairs(); void PetTheRobodog(); }
Modify the Robozilla
class to implement the interface. Because it already has implementations for the required methods, all you need is the : IRobozillaSafe
notation on the class heading:
public class Robozilla : IRobozillaSafe
...
Now you can just keep Robozilla
itself a secret from, say, everybody except Gandhi, Martin Luther King, and Mother Theresa and give most users the IRobozillaSafe
interface. Give your clients a way to instantiate a new Robozilla
, but return to them a reference to the interface (in this example, by using a static factory method added to class Robozilla
):
// Creates a Robozilla but returns only an interface reference to it. public static IRobozillaSafe CreateRobozilla(<parameter list>) { return (IRobozillaSafe)new Robozilla(<parameter list>); }
Clients then use Robozilla
like this:
IRobozillaSafe myZilla = Robozilla.CreateRobozilla(...); myZilla.ClimbStairs(); myZilla.PetTheRobodog();
It's that simple. Using the interface, they can call the Robozilla
methods that it specifies — but not any other Robozilla
methods.
Robozilla myKillaZilla = (Robozilla)myZilla;
Doing so is usually a bad idea, though. The interface has a purpose. Bill Wagner says, "Programmers who go to that much work to create bugs get what they deserve."
In real life, programmers sometimes use this hand-out-an-interface technique with the complex DataSet
class used in ADO.NET to interact with databases. A DataSet
can return a set of database tables loaded with records — such as a table of Customers
and a table of Orders
. (Modern relational databases, such as Oracle and SQL Server, contain tables linked by various relationships. Each table contains lots of records, where each record might be, for example, the name, rank, and serial number of a Customer
.)
Unfortunately, if you hand a client a DataSet
reference (even through a read-only property's get
clause), he can easily muddle the situation by reaching into the DataSet
and modifying elements that you don't want modified. One way to prevent such mischief is to return a DataView
object, which is read-only. Alternatively, you can create a C# interface to expose a safe subset of the operations available on the DataSet
. Then you can subclass DataSet
and have the subclass (call it MyDataSet)
implement the interface. Finally, give clients a way to obtain an interface reference to a live MyDataSet
object and let them have at it in relative safety — through the interface.
You usually shouldn't return a reference to a collection, either, because it lets anyone alter the collection outside the class that created it. Remember that the reference you hand out can still point to the original collection inside your class. That's why List<T>
, for instance, provides an AsReadOnly()
method. This method returns a collection that can't be altered:
private List<string> _readWriteNames = ... // A modifiable data member ... ReadonlyCollection<string> readonlyNames = _readWriteNames.AsReadOnly(); return readonlyNames; // Safer to return this than _readWriteNames.
Although it doesn't qualify as using an interface, the purpose is the same.
The HidingBehindAnInterface
example on this book's Web site shows the Robozilla
code in this section.
A C# interface can "inherit" the methods of another interface. I use quotes around the word inherit because it's not true inheritance, no matter how it may appear. The following interface code lists a base interface, much like a base class, in its heading:
interface
IRobozillaSafe: IPet
// Base interface
{ // Methods not shown here ... }
By having IRobozillaSafe
"inherit" IPet
, you can let this subset of Robozilla
implement its own "petness" without trying to impose petness inappropriately on all of Robozilla
:
class PetRobo : Robozilla, IRobozillaSafe // (also an IPet by inheritance) { // Implement Robozilla operations. // Implement IRobozillaSafe operations, then ... // Implement IPet operations too (required by the inherited IPet interface). } ... // Hand out only a safe reference, not one to PetRobo itself. IPet myPetRobo = (IPet)new PetRobo(); // ... now call IPet methods on the object.
The IRobozillaSafe
interface inherits from IPet
. Classes that implement IRobozillaSafe
must therefore also implement IPet
to make their implementation of IRobozillaSafe
complete.
This type of inheritance isn't the same concept as class inheritance. For instance, class PetRobo
in the previous example, can have a constructor, but no equivalent of a base-class constructor exists for IRobozillaSafe
or IPet
. Interfaces don't have constructors. More important, polymorphism doesn't work the same way with interfaces. Though you can call a method of a subclass through a reference to the base class (class polymorphism), the parallel operation involving interfaces (interface polymorphism) doesn't work: You can't call a method of the derived interface (IRobozillaSafe
) through a base interface reference (IPet
).
Although interface inheritance isn't polymorphic in the same way that class inheritance is, you can pass an object of a derived interface type (IRobozillasafe
) through a parameter of its base interface type (IPet
). Therefore, you can also put IRobozillasafe
objects into a collection of IPet
objects. The PassInterface
example on this book's Web site demonstrates the ideas in this section.
Interfaces are the key to object-oriented programs that bend flexibly with the winds of change. Your code will laugh in the face of new requirements.
You've no doubt heard it said, "Change is a constant." When you hand a new program to a bunch of users, they soon start requesting changes. Add this feature, please. Fix that problem, please. The RoboWarrior has feature X, so why doesn't Robozilla? Many programs have a long shelf life — thousands of programs, especially old Fortran and Cobol programs, have been in service for 20 or 30 years or longer. They undergo lots of maintenance in that extended time span, which makes planning and designing for change one of your highest priorities.
Here's an example: In the Robot
class hierarchy, suppose that all robots can move in one way or another. Robocat
s saunter. Robozilla
s charge — at least when operated by a power (hungry) user. And Robosnake
s slither. One way to implement these different modes of travel involves inheritance: Give the base class, Robot
, an abstract Move()
method. Then each subclass overrides the Move()
method to implement it differently:
abstract public class Robot { abstract public void Move(int direction, int speed); // ... } public class Robosnake : Robot { public override void Move(int direction, int speed) { // A real Move() implementation here: slithering. ... some real code that computes angles and changes snake's location relative to a coordinate system, say ... } }
But suppose that you often receive requests to add new types of movement to existing Robot
subclasses. "Please make Robosnake
undulate rather than slither," maybe. (Don't ask me what the difference is.) Now you have to open up the Robosnake
class and modify its Move()
method directly.
After the Move()
method is working correctly for slithering, most programmers would prefer not to meddle with it. Implementing slithering is difficult, and changing the implementation can introduce brand-new bugs. If it ain't broke, don't fix it.
The code just given illustrates the problem. The StrategyExample
program on this book's Web site illustrates the solution, discussed in the next several sections. The solution has the advantage of allowing the old, slithering code to flourish for some applications while providing the new, undulating movement in newer applications. Everybody's happy.
There must be a way to implement Move()
that doesn't require you to open a can of worms every time a client wants wriggling instead. You can use interfaces, of course!
Look at the following code that uses HAS_A
, a now-familiar relationship between two classes in which one class contains the other:
public class Robot
{
// This object is used to implement motion.
protected Motor _motor = new Motor(); // Refers to Motor by name
// ...
}
internal class Motor { ... }
The point about this example is that the contained object is of type Motor
, where Motor
is a concrete object. (That is, it represents a real item, not an abstraction.) HAS_A
sets up a dependency between classes Robot
and Motor
: Robot
depends on the concrete class Motor
. A class with concrete dependencies is tightly coupled to them: When you need to replace Motor
with something else, code that depends directly on Motor
like this has to change too. Instead, insulate your code by relying only on the public interface of dependencies, which you can do with interfaces. You can depend on dependent objects in a, er, less dependent way.
Depend on abstractions, not on concrete classes. I show you how.
In Chapter 7 of this minibook, in my little discourse about birds, I say, "Every bird out there is a subtype of Bird
." In other words, a duck is an instance of a subclass Duck
. You never see an instance of Bird
itself — Bird
is an abstraction. Instead, you always see concrete, physical ducks, sparrows, or hummingbirds. Abstractions are concepts. As living creatures, ducks are real, concrete objects. And, concrete objects are instances of concrete classes. (A concrete class is a class that you can instantiate. It lacks the abstract
keyword, and it implements all methods.)
You can represent abstractions in two ways in C#: with abstract classes or with C# interfaces. The two have differences that can affect your choice of which one to use:
Use an abstract class when you can profitably share an implementation with subclasses — the abstract base class can contribute real code that its subclasses can use by inheritance. For instance, maybe class Robot
can handle part of the robot's tasks, just not movement.
An abstract class doesn't have to be completely abstract. Though it has to have at least one abstract, unimplemented method or property, some can provide implementations (bodies). Using an abstract class to provide an implementation for its subclasses to inherit prevents duplication of code. That's always a good thing.
Use an interface when you can't share any implementation or your implementing class already has a base class.
C# interfaces are purely, totally abstract. A C# interface supplies no implementation of any of its methods. Yet it can also add flexibility that isn't otherwise possible. The abstract class option may not be available because you want to add a capability to a class that already has a base class (that you can't modify). For example, class Robot
may already have a base class in a library that you didn't write and therefore can't alter. Interfaces are especially helpful for representing completely abstract capabilities, such as movability or displayability, that you want to add to multiple classes that may otherwise have nothing in common — for example, being in the same class hierarchy.
I mention earlier in this chapter that you can use interfaces as a more general reference type. The containing class can refer to the contained class not with a reference to a concrete class but, rather, with a reference to an abstraction — either an abstract class or a C# interface will work:
AbstractDependentClass dependency1 = ...; ISomeInterface dependency2 = ...;
Suppose that you have an IPropulsion
interface:
interface IPropulsion { void Movement(int direction, int speed); }
Class Robot
can contain a data member of type IPropulsion
instead of the concrete type Motor
:
public class Robot {private IPropulsion _propel;
//<--Notice the interface type here. // Somehow, you supply a concrete propulsion object at runtime ... // Other stuff and then: public void Move(int speed, int direction) { // Use whatever concrete propulsion device is installed in _propel._propel.Movement(speed, direction);
// Delegate to its methods. } }
Robot
's Move()
method delegates the real work to the object referred to through the interface. Be sure to provide a way to install a concrete Motor
or Engine
or another implementer of IPropulsion
in the data member. Programmers often install that concrete object — "inject the dependency" — by passing it to a constructor:
Robot r = new Robosnake(someConcreteMotor
); // Type IPropulsion
or by assigning it via a setter property:
r.PropulsionDevice = someConcreteMotor
; // Invokes the set clause
Another approach to dependency injection is to use a factory method (which I discuss earlier in this chapter, in the section "As a method return type," and illustrate in the section "Hiding Behind an Interface"):
IPropulsion _propel = CreatePropulsion(); // A factory method