Inheritance


Inheritance is the process of deriving a child class from a parent class. The child class inherits all of the properties, methods, and events of the parent class. It can then modify, add to, or subtract from the parent class. Making a child class inherit from a parent class is also called deriving the child class from the parent, and subclassing the parent class to form the child class.

For example, you might define a Person class that includes variables named FirstName, LastName, Street, City, State, Zip, Phone, and Email. It might also include a DialPhone method that dials the person’s phone number on the phone attached to the computer’s modem.

You could then derive the Employee class from the Person class. The Employee class inherits the FirstName, LastName, Street, City, State, Zip, Phone, and Email variables. It then adds new EmployeeId, SocialSecurityNumber, OfficeNumber, Extension, and Salary variables. This class might override the Person class’s DialPhone method, so it dials the employee’s office extension instead of the home phone number.

You can continue deriving classes from these classes to make as many types of objects as you need. For example, you could derive the Manager class from the Employee class and add fields such as Secretary, which is another Employee object that represents the manager’s secretary. Similarly, you could derive a Secretary class that includes a reference to a Manager object. You could derive ProjectManager, DepartmentManager, and DivisionManager from the Manager class, Customer from the Person class, and so on for other types of people that the application needed to use. Figure 15-1 shows these inheritance relationships.

image from book
Figure 15-1: You can derive classes from other classes to form quite complex inheritance relationships.

Inheritance Hierarchies

One of the key benefits of inheritance is code reuse. When you derive a class from a parent class, the child class inherits the parent’s properties, methods, and events, so the child class gets to reuse the parent’s code. That means you don’t need to implement separate FirstName and LastName properties for the Person,

Employee, Manager, Secretary, and other classes shown in Figure 15-1. These properties are defined only in the Person class, and all of the other classes inherit them.

Code reuse not only saves you the trouble of writing more code but also makes maintenance of the code easier. Suppose that you build the hierarchy shown in Figure 15-1 and then decide that everyone needs a new BirthDate property. Instead of adding a new property to every class, you can simply add it to the Person class and all of the other classes inherit it.

Similarly, if you need to modify or delete a property or method, you only need to make the change in the class where it is defined, not in all of the classes that inherit it. If the Person class defines a SendEmail method and you must modify it so that it uses a particular e-mail protocol, you only need to change the routine in the Person class, not in all the classes that inherit it.

Tip 

Some languages allow multiple inheritance, where one class can be derived from more than one parent class. For example, suppose that you create a Vehicle class that defines properties of vehicles (number of wheels, horsepower, maximum speed, acceleration, and so forth) and a House class that defines properties of living spaces (square feet, number of bedrooms, number of bathrooms, and so forth). Using multiple inheritance, you could derive a MotorHome class from both the Vehicle and House classes. This class would have the features of both Vehicles and Houses.

Visual Basic does not allow multiple inheritance, so a class can have at most one parent class. That means relationships such as those shown in Figure 15-1 are treelike and form an inheritance hierarchy.

If you think you need multiple inheritance, you can use interface inheritance. Instead of defining multiple parent classes, define parent interfaces. Then you can make the child class implement as many interfaces as you like. The class doesn’t inherit any code from the interfaces, but at least its behavior is defined by the interfaces. See the section “Implements interface” in Chapter 16 for more information on interfaces.

Refinement and Abstraction

There are two different ways you can think about the relationship between a parent class and its child classes. First, using a top-down view of inheritance, you can think of the child classes as refining the parent class. They provide extra detail that differentiates among different types of the parent class.

For example, suppose that you start with a broadly defined class such as Person. The Person class would need general fields such as name, address, and phone number. It would also need more specific fields that do not apply to all types of person. For example, employees would need employee ID, Social Security number, office number, and department fields. Customers would need customer ID, company name, and discount code fields. You could dump all these fields in the Person class, but that would mean stretching the class to make it play two very different roles. A Person acting as an Employee would not use the Customer fields, and vice versa.

A better solution is to derive new Employee and Customer classes that refine the Person class and differentiate between the types of Person.

A bottom-up view of inheritance considers the parent class as abstracting common features out of the child classes into the parent class. Common elements in the child classes are removed and placed in the parent class. Because the parent class is more general than the child classes (it includes a larger group of objects), abstraction is sometimes called generalization.

Suppose that you are building a drawing application and you define classes to represent various drawing objects such as Circle, Ellipse, Polygon, Rectangle, and DrawingGroup (a group of objects that should be drawn together). After you work with the code for a while, you may discover that these classes share a lot of functionality. Some, such as Ellipse, Circle, and Rectangle, are defined by bounding rectangles. All the classes need methods for drawing the object with different pens and brushes on the screen or on a printer.

You could abstract these classes and create a new parent class named Drawable. That class might provide basic functionality such as a simple variable to hold a bounding rectangle. This class would also define a DrawObject routine for drawing the object on the screen or printer. It would declare that routine with the MustOveride keyword, so each child class would need to provide its own DrawObject implementation, but the Drawable class would define its parameters.

Sometimes you can pull variables and methods from the child classes into the parent class. In this example, the Drawable class might include Pen and Brush variables that the objects would use to draw themselves. Putting code in the parent class reduces the amount of redundant code in the child classes, making debugging and maintenance easier.

To make the classes more consistent, you could even change their names to reflect their shared ancestry. You might change their names to DrawableEllipse, DrawablePolygon, and so forth. This not only makes it easier to remember that they are all related to the Drawable class but also helps avoid confusion with class names such as Rectangle that are already used by Visual Basic.

The Drawable parent class also allows the program to handle the drawing objects more uniformly. It can define a collection named AllObjects that contains references to all the current drawing’s objects. It could then loop through the collection, treating the objects as Drawables, and calling their DrawObject methods. The section “Polymorphism” later in this chapter provides more details.

Usually application architects define class hierarchies using refinement. They start with broad general classes and then refine them as necessary to differentiate among the kinds of objects that the application will need to use. These classes tend to be relatively intuitive, so you can easily imagine their relationships.

Abstraction often arises during development. As you build the application’s classes, you notice that some have common features. You abstract the classes and pull the common features into a parent class to reduce redundant code and make the application more maintainable.

Refinement and abstraction are useful techniques for building inheritance hierarchies, but they have their dangers. Designers should be careful not to get carried away with unnecessary refinement or overrefinement. For example, suppose that you define a Vehicle class. You then refine this class by creating Auto, Truck, and Boat classes. You refine the Auto class into Wagon and Sedan classes and further refine those for different drive types (four-wheel drive, automatic, and so forth). If you really go crazy, you could define classes for specific manufacturers, body styles, and color.

The problem with this hierarchy is that it captures more detail than the application needs. If the program is a repair dispatch application, it might need to know whether a vehicle is a car or truck. It will not need to differentiate between wagons and sedans, different manufacturers, or colors. Vehicles with different colors have the same behaviors as far as this application is concerned.

Avoid unnecessary refinement by only refining a class when doing so lets you capture new information that the application actually needs to know.

Just as you can take refinement to ridiculous extremes, you can also overdo class abstraction. Because abstraction is driven by code rather than intuition, it sometimes leads to unintuitive inheritance hierarchies. For example, suppose that your application needs to mail work orders to remote employees and invoices to customers. If the WorkOrder and Invoice classes have enough code in common, you might decide to give them a common parent class named MailableItem that contains the code needed to mail a document to someone.

This type of unintuitive relationship can confuse developers. Because Visual Basic doesn’t allow multiple inheritance, it can also cause problems if the classes are already members of other inheritance hierarchies. You can avoid some of those problems by moving the common code into a library and having the classes call the library code. In this example, the WorkOrder and Invoice classes would call a common set of routines for mailing documents and would not be derived from a common parent class.

Unnecessary refinement and overabstracted classes lead to overinflated inheritance hierarchies. Sometimes the hierarchy grows very tall and thin. Other times, it may include several root classes (with no parents) on top of only one or two small classes each. Either of these can be symptoms of poor designs that include more classes than necessary. If your inheritance hierarchy starts to take on one of these forms, you should spend some time reevaluating the classes. Ensure that each adds something meaningful to the application and that the relationships are reasonably intuitive. Too many classes with confusing relationships can drag a project to a halt as developers spend more time trying to understand the hierarchy than they spend implementing the individual classes.

If you are unsure whether to add a new class, leave it out. It’s usually easier to add a new class later if you discover that it is necessary than it is to remove a class after developers start using it.

“Has-a” and “Is-a” Relationships

Refinement and abstraction are two useful techniques for generating inheritance hierarchies. The “has-a” and “is-a” relationships can help you understand whether it makes sense to make a new class using refinement or abstraction.

The “is-a” relationship means one object is a specific type of another class. For example, an Employee “is-a” specific type of Person object. The “is-a” relation maps naturally into inheritance hierarchies. Because an Employee “is-a” Person, it makes sense to derive the Employee class from the Person class.

The “has-a” relationship means that one object has some item as an attribute. For example, a Person object “has-a” street address, city, state, and ZIP code. The “has-a” relation maps most naturally to embedded objects. For example, you could give the Person class Street, City, State, and Zip properties.

Suppose that the program also works with WorkOrder, Invoice, and other classes that also have street, city, state, and ZIP code information. Using abstraction, you might make a HasPostalAddress class that contains those values. Then you could derive Person, WorkOrder, and Invoice as child classes. Unfortunately, that makes a rather unintuitive inheritance hierarchy. Deriving the Person, WorkOrder, and Invoice classes from HasPostalAddress makes those classes seem closely related when they are actually related almost coincidentally.

A better solution would be to encapsulate the postal address data in its own PostalAddress class and then include an instance of that class in the Person, WorkOrder, and Invoice classes.

The following code shows how the Person class would include an instance of the PostalAddress class:

  Public Class Person     Public MailingAddress As PostalAddress     ... End Class 

You make a parent class through abstraction in part to avoid duplication of code. The parent class contains a single copy of the common variables and code, so the child classes don’t need to have their own separate versions for you to debug and maintain. Placing an instance of the PostalAddress class in each of the other classes provides the same benefit without confusing the inheritance hierarchy.

You can often view a particular relationship as either an “is-a” or “has-a” relationship. A Person “has-a” postal address. At the same time, a Person “is-a” thing that has a postal address. Use your intuition to decide which view makes more sense. One hint is that postal address is easy to describe while thing that has a postal address is more awkward and ill-defined. Also, think about how the relationship might affect other classes. Do you really want Person, WorkOrder, and Invoice to be siblings in the inheritance hierarchy? Or would it make more sense for them to just share an embedded class?

Adding and Modifying Class Features

Adding new properties, methods, and events to a child class is easy. You simply declare them as you would in any other class. The parent class knows nothing about them, so the new items are added only to the child class.

The following code shows how you could implement the Person and Employee classes in Visual Basic. The Person class includes variables that define the FirstName, LastName, Street, City, State, Zip, Phone, and Email values. It also defines the DialPhone method. The version shown here simply displays the Person object’s Phone value. A real application could connect to the computer’s modem and dial the number.

The Employee class inherits from the Person class. It declares its own EmployeeId, SocialSecurityNumber, OfficeNumber, Extension, and Salary variables. It also defines a new version of the DialPhone method that displays the Employee object’s Extension value rather than its Phone value. The DialPhone method in the Person class is declared with the Overridable keyword to allow derived classes to override it. The version defined in the Employee class is declared with the Overrides keyword to indicate that it should replace the version defined by the parent class.

  Public Class Person     Public FirstName As String     Public LastName As String     Public Street As String     Public City As String     Public State As String     Public Zip As String     Public Phone As String     Public Email As String     ' Dial the phone using Phone property.     Public Overridable Sub DialPhone()         MessageBox.Show("Dial " & Me.Phone)     End Sub End Class Public Class Employee     Inherits Person     Public EmployeeId As Integer     Public SocialSecurityNumber As String     Public OfficeNumber As String     Public Extension As String     Public Salary As Single     ' Dial the phone using Extension property.     Public Overrides Sub DialPhone()         MessageBox.Show("Dial " & Me.Extension)     End Sub End Class  

A class can also shadow a feature defined in a parent class. When you declare a property, method, or event with the Shadows keyword, it hides any item in the parent that has the same name. This is very similar to overriding, except that the parent class does not have to declare the item as overridable and the child item needs only to match the parent item’s name.

For example, the parent might define a SendMail subroutine that takes no parameters. If the child class defines a SendMail method that takes some parameters and uses the Shadows keyword, the child’s version hides the parent’s version.

In fact, the child and parent items don’t even need to be the same kind of item. For example, the child class could make a subroutine named FirstName that shadows the parent class’s FirstName variable. This type of change can be confusing, however, so usually you should only shadow items with similar items.

The following code shows how the Employee class might shadow the Person class’s SendMail subroutine. The Person class displays the mailing address where it would send a letter. A real application might print a letter on a specific printer for someone to mail. The Employee class shadows this routine with one of its own, which displays the employee’s office number instead of a mailing address.

  Public Class Person     ...     ' Send some mail to the person's address.     Public Sub SendMail()         MessageBox.Show("Mail " & Street & ", " & City & ", " & State & " " & Zip)     End Sub End Class Public Class Employee     Inherits Person     ...     ' Send some mail to the person's office.     Public Shadows Sub SendMail()         MessageBox.Show("Mail " & OfficeNumber)     End Sub End Class  

Interface Inheritance

When you derive one class from another, the child class inherits the properties, methods, and events defined by the parent class. It inherits both the definition of those items as well as the code that implements them.

Visual Basic also enables you to define an interface. An interface defines a class’s behaviors, but does not provide an implementation. After you have defined an interface, a class can use the Implements keyword to indicate that it provides the behaviors specified by the interface. It’s then up to you to provide the code that implements the interface.

For example, consider again the MotorHome class. Visual Basic does not allow a class to inherit from more than one parent class, but a class can implement as many interfaces as you like. You could define an IVehicle interface (by convention, interface names begin with the capital letter I) that defines properties of vehicles (number of wheels, horsepower, maximum speed, acceleration, and so forth) and an IHouse interface that defines properties of living spaces (square feet, number of bedrooms, number of bathrooms, and so forth). Now, you can make the MotorHome class implement both of those interfaces. The interfaces do not provide any code, but they do declare that the MotorHome class implements the interface’s features.

Like true inheritance, interface inheritance provides polymorphism (see the next section, “Polymorphism,” for more details on this topic). You can use a variable having the type of the interface to refer to objects that define the interface. For example, suppose that the Employee class implements the IPerson interface. Then you can use a variable of type IPerson to refer to an object of type Employee.

Suppose that the people collection contains Employee objects. The following code uses a variable of type

  For Each person As IPerson In people     Debug.WriteLine(person.FirstName & " " & person.LastName) Next person  




Visual Basic 2005 with  .NET 3.0 Programmer's Reference
Visual Basic 2005 with .NET 3.0 Programmer's Reference
ISBN: 470137053
EAN: N/A
Year: 2007
Pages: 417

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net