1.3 Inheritance

Inheritance

Inheritance is one of the most mystifying concepts of object-oriented programming. But it's really quite simple. Inheritance is one of the key concepts to software reuse.

Let's stick with our movie theater example. Let's say Markus Multiplex made a lot of money thanks to our great reservation system, so management made a decision to build one more screen that features balcony seating. Now, that sounds great for the audience but it doesn't sound that great to us. After all, our existing classes can't handle balcony seating. What can we do?

We could create another class especially for the new screen. But that would be a shame. After all, we spent months creating the other class and we don't want to rewrite all of that. So this is not an option. We want a way to say, "The new screen is just like all the others, with the exception that we have additional seats in a balcony."

And that's what inheritance is all about. It provides a way to tell Visual FoxPro that one class is basically the same as another with a few exceptions. The way to do that you guessed it is by using the keyword AS in the class definition. Let's have a look at the very basic definition of our screen class:

DEFINE CLASS Screen AS Custom

..
..

ENDDEFINE

AS Custom basically means we don't want to inherit anything and start out from scratch. (Well, this is not entirely true, but we'll worry about that later.) In order to create a class that inherits from the class Screen, we would define a new class as follows:

DEFINE CLASS BalconyScreen AS Screen
BalconySeats = 80

ENDDEFINE

This is called subclassing. The new class BalconyScreen inherits everything the class Screen had. In addition to that, we defined a new property called BalconySeats in order to take care of the new possibilities. So internally, the new class now has the properties CurrentMovie, AvailableSeats, Date, and BalconySeats, and of course it also has the ReserveSeats() method. In addition to that, our new class inherits all of the initial values for each property.

The class Screen is also called the parent class of BalconyScreen, while BalconyScreen is the child class from the Screen's point of view.

We can assume that the number of seats in the regular seating area has increased, and can take care of this in our new class:

DEFINE CLASS BalconyScreen AS Screen
AvailableSeats = 220
BalconySeats = 80

ENDDEFINE

We have now overwritten the initial value for the number of available seats. Note that we have only one newly defined property in this definition (BalconySeating).

One great feature of inheritance is that you can always go back and add features to the parent class. All the subclasses will then automatically inherit all the features. So if some dummy one day has the idea of adding optional headphones to every seat, we are ready. We would simply add headsets as a property of our Screen class, and all instances of that class and instances of any subclasses we might have added will automatically inherit the headsets property as well.

Overwriting code

We have not yet taken care of our reservation method to accommodate the addition of the balcony class. Of course it needs some adjustment, so let's see what the possibilities are. Option number one is to simply overwrite the original method in the subclass:

DEFINE CLASS BalconyScreen AS Screen
AvailableSeats = 220
BalconySeats = 80


FUNCTION ReserveSeats( NumberOfSeats, Area )
IF Area = 1 && Regular seats
IF THIS.AvailableSeats - NumberOfSeats > -1
THIS.AvailableSeats = THIS.AvailableSeats - NumberOfSeats
ELSE
MessageBox("Not enough seats available.")
ENDIF
ELSE
IF THIS.BalconySeats - NumberOfSeats > -1
THIS.BalconySeats = THIS.BalconySeats - NumberOfSeats
ELSE
MessageBox("Not enough balcony seats available.")
ENDIF
ENDIF
RETURN
ENDFUNC

ENDDEFINE

In this example, the ReserveSeats() method has been totally overwritten and redefined. The method now supports another parameter that determines whether we want regular or balcony seating:

Screen1.ReserveSeats( 2, 1 ) && Regular seating, 2 seats

Screen1.ReserveSeats( 5, 2 ) && Balcony seating, 5 seats

This takes care of our problem. But was it really a good solution? I doubt it! Almost half of the ReserveSeats() method code has been copied from the original method. Suppose that this method had several hundred lines of complicated code. What if that code had bugs? We would have copied them over to the new method. Maybe later on we would fix that bug in the original method, but then we might forget about the copied code in the new method. And even if we didn't forget about it, the code might have been modified so it's hard to locate the bug. Or even worse, the new code might depend on the original bug and we'd break a good amount of code by changing something

There has to be a better way! And there is. It's called programming by exception.

Programming by exception

The basic idea of programming by exception is to define only new behavior, changed behavior or any other cases that might be an exception from the original code. Doing it this way, we would still get all the bugs from the original code, but all the code would be in one place. If we had to go back later to fix something or to add new functionality, we would have to take care of only one class.

So let's take a look at the method in the subclass to see what parts are copied and what is the exception:

FUNCTION ReserveSeats( NumberOfSeats, Area )
IF Area = 1 && Regular seats
* We already have that in the parent class
DoDefault( NumberOfSeats )
ELSE
IF THIS.BalconySeats - NumberOfSeats > -1
THIS.BalconySeats = THIS.BalconySeats - NumberOfSeats
ELSE
MessageBox("Not enough balcony seats available.")
ENDIF
ENDIF
RETURN
ENDFUNC

We removed all the code that belongs to Area = 1 and added a DoDefault() function call instead.

DoDefault()

DoDefault() calls code that has been defined in a parent class. So in the example above, the original code is executed within the first IF statement. As you can see, you can also pass parameters using DoDefault(). This is important because the original code in the Screen class expects the number of seats as a parameter. Note that the new version of this method receives two parameters, while we're passing only one parameter to the parent class.

DoDefault() also returns the value of the original method. So the example above is incomplete because we aren't taking care of the return value at all. This would be important, even though the parent class doesn't return anything interesting. We might decide to always return something in a future version of our Screen class. One of the problems with that is we don't yet have a clue what this return value might mean. So in this case we should simply pass whatever value we get back:

FUNCTION ReserveSeats( NumberOfSeats, Area )

LOCAL ReturnValue
ReturnValue = .T.
IF Area = 1 && Regular seats
* We already have that in the parent class
ReturnValue = DoDefault( NumberOfSeats )
ELSE
IF THIS.BalconySeats - NumberOfSeats > -1
THIS.BalconySeats = THIS.BalconySeats - NumberOfSeats
ELSE
MessageBox("Not enough balcony seats available.")
ENDIF
ENDIF
RETURN ReturnValue
ENDFUNC

DoDefault() can be called multiple times in one method. This might sound stupid, but it sometimes makes sense, such as when using CASE structures. But be very careful doing that. You might end up calling the original code twice, and reserve four seats instead of two.

The scope resolution operator

When Visual FoxPro was first released (version 3.0), there was no DoDefault() function. People had to use the more complex scope resolution operator (::). To use the scope resolution operator, you have to know the method name (which is easy) and the name of the parent class (which might be tricky).
In the example above, we would use the following command instead of the DoDefault():

Screen::ReserveSeats( NumberOfSeats )

Just like DoDefault(), the scope resolution operator can pass parameters and receive return values. However, the scope resolution operator has a couple of disadvantages. Obviously it is a lot harder to use than DoDefault(), since DoDefault() knows about all the class and method names by itself.

This might not seem like a big deal, but it really is. Let's just assume you discover that a class structure is incorrect and you decide to change the parent class. In this case you'd have to go through all the code, making sure that the scope resolution operator calls the correct class.
There also are a couple of issues when dealing with more complex scenarios. I'll go into more detail later on; you'll have to trust me for now.

On the other hand, the scope resolution operator allows more fine tuning than DoDefault(). Imagine the following class structure:

DEFINE CLASS Class1 AS Custom
FUNCTION Test
WAIT WINDOW "Class 1"

ENDFUNC
ENDDEFINE

DEFINE CLASS Class2 AS Class1
FUNCTION Test
WAIT WINDOW "Class 2"
Class1::Test()
ENDFUNC
ENDDEFINE


DEFINE CLASS Class3 AS Class2
FUNCTION Test
WAIT WINDOW "Class 3"
Class1::Test()
ENDFUNC
ENDDEFINE

In this example, Class2 is a subclass of Class1, and Class3 inherits from Class2. But still, the test method in Class3 bypasses Class2 and calls directly to Class1. This is usually a sign of bad design. In this example, we really should have subclassed Class3 from Class1 rather than from Class2. Unfortunately, scenarios like this can happen in big projects, especially when the implementation phase starts too early. So I encourage you to go back to the design phase when you discover problems that require this kind of design.

If you still can't help doing something "bad," be very careful and make sure to document what you do and why. Otherwise another programmer might just browse through your code and replace the scope resolution operator by a DoDefault(), which would lead to a bug that is very hard to spot.

What about multiple inheritance?

Maybe you've heard about a feature called multiple inheritance. To make a long story short: Visual FoxPro does not support multiple inheritance. I'll explain the concepts briefly so you won't feel uninformed the next time a C++ programmer talks about it.

In all the examples so far, every class had one parent class. (Yes, even the ones that were defined AS Custom had a parent class, as you'll see later on.) With multiple inheritance, classes can have more than one parent class. This leads to some tricky scenarios. What if you used a DoDefault()? Which class would be called? Which class would be used for the scope resolution operator? All these issues can be resolved easily as long as none of the parent classes use the same method or property names. But as soon as two classes use the same names, things can get out of control.

C++ programmers might argue that all of these issues can be handled, but I'm still pretty happy that Visual FoxPro does not have multiple inheritance.

Base classes a first look

I still haven't explained what AS Custom stood for in the examples above.

As I mentioned before, every class in Visual FoxPro must be derived (subclassed) from another class. Unlike other languages, there is no way to start from scratch. No matter what you do in Visual FoxPro, you'll always inherit some basic functionality. FoxPro has a set of default classes called the FoxPro Base Classes.

Most of these are visible classes such as buttons, textboxes, forms, and so forth. Custom is invisible. Custom is also as close as you will get to starting from scratch, but you might still be surprised at how much functionality this class already has.

I'll use this base class often in my examples. You'll find that this is a pretty accurate reflection of the real world, since most of the classes you will use in Visual FoxPro are subclassed from Custom. This, of course, applies only to non-interface classes. When you take a first look at the objects you create using FoxPro, you might be fooled by the number of interface classes you use. But keep in mind that the biggest part of an application is typically the invisible business logic that pulls the strings behind the scenes.

This book was not meant to explain FoxPro base classes; your Visual FoxPro manual already does a good job explaining them. But I'll revisit base classes later on to take a closer look at some of the methods and properties they share, and I'll also pick a couple of special base classes that are worth an in-depth look.

How to draw subclassing

Since I already explained how classes are drawn in UML, I'll also show how to draw subclassing (see Figure 2).

Inheritance is simply drawn as an arrow pointing from the subclass to the parent class. The subclass usually shows only new properties and methods. But you might see some specialized diagrams that also show inherited members, especially in complex diagrams where only part of the inheritance tree is shown.

Figure 2. A simple arrow indicates inheritance.



Advanced Object Oriented Programming with Visual FoxPro 6. 0
Advanced Object Oriented Programming with Visual FoxPro 6.0
ISBN: 0965509389
EAN: 2147483647
Year: 1998
Pages: 113
Authors: Markus Egger

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