Abstract Classes and Methods
When we think of a class type, we assume that applications will create objects of that type. In some cases, however, it is useful to declare classes for which the programmer never intends to instantiate objects. Such classes are called abstract classes. Because they are used only as base classes in inheritance hierarchies, we refer to them as abstract base classes. These classes cannot be used to instantiate objects, because as you will soon see, abstract classes are incompletederived classes must declare the "missing pieces." We demonstrate abstract classes in Section 11.5.1.
The purpose of an abstract class is primarily to provide an appropriate base class from which other classes can inherit, and thus share a common design. In the Shape hierarchy of Fig. 10.3, for example, derived classes inherit the notion of what it means to be a Shapecommon attributes such as location, color and borderThickness, and behaviors such as Draw, Move, Resize and ChangeColor. Classes that can be used to instantiate objects are called concrete classes. Such classes provide implementations of every method they declare (some of the implementations can be inherited). For example, we could derive concrete classes Circle, Square and triangle from abstract base class TwoDimensionalShape. Similarly, we could derive concrete classes Sphere, Cube and Tetrahedron from abstract base class THReeDimensionalShape. Abstract base classes are too general to create real objectsthey specify only what is common among derived classes. We need to be more specific before we can create objects. For example, if you send the Draw message to abstract class TwoDimensionalShape, the class knows that two-dimensional shapes should be drawable, but it does not know what specific shape to draw, so it cannot implement a real Draw method. Concrete classes provide the specifics that make it reasonable to instantiate objects.
Not all inheritance hierarchies contain abstract classes. However, programmers often write client code that uses only abstract base class types to reduce client code's dependencies on a range of specific derived class types. For example, a programmer can write a method with a parameter of an abstract base class type. When called, such a method can be passed an object of any concrete class that directly or indirectly extends the base class specified as the parameter's type.
Abstract classes sometimes constitute several levels of the hierarchy. For example, the Shape hierarchy of Fig. 10.3 begins with abstract class Shape. On the next level of the hierarchy are two more abstract classes, TwoDimensionalShape and THReeDimensionalShape. The next level of the hierarchy declares concrete classes for TwoDimensionalShapes (Circle, Square and TRiangle) and for ThreeDimensionalShapes (Sphere, Cube and TeTRahedron).
You make a class abstract by declaring it with keyword abstract. An abstract class normally contains one or more abstract methods. An abstract method is one with keyword abstract in its declaration, as in
public abstract void Draw(); // abstract method
Abstract methods do not provide implementations. A class that contains abstract methods must be declared as an abstract class even if that class contains concrete (non-abstract) methods. Each concrete derived class of an abstract base class also must provide concrete implementations of the base class's abstract methods. We show an example of an abstract class with an abstract method in Fig. 11.4.
Figure 11.4. Employee abstract base class.
(This item is displayed on pages 520 - 521 in the print version)
1 // Fig. 11.4: Employee.cs 2 // Employee abstract base class. 3 public abstract class Employee 4 { 5 private string firstName; 6 private string lastName; 7 private string socialSecurityNumber; 8 9 // three-parameter constructor 10 public Employee( string first, string last, string ssn ) 11 { 12 firstName = first; 13 lastName = last; 14 socialSecurityNumber = ssn; 15 } // end three-parameter Employee constructor 16 17 // read-only property that gets employee's first name 18 public string FirstName 19 { 20 get 21 { 22 return firstName; 23 } // end get 24 } // end property FirstName 25 26 // read-only property that gets employee's last name 27 public string LastName 28 { 29 get 30 { 31 return lastName; 32 } // end get 33 } // end property LastName 34 35 // read-only property that gets employee's social security number 36 public string SocialSecurityNumber 37 { 38 get 39 { 40 return socialSecurityNumber; 41 } // end get 42 } // end property SocialSecurityNumber 43 44 // return string representation of Employee object, using properties 45 public override string ToString() 46 { 47 return string.Format( "{0} {1} social security number: {2}", 48 FirstName, LastName, SocialSecurityNumber ); 49 } // end method ToString 50 51 // abstract method overridden by derived classes 52 public abstract decimal Earnings(); // no implementation here 53 } // end abstract class Employee |
Properties can also be declared abstract, then overridden in derived classes with the override keyword, just like methods. This allows an abstract base class to specify common properties of its derived classes. Abstract property declarations have the form:
public abstract PropertyType MyProperty { get; set; } // end abstract property
The semicolons after the get and set keywords indicate that we provide no implementation for these accessors. An abstract property may omit implementations for the get accessor, the set accessor or both. Concrete derived classes must provide implementations for every accessor declared in the abstract property. When both get and set accessors are specified (as above), every concrete derived class must implement both. If one accessor is omitted, the derived class is not allowed to implement that accessor. Doing so causes a compilation error.
Constructors and static methods cannot be declared abstract. Constructors are not inherited, so an abstract constructor could never be implemented. Similarly, derived classes cannot override static methods, so an abstract static method could never be implemented.
|
|
Although we cannot instantiate objects of abstract base classes, you will soon see that we can use abstract base classes to declare variables that can hold references to objects of any concrete classes derived from those abstract classes. Applications typically use such variables to manipulate derived class objects polymorphically. Also, you can use abstract base class names to invoke static methods declared in those abstract base classes.
Polymorphism is particularly effective for implementing so-called layered software systems. In operating systems, for example, each type of physical device could operate quite differently from the others. Even so, common commands can read or write data from and to the devices. For each device, the operating system uses a piece of software called a device driver to control all communication between the system and the device. The write message sent to a device driver object needs to be interpreted specifically in the context of that driver and how it manipulates a specific device. However, the write call itself really is no different from the write to any other device in the system: Place some number of bytes from memory onto that device. An object-oriented operating system might use an abstract base class to provide an "interface" appropriate for all device drivers. Then, through inheritance from that abstract base class, derived classes are formed that all behave similarly. The device driver methods are declared as abstract methods in the abstract base class. The implementations of these abstract methods are provided in the derived classes that correspond to the specific types of device drivers. New devices are always being developed, often long after the operating system has been released. When you buy a new device, it comes with a device driver provided by the device vendor. The device is immediately operational after you connect it to your computer and install the device driver. This is another elegant example of how polymorphism makes systems extensible.
It is common in object-oriented programming to declare an iterator class that can traverse all the objects in a collection, such as an array (Chapter 8) or an ArrayList (Chapter 27, Collections). For example, an application can print an ArrayList of objects by creating an iterator object and using it to obtain the next list element each time the iterator is called. Iterators often are used in polymorphic programming to traverse a collection that contains references to objects of various classes in an inheritance hierarchy. (Chapters 2627 present a thorough treatment of C#'s new "generics" capabilities, ArrayList and iterators.) An ArrayList of references to objects of class TwoDimensionalShape, for example, could contain references to objects from derived classes Square, Circle, triangle and so on. Calling method Draw for each TwoDimensionalShape object off a TwoDimensionalShape variable would polymorphically draw each object correctly on the screen.