Recall that a class, as a type, is an abstraction of a real-world object from which some of the unessential details have been omitted. We can therefore see that an abstract class is more of an abstraction than a concrete class, because with an abstract class we've omitted the details for how one or more particular behaviors are to be performed.
Now, let's take the notion of abstractness one step further. With an abstract class, we are able to avoid programming the bodies of methods that are declared to be abstract. But what about the attributes of such a class? In our Course example, we went ahead and prescribed the data structure (attributes) that we thought would be needed generically by all types of courses:
string courseName; string courseNumber; int creditValue; Collection enrolledStudents; Professor instructor;
But, what if we only wanted to specify common behaviors, and not even bother with declaring attributes? Attributes are, after all, typically declared to be private; we simply may not wish to mandate what data structure a future derived class must use in order to achieve the desired public behaviors, instead leaving it up to the designer of that class to ultimately decide.
Say, for example, that we wanted to define what it means to teach at a university. Perhaps, in order to teach, an object would need to be able to perform the following services:
Agree to teach a particular course.
Designate a textbook to be used for the course.
Define a syllabus for the course.
Approve the enrollment of a particular student in the course.
Each of these behaviors could be formalized by specifying a method header, representing how an object that is capable of teaching would be asked to perform each behavior:
public bool AgreeToTeach(Course c) public void DesignateTextbook(TextBook b, Course c) public Syllabus DefineSyllabus(Course c) public bool ApproveEnrollment(Student s, Course c)
A set of method headers such as these, which collectively define what it means to assume a certain role within an application (such as teaching), is known as an interface. Interfaces, like classes, are given names; so, let's call this the ITeacher interface. (The C# convention is for interface names to use the Pascal capitalization style; names are prefixed with the letter "I" to indicate that the type is an interface vs. a class.)
To declare an interface, we enclose the method headers that we've defined for the interface, each ending with a semicolon (;), in a set of braces, with the keyword interface and the name of the interface preceding the opening brace, as illustrated here:
public interface ITeacher { bool AgreeToTeach(Course c); void DesignateTextbook(TextBook b, Course c); Syllabus DefineSyllabus(Course c); bool ApproveEnrollment(Student s, Course c); }
An interface can be given public or internal access and is typically declared to be public. As with classes, each interface typically goes into its own source code file, whose name matches the name of the interface contained within: e.g., the ITeacher interface would go into a file named ITeacher.cs.
All of an interface's method headers are implicitly public and abstract, so we needn't specify either of those two keywords when declaring them; in fact, if we were to explicitly try to assign public access to an interface method header:
public interface ITeacher { public bool AgreeToTeach(Course c); // etc.
the compiler would generate an error:
error CS0106: the modifier 'public' is not valid for this item
A similar compiler error is generated if the abstract keyword is applied to an interface method header by mistake.
Once we've defined an interface such as ITeacher, we can set about designating various classes as being teachers—for example, Professors, or Students, or generic Person objects—simply by declaring that the class of interest implements the ITeacher interface using the syntax shown here:
// Implementing an interface ... public class Professor : ITeacher { // details omitted ... }
Note that the syntax for declaring that a class is implementing an interface is indistinguishable from the syntax that indicates inheritance—a colon followed by the interface/base class name:
// Extending a class via inheritance ... public class Professor : Person { // details omitted ... }
Note?/td> |
The only way to differentiate an interface (ITeacher) from a class (Person) when inspecting the derived/implementing class's code (Professor) is the fact that an interface name typically begins with an "I" followed by another capital letter: e.g., IFoo or IBar would be interfaces, whereas IceCream or Igloo would be class names. |
Once a class declares that it is implementing an interface:
public class Professor : ITeacher { ...
the implementing class must provide concrete versions of all of the (implicitly abstract) methods declared by the interface in question in order to satisfy the compiler. As an example, let's say that we were to code the Professor class as shown in the following code, implementing three of the four methods called for by the ITeacher interface but neglecting to code the fourth method:
public class Professor : ITeacher { private string name; private string employeeId; // etc. // Properties defined; details omitted. // We implement three of the four methods called for by the // ITeacher interface, to provide method bodies. public bool AgreeToTeach(Course c) { // Logic for the method body goes here; details omitted. } public void DesignateTextbook(TextBook b, Course c) { // Logic for the method body goes here; details omitted. } public Syllabus DefineSyllabus(Course c) { // Logic for the method body goes here; details omitted. } // Note that we've failed to provide an implementation of the // ApproveEnrollment method ... }
If we were to try to compile this class as shown previously, we'd get the following compiler error:
error CS0535: 'Professor' does not implement interface member 'ITeacher.ApproveEnrollment(Student, Course)'
You learned in Chapter 5 that inheritance is thought of as the "is a" relationship. As it turns out, implementing an interface is another form of "is a" relationship:
If the Professor class extends the Person class, then a professor is a person; and
If the Professor class implements the ITeacher interface, then a professor is a teacher.
Also, when a class A implements an interface X, all of the classes that are derived from A may also be said to implement that same interface. For example, if we derive a class called AdjunctProfessor from Professor, then if Professor implements the ITeacher interface, an adjunct professor is a teacher.
public class Professor : ITeacher { // All methods required by ITeacher will be implemented by this class ... // details omitted. } public class AdjunctProfessor : Professor { // All methods required by ITeacher will be, at a minimum, inherited from // Professor ... details omitted. }
This makes intuitive sense, because AdjunctProfessor will either inherit all of the methods called for by the ITeacher interface from Professor, or will override them; but, either way, an AdjunctProfessor will be "equipped" to perform all of the services required of an (I)Teacher.
Implementing an interface is conceptually similar to having to "flesh out" abstract methods when extending an abstract class. What are the differences, then, between implementing an interface vs. extending an abstract class? Why might we wish to use one approach over the other when designing an application?
With an interface, we specify abstract behaviors only, whereas an abstract class often specifies a "concrete" data structure (attributes) as well as a mixture of abstract and concrete behaviors. So, in terms of the "abstractness spectrum," an interface is more abstract than an abstract class (which is in turn more abstract than a concrete class) because an interface leaves even more details to the imagination.
Note?/td> |
Note that an abstract class can be merely a set of abstract method headers if we wish to design it as such; for example: public abstract class Person { // We are purposely declaring NO ATTRIBUTES for this class ... // ... and ALL of our methods are abstract. public abstract void Print(); public abstract double ComputeSalary(); // etc. } However, in such a situation, the preferred approach would be to simply declare an interface instead. |
When a nonabstract class is derived from an abstract class, the derived class provides a concrete implementation of abstract methods declared in the abstract class by overriding them. The derived class method headers therefore must include the override keyword.
When a class implements an interface, the implementing class must once again provide a concrete implementation of all of the methods declared in the interface. However, the implementing class doesn't override them. Rather, we're defining the methods for the first time from scratch, and so the override keyword isn't included in the implementing class method headers. (If we were to try to apply the override keyword, the compiler would inform us that it has "found no suitable method to override.")
The syntactical differences in these two approaches are compared side-by-side here:
A class that is derived from an abstract class needn't override all of the abstract methods it inherits with concrete versions; if one or more of the abstract methods is inherited "as is," then the derived class must also be declared abstract.
In contrast, a class that implements an interface must provide concrete versions of all abstract methods required of the interface; implementing an interface is an "all or nothing" proposition.
Another important distinction between extending an abstract class vs. implementing an interface is that whereas a given class may only be derived from one immediate base class, a class may implement as many interfaces as desired. Because this is such a powerful language feature, we'll illustrate it with an example in the next section.
As an example, if we were to invent a second interface called IAdministrator, which in turn specifies the following method headers:
public interface IAdministrator { public bool approveNewCourse(Course c); public bool hireProfessor(Professor p); }
we could then declare that a class such as Professor implements both the ITeacher and IAdministrator interfaces, in which case the class would need to implement all of the methods declared by both of these interfaces collectively:
public class Professor : ITeacher, IAdministrator { // The Professor class must implement all of the methods called for by // the ITeacher interface ... details omitted. // The Professor class must implement all of the methods called for by // the IAdministrator interface ... details omitted. }
Note?/td> |
Note that if a class implements two or more interfaces that call for methods with identical signatures, we need only implement one such method in the implementing class—that method will do "double duty" in satisfying both interfaces' implementation requirements as far as the compiler is concerned. |
When a class implements more than one interface, its objects are capable of assuming multiple identities or roles in an application; such objects can therefore be "handled" by various types of reference variables. Based on the preceding definition of a Professor as both an ITeacher and an IAdministrator, the following client code would be possible:
// Instantiate a Professor object, and store its handle in a reference // variable of type Professor. Professor p = new Professor(); // We then declare reference variables of the two types of interfaces that the // Professor class implements. ITeacher t; IAdministrator a; t = p; // We store a handle on the Professor in a reference variable of // type Teacher; this is possible because a professor IS A teacher! a = p; // We store a handle on the Professor in a reference variable of // type Administrator; this is possible because a professor IS AN // administrator!
as illustrated conceptually in Figure 7-4.
This is conceptually the same thing as you, as a person, being viewed as having different roles by different people: you're viewed as an employee by your manager, as a son or daughter by your parents, perhaps as a parent by your children, and so forth.
We may then command the same object either as a Professor …
// Department is a property defined for the Professor class ... p.Department = "Computer Science";
or as a Teacher …
// AgreeToTeach is a method defined for the ITeacher interface ... t.AgreeToTeach(c); // Note that p.agreeToTeach(c); also works ...
or as an Administrator …
// ApproveNewCourse is a method defined for the IAdministrator interface ... a.approveNewCourse(c); // Note that p.approveNewCourse(c); also works ...
because it's all three, rolled into one!
Note?/td> |
Note that not all OO languages embrace the notion of interfaces. For example, both C# and Java do, but C++ does not. C++ does, however, support multiple inheritance; C#'s provision for a class to be able to implement multiple interfaces enabled the C# language designers to avoid multiple inheritance, as we discussed in Chapter 5. |
A class may simultaneously extend a single base class and implement one or more interfaces, as follows:
public class Professor : Person, ITeacher, IAdministrator { ... }
Under such circumstances, the name of the base class will always come first in the list, followed by the names of all interfaces to be implemented. As we discussed in Chapter 5, C# doesn't support multiple inheritance, and so it isn't possible to list more than one class name after the colon; assuming that Professor and Student are both classes, the following wouldn't compile:
// This would not compile. public class StudentTeacher : Professor, Student, ITeacher { // details omitted }
The preceding code would produce the following compilation error:
error CS0527: 'Student' type in interface list is not an interface
In other words, the compiler will only consider the first entry after the colon as a base class and everything else in the list is assumed to be an interface.
Interfaces can't be instantiated, because there is no such thing as a constructor for an interface. That is, if we define ITeacher to be an interface, we may not try to instantiate it directly:
ITeacher t = new ITeacher(); // Impossible! The compiler will generate an error // on this line of code.
We'd get the following compilation error on the preceding line of code:
error CS0144: Cannot create an instance of the abstract class or interface 'ITeacher'
While we're indeed prevented from instantiating an interface, we're nonetheless permitted to declare reference variables to be of an interface type, as we saw earlier:
ITeacher t; // This is OK.
We could, therefore, do the following:
ITeacher t = new Professor();
Why is this permitted? The compiler allows assignments to occur if the type of the expression to the right of the equal sign (=) is a type that is compatible with the variable to the left of the equal sign. Since Professor implements ITeacher, a professor is a teacher, and so this assignment is permitted.
Interfaces are one of the most poorly understood, and hence underutilized, features of the OOPLs that support them. This is quite unfortunate, as interfaces are extremely powerful if utilized properly.
Whenever possible/feasible, design the public aspects of your classes using interface types instead of specific class types to allow for greater flexibility/utility of your methods, to include
Let's use two different examples to illustrate the power of interfaces.
In this example, assume that
Professor is a derived class of Person.
Student is a derived class of Person.
Professor and Student are sibling classes—neither derives from the other.
Person implements the ITeacher interface, and thus both Professor and Student indirectly implement the ITeacher interface, as we discussed earlier in this chapter.
We'll start by designing a class called Course with a private attribute of type Professor called teachingAssistant, and a property for accessing this attribute:
public class Course { private Professor teachingAssistant; // Other features omitted ... public Professor TeachingAssistant { get { return teachingAssistant; } set { teachingAssistant = value; } } // Other features omitted ... }
Then, we'd perhaps utilize this class from client code as follows:
// Client code. Course c = new Course("Math 101"); Professor p = new Professor("John Smith"); c.TeachingAssistant = p;
If, later on, we'd prefer to change the type of the private teachingAssistant attribute from Professor to Student, we'd also have to change the type of the public TeachingAssistant property to match:
public class Course { // Change type from Professor to Student here ... private Student teachingAssistant; // ... and here. public Student TeachingAssistant { // Details omitted ... } // etc. }
Our client code as originally written would no longer work—the highlighted line in the following code will no longer compile—because the TeachingAssistant property as modified is expecting to be assigned a Student reference now, and a Professor is not a Student:
// Client code. Course c = new Course("Math 101"); Professor p = new Professor("John Smith"); c.TeachingAssistant = p; // This line of code will no longer compile.
Now, let's look at an alteration to our original Course class design. Let's say that we had originally taken advantage of the fact that the Professor class implements the ITeacher interface to declare the TeachingAssistant property to be of type ITeacher from the outset:
public class Course { private ITeacher teachingAssistant; // details omitted ... public ITeacher TeachingAssistant { // details omitted ... } // etc. }
We're thus opening up more possibilities for client code: we can assign a Professor as a teaching assistant:
// Client code Course c = new Course("Math 101"); Professor p = new Professor("John Smith"); c.TeachingAssistant = p;
or a Student as a teaching assistant:
// Client code Course c = new Course("Math 101"); Student s = new Student("George Jones"); c.TeachingAssistant = s;
or a reference to any other type of object that implements the ITeacher interface.
An example of a commonly used predefined C# interface is the IList interface. The IList interface enforces implementation of the following method headers:
int Add(object value) void Clear() bool Contains(object value) int IndexOf(object value) void Insert(int index, object value) void Remove(object value) void RemoveAt(int index)
and is implemented by several of the predefined C# collection types, including the Array and ArrayList classes.
If we write a method that is to operate on a collection so that it accepts a generic IList reference versus a specific type of collection—say, an ArrayList— then the method is much more versatile; client code is free to pass in whatever collection type it wishes.