Chapter 8. Interfacing with the Interface

In This Chapter

  • 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.

Introducing CAN_BE_USED_AS

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 ...
  }
}

Tip

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.

Knowing What an Interface Is

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.

Note

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

  • Access specifiers, such as public or private (see Chapter 5 of this minibook)

  • Keywords such as virtual, override, or abstract (see Chapter 7 of this minibook)

  • Data members (see Chapter 2 of this minibook)

  • Implemented methods — nonabstract methods with bodies

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.

How to implement an interface

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

Note

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 ...

Tip

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.

How to name your interface

The .NET naming convention for interfaces precedes the name with the letter I. Interface names are typically adjectives, such as IRecordable.

Why C# includes interfaces

Note

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.)

Mixing inheritance and interface implementation

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.

And he-e-e-re's the payoff

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.

Note

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.

Tip

C# structures can implement interfaces just as classes can.

Using an Interface

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.

As a method return type

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.

As the base type of an array or collection

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.

As a more general type of object reference

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.

Using the C# Predefined Interface Types

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.

Tip

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 Students, 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.

Looking at a Program That CAN_BE_USED_AS an Example

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.)

Creating your own interface at home in your spare time

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 string Display()
  {
    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

Implementing the incomparable IComparable<T> interface

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 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). 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 Students 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.

Putting it all together

This is the moment you've been waiting for: the complete SortInterface program that uses the features described earlier in this chapter:

Note

// 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 grade
  class 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.

Getting back to the Main() event

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...

Unifying Class Hierarchies

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;.)

A tale of two class hierarchies and one interface.

Figure 8-1. A tale of two class hierarchies and one interface.

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.

Note

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.

Note

You can add support for an interface to any class — but only if you're free to modify the source code.

Hiding Behind an Interface

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:

  1. Design a C# interface that exposes only the safe methods:

    public interface IRobozillaSafe
    {
      void ClimbStairs();
      void PetTheRobodog();
    }
  2. 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.

Note

Programmers (I think I can guess which ones) can defeat my little ploy with a simple cast:

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.

Tip

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.

Note

The HidingBehindAnInterface example on this book's Web site shows the Robozilla code in this section.

Inheriting an Interface

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).

Note

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.

Using Interfaces to Manage Change in Object-Oriented Programs

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.

Note

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. Robocats saunter. Robozillas charge — at least when operated by a power (hungry) user. And Robosnakes 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.

Note

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.

Note

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.

Making flexible dependencies through interfaces

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.

Note

Depend on abstractions, not on concrete classes. I show you how.

Abstract or concrete: When to use an abstract class and when to use an interface

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.)

Note

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.

Doing HAS_A with interfaces

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
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.59.69.53