Special Edition Using Visual FoxPro 6


Chapter 13

Introduction to Object-Oriented Programming


Understanding Object-Oriented Programming

Object-oriented programming (OOP) revolutionizes nearly every aspect of application development with Visual FoxPro, from the analysis and design phases to the coding and testing of applications and application components. Object-oriented programming was introduced in Visual FoxPro Version 3.0 and has become one of its standard and most useful features.

This and the next four chapters discuss object-oriented programming with Visual FoxPro in detail and include the following information:

Visual FoxPro, like Visual C++, is a hybrid language. This means that the developers have the option of developing applications using OOP methods or modular programming. If you develop applications in other languages, such as SmallTalk, you must use OOP.

If Visual FoxPro does not require the use of OOP, why should you consider leaving the warm comfort of developing the old way to go to the radically new mind-set of OOP? To answer this question, it is first necessary to review the background of what is called the software crisis.

The Software Crisis

Businesses today are experiencing a software crisis that stems from the inability of today's programming methodologies to adapt to the rapidly changing world of business. Applications developed in modular form often require a long development cycle, are based on marginally reusable code bases, and are frequently outdated before they hit the user's desk. The premise of modular development is to create software based on modules that are typically, by their very nature, different from business to business and even from department to department within a single company. In today's global business environment, companies need the diversity of an operating system or programming language that will enable them to develop programs or software that is cost efficient to produce and deploy in many working environments. For example, an inventory control program written to work in an American company might not have all the functionality that would be needed to work in a Mexican company. Visual FoxPro is one of the programming languages that is adaptable enough to enable the developer the versatility to quickly meet the needs of most companies in many situations.

Take the example of accounting applications. At the most basic level, they are the same: accounts receivable programs that manage and track the money owed to the company, accounts payable programs manage the bills owed by the company, and so on. However, when you drill down to the operations of the individual business functions, differences typically arise from organization to organization. For example, the format of a bill for a service organization is different from the format of a bill for a merchandising firm.

When software is developed in modules, as it was in FoxPro 2.x, individual programs are combined to create an application. If an application is brought from one company to another, or even from one department to another, typically the software is copied and the source code is modified to fit the requirements of the new users. Imagine what happens when an upgrade is released for the original software. The new users do not get the benefits of the new upgrade unless a second effort is made to incorporate the changes into the copied version. If a bug is found in one version of the software, the software must be fixed in two places. Obviously, this is not the best way to do things.

OOP takes a radically different approach. Software is created not in modules but rather in terms of entities (or objects). For example, using OOP, you do not create an accounts receivable module per se but instead create customer objects, invoice objects, and so on and then combine them into an application. In effect, components are developed that are put together to form an application.

Using this new method of development, you focus on the entities being modeled in the problem domain (a fancy term meaning the business problem you are trying to solve with software). The design of a customer object is based on the simple concept of trying to determine what a customer does and what a customer knows within the context of a business. "Within the context" means that you will come up with a theoretical model for the customer based on what the "customer" is within the business environment. For example, a customer knows his or her name, address, telephone number, and credit balance. The customer would also know how to print itself, save itself, and more. This does not mean that Joe Customer knows how to print his own record or save himself to the database. Rather, it means that a customer object would have these responsibilities.

If an application is taken from one business to another, the differences can be accommodated by basing new objects on existing ones (a process known as subclassing). The behaviors and attributes of the original object are automatically brought forward (inherited) and are changed as needed for the new software. This results in radically fewer code modifications. If changes are made to an original object, they are automatically brought forward to the new objects.

Why would you want to change to OOP from the familiar old world of modular programming? Because the old world is broken and OOP can fix it. Specifically, object orientation is designed to provide the following:

Now that you know what to look for from OOP, the following is an overview of the concepts that make up object orientation.

Objects and Encapsulation

If you've ever read a book or article about object orientation, you nght have noticed many terms thrown about as if they were self-explanatory. Terms such as inheritance, encapsulation, and polymorphism are used to express OOP concepts. Because of these odd and difficult terms, OOP is often thought to be complex. The truth could not be more different.

The first and most important key concept in OOP is an object. An object is a package of information and actions. An object is self-contained. An object has things on the inside that only it knows about, characteristics that everyone can see, and ways of doing things.

Use an elevator as an example. An elevator is an object in that it has properties (such as a maximum weight load), it performs actions (such as opening and closing the door), and it has a public interface (the control buttons) that enables it to interact with the environment around it. An object has precisely these qualities.

The key to understanding objects is to view them like an elevator. When you think about an elevator, all you think about is the public interface: the buttons that tell the elevator where to go, the door that opens to let you in and out, and so on. However, there is a great deal more to the elevator that you never think about. For example, you probably don't know how the elevator interprets the buttons you press into a destination or how the elevator knows which floor it is on. And, for that matter, you probably don't care. These functions, although a significant part of the elevator, do not concern you.

This introduces a few more concepts. First, all the knowledge and behaviors of an object are contained within the object. This is known as encapsulation. An object knows what it needs to know and how to do what it needs to do without relying on the outside world. The data and behaviors are encapsulated within the object.

Properties

The data in an object is called a property. A property (in Visual FoxPro terms) is simply a memory variable that is attached and scoped to an object. You query the value and modify it by using the object name followed by a period (.) and then the name of the property. (For the record, properties are also called member variables in some texts.) Properties can have any data types that are valid for a plain old Visual FoxPro memory variable. A property exists as long as the object it is attached to exists.

For example, if you have a property called lIsNew that is attached to an object called oCust, you could query the value of the property by stating the following:

? oCust.lIsNew

Methods

In addition to having attached data, objects have actions to perform. These actions are coded in procedures that are attached to the object. These procedures are known as methods. There is little difference between a "regular" procedure and a method except in the way they are called. You call a method by stating the name of the object followed by a period (.) and then the name of the method. For example, the following line illustrates how to call a method named Print that is attached to the oCust object:

oCust.Print()

Technically, the parentheses at the end of the method name are only required if you expect a return value or if you are passing parameters to the method. I suggest that you always use the parentheses for consistency. It also makes it clear that you're calling a method.

Notice, by the way, the manner in which this method is called. Unlike Visual FoxPro procedures and user-defined functions, you do not have to use a DO-type syntax or specify the function with an expression. To call a method that is a procedure (assuming you do not expect a return value), all you need to do is call the method as shown in the previous example. If you expect a return value, you can use the old UDF-type syntax. For example, if the Print method returns a logical stating whether the customer was properly printed, you could capture the value with this:

llReturnValue = oCust.Print()

Events

Events are things that happen. For example, clicking the mouse is an event. Events can be caused by a user's action (such as clicking a mouse) or by the system itself (such as when an error occurs). When you are creating a class (such as a pushbutton) in Visual FoxPro, you can attach code (such as methods) to events. When the event happens (for example, the user clicks the left mouse button while on the object), the associated method (that is, the click method) is automatically called.

Events are not new. Developers have been using them since the advent of FoxPro 2.0. Valid clauses, for example, are simply procedures that are attached to an event (such as attempting to exit a modified field or clicking a pushbutton). Part of the power of using objects comes from attaching methods to an object that automatically execute when something specific happens. In Visual FoxPro, you can attach methods to all kinds of events: when the mouse is clicked, when the mouse is released, and so on. You can even attach code to events that execute automatically when the object is created (the Init event) and when the object is released (the Destroy event).

There is one major difference between events in an object-oriented development environment and the old Valid and When clauses. In FoxPro 2.6, there was no direct method for manually executing the code snippet attached to an event (well, there was a way, but it was a kludge). In other words, there was no single command that ran the procedure attached to a screen object's Valid event. In an object-oriented development environment you can do this easily by calling the event the same way you would call a method. For example, if you have a pushbutton called cmdOK, you could execute the code attached to the Click event (that is, the Click() method) at any time by issuing the following:

cmdOk.Click()

By attaching code to events, you greatly increase the control you have over the behavior of an object. In fact, with the myriad of events to which Visual FoxPro enables you to respond, you can have acute, pinpoint control over forms and objects.

So far, you have seen that you can create objects as well as assign properties to them and create methods for them. If you had to write code to fine-tune an object every time you created it, you would be in for a lot of coding. Fortunately, OOP has the answer for this as well-classes.

Classes

To this point, all of the discussion in this chapter has centered around the object. But, what is the basis for an object? How is an object coded?

Consider a candle for a moment. A candle maker typically creates a candle from a mold rather than creating each one individually by hand. If the design of a candle needs to change, where would it be changed? On every individual candle? Not likely. Instead, the mold would be changed. When the mold is changed, all new candles get the change automatically.

In object-oriented programming, objects are never coded. Rather, object molds (called classes) are coded. All objects are then instantiated (that is, created) from that class. All coding is done at the class level. Once an object is instantiated from a class, all you do is interact with it. You do not add methods or change existing methods in an object, but rather you add and change methods in a class.

Here's an example of a class:

DEFINE CLASS myClass AS Custom
     cName = ""
     cType = ""
     lIsNew = .F.

     PROCEDURE ShowVals
          ? this.cName
          ? this.cType

          IF this.lIsNew
               ? "I'M NEW"
          ELSE
               ? "I'M OLD"
          ENDIF
     ENDPROC
ENDDEFINE

A brief dissection of this block of code follows; however, for a more definitive description of the syntax associated with coding classes, refer to Chapter 14.

Define Class myClass as Custom

This line of code tells Visual FoxPro that you are defining a new class called MyClass that is based on another class called Custom, which is discussed in more detail later in the chapter.

cName = ""
cType = ""
lIsNew = .F.

The preceding lines are known as declaration code. In this part of the class definition, you list the member variables (properties) of the object and their initial values. If one of the member variables is an array, the DECLARE statement would be placed here.

PROCEDURE ShowVals
     ? this.cName
     ? this.cType

     IF this.lIsNew
          ? "I'M NEW"
     ELSE
          ? "I'M OLD"
     ENDIF
ENDPROC

This is a method definition. Calling a method executes all the code from the PROCEDURE line to the ENDPROC line. A method is very similar to a procedure in a FoxPro 2.x procedure file, except that the method is called through its object.

Instantiating Objects

An object is instantiated with the CREATEOBJECT() function. Here is the syntax for creating an instance of MyClass:

oMyClass = CREATEOBJECT("MyClass")

By the way, oMyClass is simply a memory variable of type Object.

In order to access the members of oMyClass, you could use the following commands:

? oMyClass.cName    && Initially blank
oMyClass.cName = "Menachem Bazian"
? oMyClass.cName    && Now shows "Menachem Bazian"
oMyClass.ShowVals() && Runs the showvals method

Referencing a Method or Property in a Class

An issue that arises with this method of development is that it is difficult to know what the name of an object's instance variable is inside a class. When coding the class, the name of the variable that holds the instance should be, and is, irrelevant.

In order to refer to an object's properties or methods within itself, the identifier THIS, in place of the object name, is used. (You saw this previously in procedure SHOWVALS.)

IF this.lIsNew
          ? "I'M NEW"
     ELSE
          ? "I'M OLD"
     ENDIF

The keyword THIS means that you are accessing the method or member variable of the object itself. It's that simple.

Subclassing-Basing a Class on Another Class

So far, you have learned just about all there is to know about objects, and you have also learned about properties, methods, and events. You have also seen how to create an object's blueprint with a class, which you then use to instantiate the object. However, one more important piece remains (the really exciting part as it turns out): creating classes based on prior classes.

Suppose you have a class called Light that models a light in a room. The class would need a method for turning the light on and off and a property for the current status of the light. It might look something like this:

DEFINE CLASS light AS custom
  status = "OFF"

  PROCEDURE Toggle
   IF this.status = "OFF"
     this.status = "ON"
   ELSE
     this.status = "OFF"
   ENDIF
  ENDPROC
ENDDEFINE

In the sample Light class, you create an object that basically has one property and one method. This works well for light switches that just turn on and off. But suppose you want to create a light switch that can dim as well? What do you do? Do you have to write a whole new class? The toggle is still applicable: the light can still be turned on and off. What you need is a modified version of the Light class that has all the same capabilities of the Light class plus the capability of dimming the light.

For the purposes of this illustration, I'll set the following rules: When you attempt to use the dimmer, it goes from full light to half light and then back again. In order to turn the light on or off, you still need to use the original light switch method.

Here's how you could accomplish this using an OOP model:

DEFINE CLASS dimmer AS light
  intensity = "FULL"

  PROCEDURE DimmIt
   IF this.status = "OFF"
     RETURN
   ENDIF

   this.intensity = IIF(this.intensity = "FULL", ;
              "HALF", "FULL")
   WAIT WINDOW "Lights are now "+this.intensity+" power."
  ENDPROC
ENDDEFINE

Notice the original DEFINE of the class. In the original DEFINE (class Light), I used Custom as the base class. Custom is the simplest base class that is built into Visual FoxPro; you use it when you are creating objects of your own from scratch. (In Chapter 15 you learn what Custom is in more detail and how you use it.) In the DEFINE used here, the base class is Light. This means that class Dimmer automatically inherits everything that Light has. Thus, although no code exists in the Dimmer class to handle the LightSwitch method and the Status property, Dimmer gets the method and property automatically because it is a subclass of Light. This process is known as inheritance.

In effect, a subclass (Dimmer) is a more specialized version of the superclass (Light).

Overriding Inherited Behavior

One of the nice things about inheritance is that you can accept what you like from the superclass and override the rest. A method is overridden when it is "recoded" in the subclass. Here is an example:

DEFINE CLASS offdimmer AS dimmer
     intensity = "FULL"

     PROCEDURE DimmIt
          WAIT WINDOW "Dimmer is DIsabled"
     ENDPROC
ENDDEFINE

In this case, the DimmIt method has been overridden. The DimmIt method from the Dimmer class is not called.

Suppose you want to run the method from the Dimmer class's DimmIt method and then add additional code. Here's how you could do it:

DEFINE CLASS AnotherDimmer AS offdimmer
     intensity = "FULL"

     PROCEDURE DimmIt
          Dimmer::Dimmit()
          OffDimmer::Dimmit()
          WAIT WINDOW "Isn't this cool?"
     ENDPROC
ENDDEFINE

The double colon operator (::) is used to call methods from classes higher in the class hierarchy. Notice that you can only call methods from classes that have been inherited from.

Protecting Methods and Properties

When you create an object, you should take great care to decide what the public interface of the class is going to be. A class will typically have properties and methods that are intended for use inside the class only. Other properties and methods, if accessed from the outside, can have a disastrous effect on the inner workings of a class.

Consider the sample Light class and suppose that the Toggle method has code in it that turns the light on and off based on the Status property. If you modify the Status property by accessing it outside of the class, the Toggle method will not work properly.

The solution to this problem is to protect the methods and properties that should not be accessible outside of the class. When you code the classes you can protect the properties by adding PROTECTED propertyname definitions in the declaration section of the code. For methods, add the keyword PROTECTED to the PROCEDURE line. Here is an example:

DEFINE CLASS myClass AS Custom
     PROTECTED cName
     cName = ""
     cType = ""
     lIsNew = .F.

     PROTECTED PROCEDURE ShowVals
          ? this.cName
          ? this.cType

          IF this.lIsNew
               ? "I'M NEW"
          ELSE
               ? "I'M OLD"
          ENDIF
     ENDPROC
ENDDEFINE

In this example, both the cName property and the Showvals method are protected. Attempts to access them from outside of the class will produce an error as if the property and method did not exist (and as far as the world outside of the class is concerned, they don't exist).

If a class has a property that has to be tightly controlled (such as the Status property, which can only be changed by the Toggle method), you should protect it. If the user needs to read the value of that property, provide a method that returns the value of the protected property. For example, in order to access the cName property in the sample class (shown previously) you might create a method called ShowName as follows:

FUNCTION ShowName
     RETURN (this.cName)
ENDFUNC

Understanding Polymorphism

Polymorphism is the next term that needs to be covered in this discussion. Polymorphism is the capability to give methods and properties in different classes the same name even if they mean different things.

For example, consider the Light objects. They all have a method called Toggle, which turns the light on and off. Suppose now that you were to create an entirely different object: a telephone. The Telephone object might not have anything to do with a Light object, but there is a method attached to it that is also called Toggle. The Toggle method in the Telephone class might or might not do anything like the Toggle method in the Light classes.

Compare the following commands:

oLight = CREATEOBJECT("Light")
oPhone = CREATEOBJECT("Telephone")
oLight.Toggle()  &&Runs the Toggle method from
                 &&the Light object
oPhone.Toggle()  &&Runs the Toggle method from
                 &&the Phone object

Notice how similar the code is between oLight and oPhone. You can call the Toggle method from either object in a similar fashion although they might do different things.

Polymorphism is an extremely useful way to develop classes. It enables you to put standards in place for naming methods that do similar things. For example, you can have a Show method for different objects that is designed to bring up the display portion of the object (for example, oCust.Show() might display the customer form, whereas oInv.Show() might display the invoice form). The beauty of this comes from a user perspective. It means that you can use the objects with much greater ease because you can develop a consistent interface when you work with your classes (imagine the difficulty you would have if the display method were called Show in one class and Display in another).

Messages, Messages, Messages

Everything you have learned so far that deals with working with OOP-based systems can be described as "sending a message." The following sections redefine previous examples of working with objects and discuss them in terms of messages.

Creating an Object

oInv = CREATEOBJECT("Invoice")

This line of code sends a message to the Invoice class telling it to create an object called oInv based on itself.

Getting the Value of a Property

lnAmount = oInv.nAmount

This can be described as sending a message to the oInv object and telling it to get the value of the nAmount property and to return it to lnAmount.

Calling a Method

oInv.Display && Show the Invoice

This can be described as sending a message to the oInv object and telling it to execute its Display method.

If you understand the concept of a message, a great deal of the gobbledygook you read in OOP literature becomes understandable. For example, polymorphism has been defined in OOP literature as "the capability to send the same message to different objects and have different actions take place." The practical definition of a message presented in this chapter means the same thing, but it is easier to understand than the OOP literature definition.

The moral of this story is this: Do not let the language throw you.

Encapsulation Revisited

Taking the concepts that you have seen so far in this chapter, the concept of encapsulation becomes clearer. Basically, encapsulation means that an object is a self-contained unit. The object contains data in the form of properties (also called member variables) and methods that are associated with the object to perform whatever actions need to be done.

You can also create a Customer class if you want. You can associate data and methods with it that encapsulate customer information and actions.

A Customer object's data could be items such as a name, address, phone number, credit limit, and so on. Methods associated with the object could be actions related to displaying customer data, enabling the user to edit or add customers, printing a customer, and so on. If you develop naming conventions for your object methods and properties, using the objects become a breeze.

Naming conventions means adopting standards for the names of the methods and properties. A good example would be a method that displays an object (for example, the form associated with a customer object). This method could be called Show(), Display(), ShowForm(), or even ShowTheFormForThisClassRightNow(). To a degree, what you call it really doesn't matter as long as you are consistent. The only area where you might have problems is with some of the more popular type of actions such as Print(). If Microsoft has a method that does the type of action the method does (such as the Show() method that shows a form), it might make sense to adhere to that standard. I am not suggesting that you are bound to someone else's standard, but imagine the tower of Babel-type development that will occur if one class calls the method Show(), another calls it Display(), and yet a third uses the name ShowForm(). This is not a situation I would want to be in.

To illustrate polymorphism, take a look at the following example, which uses two sample classes: Customer and Invoice. Notice how the code, in order to work with the two classes, can be exceedingly similar at this level. In fact, by using OOP, the developer who takes the objects and puts them together in the form of a system will have a much easier job.

oCust = CREATEOBJECT("Customer")
oCust.Show()        && Show the customer
oCust.Edit()        && Edit the Customer
oCust.Save()        && Save Customer
oCust.Print()       && Print the customer

oInv = CREATEOBJECT("Invoice")
oInv.Display()      && Show the Invoice
oInv.Edit()         && Edit the Invoice
oInv.Save()         && Save Invoice
oInv.Print()        && Print the Invoice

OOP and its Effect on Development

Now that you have seen what objects are and what all the 10-dollar words mean, the next question is "Big deal. What does this do for me?"

OOP shifts the focus of development from coding procedures to the designing and defining of classes. Because objects are, in effect, complete and independent modules, it is possible to have developers who just work on classes of objects. The application developers can then use these classes, either directly or by subclassing them, and put them together to form a system.

Does this mean that after you have a library of classes you will never need to write code again? Not quite, but a class library will make your life a lot easier once it is developed, debugged, and ready to go.

Analysis and Design with OOP

Object-oriented programming does not start with code. Although the discussions in this chapter basically center around the implementation phase of writing objects, this step is the last step in creating object-oriented software.

The first step is to analyze the problem domain and to design the class hierarchies that make up the system. Sounds simple, right? In fact, object-oriented analysis and object-oriented design (OOA and OOD) are not difficult processes to perform, they just require discipline. Many different methodologies have been proposed and written about over the years. The more popular authors in this realm are Grady Booch, Ivar Jacobsen, and Rebecca Wirfs-Brock. Personally, I use the Wirfs-Brock method of CRC cards, but that is a whole different story. Her book, Designing Object-Oriented Software, should be required reading for all developers working on object-oriented analysis and design, in my opinion. But don't go by just what I recommend because others feel strongly about other authors.

Analysts have stated that as much as 70 percent of the time allotted for an object-oriented project is spent on analysis and design. That's a lot of time. Therefore, you should take the time you need to do your research and to find a methodology with which you are comfortable.

Whichever methodology you choose, the end result of object-oriented analysis and design is a coherent, well thought-out, logical class design that will clearly show the classes in the application as well as the public interface (that is, the unprotected properties and methods) and how they interact. When you have all this, coding can begin.

Multideveloper Issues with Object-Oriented Software

The focus of object orientation, which is based on objects rather than modules, has another interesting effect on the development process: It makes multi-developer situations much easier to handle. If the analysis and design phases have been properly done, the system is broken down into discrete pieces. The design will call for a particular class hierarchy and will state what each class can expect from other classes in the system. After you accomplish this, coding each class tree can be done independently of others in the system.

For example, the design document for a system calls for a Customer class and a Statement class. The Customer class calls the Statement class to create and print a statement that shows open invoices for customers. The design document states what methods and properties are available in the Statement class. There might be a Create() method that creates the statement and accepts a customer number as a parameter, a Print() method that prints the statement, and a property called cStatementNumber for the statement number. The developer of the Customer class could write the following method:

PROCEDURE CustomerStatement
     LOCAL loStatement

     *-- The next line creates the object
     loStatement = CREATEOBJECT("statement")

     *-- Now create the statement's contents.
     loStatement.Create(this.CustomerNumber)

     *-- Now print the statement
     loStatement.Print()

     *-- Finally, tell the user that we are done
     WAIT WINDOW "Statement number " + ;
                    loStatement.cStatementNumber + ;
                    " has been created and printed!"
ENDPROC

You can write this method without ever seeing a line of the Statement object's code. In fact, this is part of the whole point. Just like you could not care less what makes an elevator work, you could not care less what makes the Statement object tick. As long as the developer who created the Statement class did the job properly, you can rely on the design document to know how you need to interact with the class. That's all you need.

You can test the method by trading files. You should never have to modify someone else's code-it's none of your business.

By segregating development in this theoretical manner, OOP creates a situation that minimizes the need to modify shared code and, therefore, makes multi-developer teams easier to manage.

Of course, there is another issue to be dealt with here: the issue of integrating classes created by developers into the application's class libraries. This is the job of the class librarian. For more information on the class librarian, refer to Chapter 16.

System Maintenance with OOP

Users like to change things, right? Using the light example, suppose that the user changes the base definition of the light switch. In this example, a light switch has only one property (Status) and one method (Toggle). Suppose that the company redefined the base light switch (the Light class) to add another feature. Now, when the user turns the light off or on, the system tells the user what has been done.

In order to accomplish this, all you need to do is to modify the Light class definition as follows:

DEFINE CLASS light AS custom
     status = "OFF"

     PROCEDURE LightSwitch
          IF this.status = "OFF"
               this.status = "ON"
          ELSE
               this.status = "OFF"
          ENDIF
          WAIT WINDOW "Light is now " + this.status
     ENDPROC
ENDDEFINE

From this point on, all objects instantiated from the Light class get the changed method. In effect, you have changed the behavior of every object based on this class by adding one line of code to the class definition.

But wait, there's more. Not only have you modified all the objects based on the Light class, but you have also modified every object based on subclasses of light (Dimmer, for example). This is a powerful way to develop reusable code.

The flip side of this is that if you break a class, you might also break all the subclasses based on it, regardless of the application in which the class is used. If you have used a class in a production application, you'll need to be very careful with this.


© Copyright, Sams Publishing. All rights reserved.