Inheritance


Inheritance is the concept that a new class can be based on an existing class, inheriting its interface and functionality from the original class. The mechanics and syntax of inheritance are described earlier in this chapter, so we won’t rehash them here. However, you haven’t yet looked at inheritance from a practical perspective, and that is the focus of this section.

When to Use Inheritance

Inheritance is one of the most powerful object-oriented features a language can support. At the same time, inheritance is one of the most dangerous and misused object-oriented features.

Properly used, inheritance enables you to increase the maintainability, readability, and reusability of your application by offering you a clear and concise way to reuse code, via both interface and implementation. Improperly used, inheritance creates applications that are very fragile, whereby a change to a class can cause the entire application to break or require changes.

Inheritance enables you to implement an is-a relationship. In other words, it enables you to implement a new class that is a more specific type of its base class. Properly used, inheritance enables you to create child classes that are actually the same as the base class.

Perhaps a quick example is in order. You know that a duck is a bird. However, a duck can also be food, though that is not its primary identity. Proper use of inheritance enables you to create a Bird base class from which you can derive a Duck class. You would not create a Food class and subclass Duck from Food, as a duck isn’t primarily food - it merely acts as food sometimes.

This is the challenge. Inheritance is not just a mechanism for code reuse, but a mechanism to create classes that flow naturally from another class. If you use it anywhere you want code reuse, you’ll end up with a real mess on your hands. If you use it anywhere you just want a common interface but where the child class is not really the same as the base class, then you should use multiple interfaces - something we’ll discuss shortly.

Important 

The question you must ask when using inheritance is whether the child class is a more specific version of the base class.

For example, you might have different types of products in your organization. All of these products have some common data and behaviors; they’ll all have a product number, description, and price. However, if you have an agricultural application, you might have chemical products, seed products, fertilizer products, and retail products. These are all different - each having its own data and behaviors - and yet there is no doubt that each one of them really is a product. You can use inheritance to create this set of products, as illustrated by the class diagram in Figure 4-16.

image from book
Figure 4-16

This diagram shows that you have an abstract base Product class, from which you derive the various types of product your system actually uses. This is an appropriate use of inheritance because each child class is obviously a more specific form of the general Product class.

Alternately, you might try to use inheritance just as a code-sharing mechanism. For example, you may look at your application, which has Customer, Product, and SalesOrder classes, and decide that all of them need to be designed so that they can be printed to a printer. The code to handle the printing will all be somewhat similar, so to reuse that printing code, you create a base PrintableObject class. This would result in the diagram shown in Figure 4-17.

image from book
Figure 4-17

Intuitively, you know that this doesn’t represent an is-a relationship. A Customer can be printed, and you are getting code reuse, but a customer isn’t really a specific case of a printable object. Implementing a system following this design will result in a fragile design and application. This is a case where multiple interfaces are a far more appropriate technology, as you’ll see later.

To illustrate this point, you might later discover that you have other entities in your organization that are similar to a customer but not quite the same. Upon further analysis, you may determine that Employee and Customer are related because they are specific cases of a Contact class. The Contact class provides commonality in terms of data and behavior across all these other classes (see Figure 4-18).

image from book
Figure 4-18

However, now your Customer is in trouble; you’ve said it is a PrintableObject, and you’re now saying it is a Contact. You might be able to just derive Contact from PrintableObject (see Figure 4-19).

image from book
Figure 4-19

The problem with this is that now Employee is also of type PrintableObject, even if it shouldn’t be, but you’re stuck because unfortunately you decided early on to go against intuition and say that a Customer is a PrintableObject.

This problem could be solved by multiple inheritance, which would enable Customer to be a subclass of more than one base class - in this case, of both Contact and PrintableObject. However, the .NET platform and Visual Basic don’t support multiple inheritance in this way. An alternative is to use inheritance for the is-a relationship with Contact, and use multiple interfaces to enable the Customer object to act as a PrintableObject by implementing an IPrintableObject interface.

Application versus Framework Inheritance

What you’ve just seen is how inheritance can accidentally cause reuse of code where no reuse was desired, but you can take a different view of this model by separating the concept of a framework from your actual application. The way you use inheritance in the design of a framework is somewhat different from how you use inheritance in the design of an actual application.

In this context, the word framework is being used to refer to a set of classes that provide base functionality that is not specific to an application, but rather may be used across a number of applications within the organization, or perhaps even beyond the organization. The .NET Framework base class library is an example of a very broad framework you use when building your applications.

The PrintableObject class discussed earlier, for example, may have little to do with your specific application, but may be the type of thing that is used across many applications. If so, it is a natural candidate for use as part of a framework, rather than being considered part of your actual application.

Framework classes exist at a lower level than application classes. For example, the .NET base class library is a framework on which all .NET applications are built. You can layer your own framework on top of the .NET Framework as well (see Figure 4-20).

image from book
Figure 4-20

If you take this view, then the PrintableObject class wouldn’t be part of your application at all, but part of a framework on which your application is built. If so, then the fact that Customer is not a specific case of PrintableObject doesn’t matter as much, as you’re not saying that it is such a thing, but rather that it is leveraging that portion of the framework’s functionality.

To make all this work requires a lot of planning and forethought in the design of the framework itself. To see the dangers you face, consider that you might want to not only print objects, but also store them in a file. In that case, you might have not only PrintableObject, but also SavableObject as a base class.

The question is, what do you do if Customer should be both printable and savable? If all printable objects are savable, you might have the result shown in Figure 4-21.

image from book
Figure 4-21

Or, if all savable objects are printable, you might have the result shown in Figure 4-22. However, neither of these truly provides a decent solution, as it is likely that the concept of being printable and the concept of being savable are different and not interrelated in either of these ways.

image from book
Figure 4-22

When faced with this sort of issue, it is best to avoid using inheritance and instead rely on multiple interfaces.

Inheritance and Multiple Interfaces

While inheritance is powerful, it is really geared around implementing the is-a relationship. Sometimes you will have objects that need a common interface, even though they aren’t really a specific case of some base class that provides that interface. We’ve just explored that issue in the discussion of the PrintableObject, SavableObject, and Customer classes.

Sometimes multiple interfaces are a better alternative than inheritance. The syntax for creating and using secondary and multiple interfaces was discussed.

Multiple interfaces can be viewed as another way of implementing the is-a relationship, although it is often better to view inheritance as an is-a relationship and to view multiple interfaces as a way of implementing an act-as relationship.

To think about this further, we can say that the PrintableObject concept could perhaps be better expressed as an interface - IPrintableObject.

When the class implements a secondary interface such as IPrintableObject, you’re not really saying that your class is a printable object, you’re saying that it can act as a printable object. A Customer is a Contact, but at the same time it can act as a printable object. This is illustrated in Figure 4-23.

image from book
Figure 4-23

The drawback to this approach is that you have no inherited implementation when you implement IPrintableObject. Earlier you saw how to reuse common code as you implement an interface across multiple classes. While not as automatic or easy as inheritance, it is possible to reuse implementation code with a bit of extra work.

Applying Inheritance and Multiple Interfaces

Perhaps the best way to see how inheritance and multiple interfaces interact is to look at an example. Returning to the original OOExample project, the following example combines inheritance and multiple interfaces to create an object that has both an is-a and act-as relationship at the same time. As an additional benefit, you’ll be using the .NET Framework’s capability to print to a printer or Print Preview dialog.

Creating the Contact Base Class

You already have a simple Customer class in the project, so now let’s add a Contact base class. Choose Project image from book Add Class and add a class named Contact, adding the following code:

  Public MustInherit Class Contact   Private mID As Guid = Guid.NewGuid   Private mName As String   Public Property ID() As Guid     Get       Return mID     End Get     Set(ByVal value As Guid)       mID = value     End Set   End Property   Public Property Name() As String     Get       Return mName     End Get     Set(ByVal value As String)       mName = value     End Set   End Property End Class 

Subclassing Contact

Now you can make the Customer class inherit from this base class because it is a Contact. In addition, because your base class now implements both the ID and Name properties, you can simplify the code in Customer by removing those properties and their related variables:

  Public Class Customer   Inherits Contact   Private mPhone As String   Public Property Phone() As String     Get       Return mPhone     End Get     Set(ByVal value As String)       mPhone = value     End Set   End Property End Class 

This shows the benefit of subclassing Customer from Contact, as you’re now sharing the ID and Name code across all other types of Contact as well.

Implementing IPrintableObject

You also know that a Customer should be able to act as a printable object. To do this in such a way that the implementation is reusable requires a bit of thought. First, though, you need to define the IPrintableObject interface.

You’ll use the standard printing mechanism provided by .NET from the System.Drawing namespace. As shown in Figure 4-24, add a reference to System.Drawing.dll to the Interfaces project.

image from book
Figure 4-24

With that done, bring up the code window for Interfaces.vb in the Interfaces project and add the following code:

  Imports System.Drawing Public Interface IPrintableObject   Sub Print()   Sub PrintPreview()   Sub RenderPage(ByVal sender As Object, _       ByVal ev As System.Drawing.Printing.PrintPageEventArgs) End Interface 

This interface ensures that any object implementing IPrintableObject will have Print and PrintPreview methods, so you can invoke the appropriate type of printing. It also ensures that the object has a RenderPage method, which can be implemented by that object to render the object’s data on the printed page.

At this point, you could simply implement all the code needed to handle printing directly within the Customer object. This isn’t ideal, however, as some of the code will be common across any objects that want to implement IPrintableObject, and it would be nice to find a way to share that code. To do this, you can create a new class, ObjectPrinter. This is a framework-style class, in that it has nothing to do with any particular application, but can be used across any application in which IPrintableObject will be used.

Add a new class named ObjectPrinter to the OOExample project. This class will contain all the code common to printing any object. It makes use of the built-in printing support provided by the .NET Framework class library. To use this, you need to import a couple of namespaces, so add the following code to the new class:

  Imports System.Drawing Imports System.Drawing.Printing Imports Interfaces 

You can then define a PrintDocument variable, which will hold the reference to your printer output. You’ll also declare a variable to hold a reference to the actual object you’ll be printing. Notice that you’re using the IPrintableObject interface datatype for this variable:

 Public Class ObjectPrinter   Private WithEvents document As PrintDocument   Private printObject As IPrintableObject 

Now you can create a routine to kick off the printing process for any object implementing IPrintableObject. This code is totally generic; you’ll write it here so it can be reused across any number of other classes:

  Public Sub Print(ByVal obj As IPrintableObject)   printObject = obj   document = New PrintDocument()   document.Print() End Sub 

Likewise, you can implement a method to show a print preview display of your object. This code is also totally generic, so add it here for reuse:

  Public Sub PrintPreview(ByVal obj As IPrintableObject)   Dim PPdlg As PrintPreviewDialog = New PrintPreviewDialog()   printObject = obj   document = New PrintDocument()   PPdlg.Document = document   PPdlg.ShowDialog() End Sub 

Finally, you need to catch the PrintPage event that is automatically raised by the .NET printing mechanism. This event is raised by the PrintDocument object whenever the document determines that it needs data rendered onto a page. Typically, it is in this routine that you would put the code to draw text or graphics onto the page surface. However, because this is a generic framework class, you won’t do that here; instead, you’ll delegate the call back into the actual application object that you want to print:

   Private Sub PrintPage(ByVal sender As Object, _       ByVal ev As System.Drawing.Printing.PrintPageEventArgs) _       Handles document.PrintPage            printObject.RenderPage(sender, ev)   End Sub End Class

This allows the application object itself to determine how its data should be rendered onto the output page. Let’s see how you can do that by implementing the IPrintableObject interface on the Customer class:

 Imports Interfaces Public Class Customer   Inherits Contact   Implements IPrintableObject 

By adding this code, you require that your Customer class implement the Print(), PrintPreview(), and RenderPage() methods. To avoid wasting paper as you test the code, let’s make both the Print() and PrintPreview() methods the same and have them just do a print preview display:

  Public Sub Print() _   Implements Interfaces.IPrintableObject.Print   Dim printer As New ObjectPrinter()   printer.PrintPreview(Me) End Sub 

Notice that you’re using an ObjectPrinter object to handle the common details of doing a print preview. In fact, any class you ever create that implements IPrintableObject will have this exact same code to implement a print preview function, relying on your common ObjectPrinter to take care of the details.

You also need to implement the RenderPage() method, which is where you actually put your object’s data onto the printed page:

  Private Sub RenderPage(ByVal sender As Object, _     ByVal ev As System.Drawing.Printing.PrintPageEventArgs) _     Implements IPrintableObject.RenderPage     Dim printFont As New Font("Arial", 10)     Dim lineHeight As Single = printFont.GetHeight(ev.Graphics)     Dim leftMargin As Single = ev.MarginBounds.Left     Dim yPos As Single = ev.MarginBounds.Top     ev.Graphics.DrawString("ID: " & ID.ToString, printFont, Brushes.Black, _       leftMargin, yPos, New StringFormat())     yPos += lineHeight     ev.Graphics.DrawString("Name: " & Name, printFont, Brushes.Black, _       leftMargin, yPos, New StringFormat())     ev.HasMorePages = False End Sub 

All of this code is unique to your object, which makes sense because you’re rendering your specific data to be printed. However, you don’t need to worry about the details of whether you’re printing to paper or print preview; that is handled by your ObjectPrinter class, which in turn uses the .NET Framework. This enables you to focus on generating the output to the page within your application class.

By generalizing the printing code in ObjectPrinter, you’ve achieved a level of reuse that you can tap into via the IPrintableObject interface. Anytime you want to print a Customer object’s data, you can have it act as an IPrintableObject and call its Print() or PrintPreview() method. To see this work, add a new button control to Form1 with the following code:

  Private Sub btnPrint_Click(ByVal sender As System.Object, _   ByVal e As System.EventArgs) Handles btnPrint.Click   Dim obj As New Customer   obj.Name = "Douglas Adams"   CType(obj, IPrintableObject).PrintPreview() End Sub 

This code creates a new Customer object and sets its Name property. You then use the CType() method to access the object via its IPrintableObject interface to invoke the PrintPreview method.

When you run the application and click the button, you’ll get a print preview display showing the object’s data (see Figure 4-25).

image from book
Figure 4-25

How Deep to Go?

Most of the examples discussed so far have illustrated how you can create a child class based on a single parent class. That is called single-level inheritance. However, inheritance can be many levels deep. For example, you might have a deep hierarchy such as the one shown in Figure 4-26.

image from book
Figure 4-26

From the root of System.Object down to NAFTACustomer you have four levels of inheritance. This can be described as a four-level inheritance chain.

There is no hard-and-fast rule about how deep inheritance chains should go, but conventional wisdom and general experience with inheritance in other languages such as Smalltalk and C++ indicate that the deeper an inheritance chain becomes, the harder it is to maintain an application.

This happens for two reasons. First is the fragile base class or fragile superclass issue, which we’ll discuss shortly. The second reason is that a deep inheritance hierarchy tends to seriously reduce the readability of your code by scattering the code for an object across many different classes, all of which are combined by the compiler to create your object.

One of the reasons for adopting object-oriented design and programming is to avoid so-called spaghetti code, whereby any bit of code you might look at does almost nothing useful but instead calls various other procedures and routines in other parts of your application. To determine what is going on with spaghetti code, you must trace through many routines and mentally piece together what it all means.

Object-oriented programming can help you avoid this problem, but it is most definitely not a magic bullet. In fact, when you create deep inheritance hierarchies you are often creating spaghetti code because each level in the hierarchy not only extends the previous level’s interface, but almost always also adds functionality. Thus, when you look at the final NAFTACustomer class it may have very little code. To figure out what it does or how it behaves, you have to trace through the code in the previous four levels of classes, and you might not even have the code for some of those classes, as they may come from other applications or class libraries you’ve purchased.

On the one hand, you have the benefit that you’re reusing code, but on the other hand, you have the drawback that the code for one object is actually scattered through five different classes. Keep this in mind when designing systems with inheritance - use as few levels in the hierarchy as possible to provide the required functionality.

The Fragile Base Class Problem

You’ve explored where it is appropriate to use inheritance and where it is not. You’ve also explored how you can use inheritance and multiple interfaces in conjunction to implement both is-a and act-as relationships simultaneously within your classes.

Earlier, we noted that while inheritance is an incredibly powerful and useful concept, it can also be very dangerous if used improperly. You’ve seen some of this danger in the discussion of the misapplication of the is-a relationship, and how you can use multiple interfaces to avoid those issues.

One of the most classic and common problems with inheritance is the fragile base class problem. This problem is exacerbated when you have very deep inheritance hierarchies, but it exists even in a single-level inheritance chain.

The issue you face is that a change in the base class always affects all child classes derived from that base class. This is a double-edged sword. On the one hand, you get the benefit of being able to change code in one location and have that change automatically cascade through all derived classes. On the other hand, a change in behavior can have unintended or unexpected consequences farther down the inheritance chain, which can make your application very fragile and hard to change or maintain.

Interface Changes

There are obvious changes you might make, which require immediate attention. For example, you might change your Contact class to have FirstName and LastName instead of simply Name as a property. In the Contact class, replace the mName variable declaration with the following code:

  Private mFirstName As String Private mLastName As String 

Now replace the Name property with the following code:

  Public Property FirstName() As String   Get     Return mFirstName   End Get   Set(ByVal value As String)     mFirstName = value   End Set End Property Public Property LastName() As String   Get     Return mLastName   End Get   Set(ByVal value As String)     mLastName = value   End Set End Property 

At this point, the Error List window in the IDE will display a list of locations where you need to alter your code to compensate for the change. This is a graphic illustration of a base class change that causes cascading changes throughout your application. In this case, you’ve changed the base class interface, thus changing the interface of all subclasses in the inheritance chain.

To avoid having to fix code throughout your application, always strive to keep as much consistency in your base class interface as possible. In this case, you can implement a read-only Name property that returns the full name of the Contact:

  Public ReadOnly Property Name() As String   Get     Return mFirstName & " " & mLastName   End Get End Property 

This resolves most of the items in the Error List window. You can fix any remaining issues by using the FirstName and LastName properties. For example, in Form1 you can change the code behind your button to the following:

 Private Sub Button1_Click(ByVal sender As System.Object, _      ByVal e As System.EventArgs) Handles button1.Click   Dim obj As New Customer   obj.FirstName = "Douglas"   obj.LastName = "Adams"   CType(obj, Interfaces.IPrintableObject).Print() End Sub

Any change to a base class interface is likely to cause problems, so you must think carefully before making such a change.

Implementation Changes

Unfortunately, there’s another, more subtle type of change that can wreak more havoc on your application: an implementation change. This is the core of the fragile base class problem.

Encapsulation provides you with separation of interface from implementation. However, keeping your interface consistent is merely a syntactic concept. If you change the implementation, you are making a semantic change, a change that doesn’t alter any of your syntax but can have serious ramifications on the real behavior of the application.

In theory, you can change the implementation of a class, and as long as you don’t change its interface, any client applications using objects based on that class will continue to operate without change. Of course, reality is never as nice as theory, and more often than not a change to implementation will have some consequences on the behavior of a client application.

For example, you might use a SortedList to sort and display some Customer objects. To do this, add a new button to Form1 with the following code:

 Private Sub btnSort_Click(ByVal sender As System.Object, _   ByVal e As System.EventArgs) Handles btnSort.Click   Dim col As New Generic.SortedDictionary(Of String, Customer)   Dim obj As Customer      obj = New Customer()   obj.FirstName = "Douglas"   obj.LastName = "Adams"   col.Add(obj.Name, obj)      obj = New Customer()   obj.FirstName = "Andre"   obj.LastName = "Norton"   col.Add(obj.Name, obj)      Dim item As Generic.KeyValuePair(Of String, Customer)   Dim sb As New System.Text.StringBuilder      For Each item In col     sb.AppendLine(item.Value.Name)   Next      MsgBox(sb.ToString) End Sub

This code simply creates a couple of Customer objects, sets their FirstName and LastName properties, and inserts them into a generic SortedDictionary object from the System.Collections.Generic namespace.

Items in a SortedDictionary are sorted based on their key value, and you are using the Name property to provide that key, meaning that your entries will be sorted by name. Because your Name property is implemented to return first name first and last name second, your entries will be sorted by first name.

If you run the application, the dialog will display the following:

 Andre Norton Douglas Adams

However, you can change the implementation of your Contact class - not directly changing or affecting either the Customer class or your code in Form1 - to return last name first and first name second, as shown here:

 Public ReadOnly Property Name() As String   Get     Return mLastName & ", " & mFirstName   End Get End Property

While no other code requires changing, and no syntax errors are flagged, the behavior of the application is changed. When you run it, the output will now be as follows:

 Adams, Douglas Norton, Andre

Maybe this change is inconsequential. Maybe it totally breaks the required behavior of your form. The developer making the change in the Contact class might not even know that someone was using that property for sort criteria.

This illustrates how dangerous inheritance can be. Changes to implementation in a base class can cascade to countless other classes in countless applications, having unforeseen side effects and consequences of which the base class developer is totally unaware.




Professional VB 2005 with. NET 3. 0
Professional VB 2005 with .NET 3.0 (Programmer to Programmer)
ISBN: 0470124709
EAN: 2147483647
Year: 2004
Pages: 267

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