Information Technology Consulting & Solutions

Home
Up
About Us
Our Clients
FAQ
Recent News
Tech Tips
Links
Mailing List
Contact Us
Client Access

Knowledge Base

OOP 101

Copyright 2000 by Data Systems Consultants, Inc.

Here are the basics of Object Oriented Programming (OOP) and a few key differences between OOP and more traditional programming methods.

What Is OOP?

OOP is a set of concepts and tools that allow programmers to better address the new operating systems and graphical user interfaces common on today’s computers. OOP concepts stand on three pillars. These are:

Encapsulation

Inheritance

Polymorphism

We’ll take a look at these concepts in detail below but, for right now, think of objects in programming in the same way you think of objects in the real world. Your keyboard is an object and so is your mouse. The two are totally separate but they are used together to form a common input method for you to interact with your computer.

Why Objects?

The world got along fine without objects for a lot of years, so why is it so popular now?

In the good old days all sophisticated languages were procedural by design. Generally, a procedural program demands that the user interact with it in a top down order. In other words, the program controls the user’s flow through itself. This means that assumptions can be safely made regarding the state of variable definitions, databases and memory because the program knows exactly how it got to where it is currently executing.

The world changed when Windows was released. By definition, Windows is an event driven operating system and applications that are truly windows compliant must also be event driven. What does event driven mean? Simple. It means the user controls the flow of the application rather than the application controlling the flow of the user.

For example, consider a simple edit window for edit of names and addresses. In an old DOS application, the entry fields would be presented on the screen and the user would be allowed access to the fields in a certain order:

  1. Name
  2. Street
  3. City
  4. State
  5. Zip Code

Typically, when the user entered the zip code and pressed the enter key, the record would be validated and saved. The program knew that if a save attempt was made, the user had to have entered all of the information required or at least would have been led through the fields in a certain order.

Event driven systems are different. Here the user can enter information in any order. In our example, there’s nothing to prevent this order.

  1. Zip Code
  2. Street
  3. Name
  4. Click OK Button
  5. State
  6. City

In other words, by using the mouse, the user can jump to any field on the window or even generate a new window without saving the first. All of those mouse clicks the user is using to jump around are events and, for the application to be event driven, it must accommodate any silly thing the user might choose to do.

Let’s take a look at the code for a procedural function that performs this function. This is Clipper code but you should be able to follow along.

‘ Var Declaration
public cName
public cStreet
public cCity
public cState
public cZipCode
public lValid

function GetAddress
‘ Gets and validates user input for a name and address.
‘ Clear the vars for entry.

cName = ""
cStreet = ""
cCity = ""
cState = ""
cZipCode = ""

‘ Assume user input is invalid.
lValid = .f.

‘ Clear the screen and define entry fields.
clear screen
@ 1,5 get cName
@ 2,5 get cStreet
@ 3,5 get cCity
@ 3,31 get cState
@ 3,34 get cZipCode

do while .not. lValid
    ‘ Get user input to fields.
   
read
    ‘ Validate address
   
lValid = ValidAddress()
   
if .not. lValid
   
ErrorPanel("Invalid Entry! Try Again.")

loop ‘ Back to the top of the do loop.

‘ To get here, the user must have entered valid data.
‘ Add a new record to address table and update the fields.
append blank in address
replace name with cName
replace street with cStreet
replace city with cCity
replace state with cState
replace zipcode with cZipCode
return

Okay. Let’s go through this and see what’s happening. First, we’re defining a bunch of public variables to hold the user’s input. I’ve said this is a no-no so why is it done? It’s okay here because this program controls the user and, by declaring these variables public they are visible to the ValidAddress function. That means I don’t have to pass them as parameters to the function. Let’s go on and you’ll see why it doesn’t matter that these are public.

We define a boolean flag to test for validation of the information entered then we start a do loop that won’t exit until the user has entered valid information. "Ah Ha!", the program says, "Now your trapped! You can click that mouse all you want but all I’m going to let you do is enter text into these fields. And, if you ever want to escape my grasp you better do it right!"

In reality, we’d leave the user a way to cancel but you get the idea. The program is locked into one specific task and will allow only that task to take place until it’s completely finished. The user has no say in the matter. This means the program can make some big assumptions.

First, we know that the information to be written to the database will be correct because there’s no way to get out of the loop until it is.

Second, we can define public variables because, after valid entry, the user has no way of interrupting the process until the data has been written to the database.

Third, should the user want to enter another address, the variables are cleared before the entry. This means we don’t really care if the variables are tromped on after the first save and before the start of the second entry. The first entry’s data is in the database and the variables are not needed until the next entry starts.

"Wait!", you might be saying, "What about other routines that use these variables! If we arbitrarily clear them couldn’t we be losing data set by another function?" Theoretically yes and this is a good argument for not using public variables even in procedural programming but, keep in mind that another function that uses these variables is responsible for it’s own data and should initialize the variables for its own use when it is called.

The important thing to remember is that this application can do only one thing at a time and it’s very picky about what it will allow the user to do while doing that one thing.

Now. If this same program were written in VB all bets are off. Since Windows and VB applications are event driven there is no telling what the user might do. Given this, some obvious issues come up.

  • The user can spawn another window just like this one by clicking a tool bar button. Unless we prevent this, our public variables are tromping on each other. When the user changes the street variable in one window, it’s going to show up in the other window. There’s no way the user can edit two addresses correctly at the same time but there’s nothing to stop her from trying unless we stop her which requires more code. Also, if we do prevent this, we’re violating the event driven model. We want the user to be able to choose her own course of action as much as possible.
  • There’s nothing to prevent the user from clicking the OK button to save the record even if no information has been entered. The OK button is a totally separate object and the click of the button is a totally separate event. So the user can very easily write invalid data to the database.

You can see that, in an event driven environment, we can no longer make assumptions about the state of the system. In fact, the only assumption we can safely make is that the user is going to do something totally unexpected. This means that each component of the application must be able to work alone as a complete and independent mini application in its own isolated world. This concept of independence and isolation is the first pillar of the three pillars that hold up the object oriented concept and it’s called encapsulation.

Pillar One - Encapsulation

Encapsulation means that data takes on a life of its own within an object. Instead of directly manipulating that data, you send the object messages. The object accepts those messages and uses its methods to manipulate its properties based upon rules that the object defines. The data that would otherwise be stored in variables is instead stored in these properties. So, the methods and events (functions and subroutines defined within the object) and the properties (essentially variables) are all wrapped up and isolated within the object.

Better yet, if you have two objects of the same type, when you send a message to one of the objects the other is not affected. So again, each object is its own little encapsulated world that sends and receives messages to the outside world.

Let’s take a look at a simple line of code to see what’s happening behind the scenes.

txtZipCode.Value = "73127-6244"

We know txtZipCode is an object of type TextBox and we know it has a property called Value. This means we also know that when we assign a value to this property, we are actually sending a message to the object. Here’s a way to look at this line of code that shows what’s really happening.

txtZipCode.Value.Let("73127-6244")

The object has a method, called "Let", that sets the value property when you send it a message to do so. All objects that have properties have a corresponding Let method for setting each property. Here’s what it looks like. This is only general. The variables may actually be typed more explicitly than this depending on the object type.

Property Let Value(ByVal vPassedValue as Variant)
   
vValue = vPassedValue
End Property

The Let method of the property assigns the value of the message’s passed string to vValue which is a variable defined in the object that contains the value of the property. Now, note that this looks an awful lot like a sub routine. Well it is, but it’s a special kind that is associated to properties of objects. Note that the let method could have some validation code that prevents a zip code value from being entered incorrectly before the assignment. In fact, by defining your own properties to objects, you can do anything you want. Okay, how about this statement.

MsgBox(txtZipCode.Value)

Again, we are sending a message to the object. In this case the message requests that the object returns the value of the Value property and just as the let method sets the value, there is a "Get" method that gets the value. Here’s the call with the Get included.

MsgBox(txtZipCode.Value.Get)

And here’s the actual get defined in the object.

Property Get Value() as Variant
   
Value = vValue
End Property

This one looks and acts like a function and returns the value stored in vValue.

So, in understanding objects, it’s always important to remember that you never directly modify an object’s properties. Rather, you send messages requesting the modification and the object decides whether or not this is okay. That’s encapsulation.

Okay fine. But, when we design a system in VB we define only one address edit window. How can the user have two running simultaneously if we define only one?

This is also part of encapsulation but in a broader sense. What happens is this. When you define the window in VB, you are defining an object class. A class can be thought of as a template. When the user requests an address edit window, the application looks up the address window class and copies it to an instance of the class. That is, an instance of the class is instantiated. The user never, ever works directly with the class. Rather, the user works with instances of the class and may create as many instances as necessary to perform the required task(s). Let’s look at the code that creates the instance. You’ve seen this before although you may not have realized exactly what you were seeing.

Private Sub mnuEditAddress_Click
‘ Define an object variable to hold the instance’s
‘ memory address.

Dim oForm As New frmEditAddress
‘ Instantiate, load and show the new instance.
    oForm.Show
End Sub

This is the click method of a menu option. The first thing that happens is that an object variable, named oForm, is created. After the execution of the Dim statement, oForm is equal to Nothing which is a special value for object variables that have not been assigned an instance. Note however that it is assigned a type, frmEditAddress, which is the class we created for our window for editing address.

Also note that we’ve added the New keyword to let VB know that when this object is assigned an instance it needs to be a new instance rather than an existing instance of the class.

Okay, next line. VB does an awful lot of things in the background before actually showing the form. First, it notes that oForm is Nothing so it looks up the class in the application’s type library and creates an instance of it. Next it loads the instance into memory and assigns the memory address to oForm. Next, it runs the various startup methods for a form class (instantiation of objects in the form, form load, form activate, etc.) and finally makes the form visible and on top in the window Z order. That’s a simplified description of what happens behind the scenes but it illustrates the basic idea. The result is that the user has a new instance of the class which is assigned to an object variable. The object is now ready to send and receive messages.

Now you may notice one other thing. The oForm variable we created is scoped locally to the menu’s click method. Once this method completes, oForm is destroyed. How can the form be accessed without that object variable? Well, VB does some more stuff behind the scene. Anytime an object is instantiated, VB creates an implicit object variable to reference it. This way, the form can still be referenced for messages after the local variable is destroyed.

Pillar Two – Inheritance

VB calls this "reusability" but the rest of the world calls it inheritance. Inheritance means being able to create a new class of object that uses the features of an existing class without having to recode those features. This is called subclassing. Here’s an example of object inheritance and subclassing. First let’s define a parent class and two subclasses:

Class – clsAnimal

Properties:

  • Head
  • Body

Methods:

  • Eat
  • Sleep
  • Breathe

Subclass – clsCow of clsAnimal

Properties:

  • Legs
  • Tail
  • Horns

Methods:

  • Graze
  • Moo
  • SwishTail

Subclass – clsHuman of clsAnimal

Properties:

  • Legs
  • Arms
  • Hands

Methods:

  • Play
  • Work
  • Speak

Okay. Now let’s use these in some code.

‘ Define object vars for Robert and Elsie.
Dim Robert As clsHuman
Dim Elsie As clsCow

‘ Instantiate Robert and Elsie.
set Robert = New clsHuman
set Elsie = New clsCow

The next two lines of code will create errors.

Robert.Graze
Elsie.Play

This is because the human class does not have a graze method and the cow class does not have a play method. These two lines of code will also create errors.

Elsie.Arms = 2
Robert.Tail = 1

These properties are not defined for the respective classes.

The following lines of code will work.

Robert.Play
Elsie.Graze
Elsie.Horns = 2
Robert.Arms = 2

These lines of code will also work.

Robert.Head = 1
Elsie.Head = 1
Robert.Breathe
Elsie.Breathe
Robert.Eat
Elsie.Sleep

These methods and properties are inherited from the parent clsAnimal class.

Now suppose we added a property to the clsAnimal class called Organs. Both the clsCow and clsHuman classes would automatically inherit the Organs property.

Some real life examples? How about a text box and a label. These are both subclasses of the window class (Yep. All controls are windows.) and have some common properties inherited from the Windows parent class, Height, Width, Top, Left, etc. They also have unique properties, for instance, text box has a Text property and label does not. Same thing on methods.

I suspect that the reason why VB calls inheritance reusability is because VB really doesn’t do inheritance well. For what it does do, reusability is a better description. For example, the standard forms, frmAbout and frmTechSupt are reusable. Since they are forms, they are object classes and inherit properties and methods from their parent class "Form", and since they are fully encapsulated, they may be added to any project we wish.

Further, if we change either of these forms, the changes are automatically inherited into any project that uses the forms. However, technically that’s not really inheritance. True inheritance would allow us to create a subclass of frmAbout and add features to the subclass. VB does not do this, at least not easily. Realistically, about all we can do is copy the form and enhance the copy. This creates a new subclass of class Form. Not a subclass of class frmAbout.

"Wait!", you say, "What about creating Active X Controls? Isn’t that subclassing?" No. Not really. When VB defines an Active X control what it is creating is a container object that contains other controls. While this can be very handy, it’s not subclassing.

Pillar Three – Polymorphism

Polymorphism means that any object will be able to do the right thing if you pass it a message that it understands. A simple example. Imagine three objects:

  • John – Class Programmer
  • Paul – Class Lumberjack
  • Sam – Class Butcher

Now I write the following code to send a message to each of these objects:

John.Hack
Paul.Hack
Sam.Hack

The message I send to each object is identical but the results are quite different:

  • John sits down at his computer and writes bad code.
  • Paul picks up an axe and chops down a tree.
  • Sam picks up a cleaver and turns a side of beef into hamburger.

The key here is that each object knows what to do with this particular message in its own context. This means that when I use these objects in my code, I don’t need to worry about what will actually happen when I send a message. Each object knows intrinsically what it needs to do with that message and behaves accordingly.

In other words, I need never worry about chopping down some spotted owl’s home when I pass a hack message to the programmer class. This makes my life a lot simpler. If this were not the case, I’d have to add code to my application to make sure the programmer was no where near a tree before passing the hack message.

Here’s a real world example of polymorphism. Take a look at the Option Button and Check Box controls. Each has a value property but Option Button returns a boolean true or false and Check Box returns a 0, 1 or 2. Nevertheless, both controls will respond correctly to a Value message.

Some OOP Do’s and Don’ts

The following are some techniques that help us use objects effectively.

  1. Do declare variables in form module headers with DIM instead of Public. Variables declared with Dim in module headers are by definition global to the module in which they are defined but are still encapsulated from the outside world.
  2. Do declare variables in form module headers with Public when you are creating a form property. Property? Yep. Forms are objects and the module code is where properties and methods are defined when the inherited properties and methods are not adequate. When you define a public variable in the header of a form module you are essentially creating a property that can be accessed like this:
  3. frmSomeForm.VarName

    So, it is perfectly acceptable to create a public variable in a form module header when you need to set the value of the variable outside of the form.

    Here’s an example of such a use. Suppose I had a search window for a database that has an edit button. I highlight a record in the grid of the search window and then click the edit button. Here’s the edit button’s click method.

    Private Sub cmdEdit_Click
    ‘ Call the edit window with the highlighted record’s UID.
    Dim oForm as New frmEdit

    ‘ Pass the current record’s UID to the edit window.
    oForm.nUID = rstSomeRecordSet!UID
    oForm.Show

    End Sub

    Here’s the edit form’s module header:

    Option Explicit
    ‘ frmEdit – Edits the record of passed UID. Called from frmSrch.
    Public nUID as Long ‘ Public var to receive UID of record to
       
                                      ‘ edit.

    Dim rstSomeRecordSet as RecordSet
    Dim lFirstTime as Boolean

    The edit form’s load event:

    Private Sub frmEdit_Load()
    ‘ Init the form and check for proper pass of UID.
    ‘ Did we get a good UID?
    If nUID = 0 Then
        ‘ Nope. Let’em know and exit.
       
    MsgBox "Invalid call to frmEdit." & vbCRLF & _
       
    "Please report this error.", vbCritical _
       
    "System Error!"
       
    Unload Me
    End If

    ‘ Got a good ID. Set the first time flag for activate method.
    lFirstTime = True
    End Sub

    The edit form’s activate event:

    Private Sub frmEdit_Activate()
    ‘ If lFirstTime run the init code.
    If lFirstTime Then
        set rstSomeRecordSet = dbSomeTable.RecordSet

        ‘ Locate the record to edit.
       
    With rstSomeRecordSet
       
         .FindFirst "UID = " & nUID
       
         If .NoMatch Then
       
             ‘ Record not found. Abort the edit.
       
             MsgBox "Invalid Record ID" & vbCRLF & _
       
                 "passed to Edit Form." & vbCRLF & _
       
                 "Please report this error.", _
       
                 vbCritical, "System Error!"
       
             Unload Me
       
         End If
       
    End With
       
    lFirstTime = False
    End If

    End Sub

  4. Do declare subroutines or functions in form modules as Public when they need to be called from outside the form. This makes the sub or function an exported method of the form object and it can be called like this:
  5. frmSomeForm.SomeRoutine

    An example of this might be for an initialization function that needs to be run before the form is visible. This is also a slick way to send multiple parameters to a form. Maybe something like this:

    frmSomeform.Setup nUID, nAction

    The module might have the following variables defined.

    Dim nUID as Integer
    Dim nAction as Integer

    Note that these are declared with Dim. They don’t need to be public in this case. You’ll see why in a minute. Here’s the Setup routine:

    Public Sub Setup(ByVal nPassedUID as Integer, _
       
    ByVal nPassedAction as Integer)
    ‘ Define a static var that will prevent this sub from running
    ‘ more than once in the life of the form. As a boolean, it
    ‘ will be initially defined as false.

    Static lIveBeenCalled as Boolean

    If Not lIveBeenCalled Then
       
    ‘ Assign the passed parameters to the module variables.
       
    nUID = nPassedUID
       
    nAction = nPassedAction
       
    ‘ Don’t allow these values to be set by a second call to
       
    ‘ setup.
       
    lIveBeenCalled = True

    End If

    Exit Sub

    The module variables don’t need to be public because they are set in a routine that is local to the module. This protects them from outside influence. However, to make the Setup routine an exported method, it must be declared as Public.

    The advantage to this method is that once the variables are assigned by the setup routine, they can’t be changed from the outside for as long as this instance of the form is alive. In other words, they are encapsulated.

    This is controlled by the static variable in the Setup routine that prevents the assignments from being repeated. The only time the assignments can be made is on the first call to Setup. This encapsulation can never happen with publicly declared variables. They are always available to the world.

  6. Don’t define variables in the form module header if they can be defined within a sub or function instead. Just as objects are encapsulated, we also want to encapsulate subs and functions. This way we are assured that we won’t be tromping on variables and it makes the sub or function reusable in other modules or in library code.
  7. The only time it is absolutely necessary to define variables in module headers is when they need to be public to the world. Other examples of appropriate usage are for variables such as recordset variables, module wide flags like lFirstTime and other applications where the values are set once and then not changed regularly.

    In general we would prefer to pass parameters to other functions or create subroutine within functions and subs to share variables. Here’s an example of parameter passing.

    Private Sub CustomPrint
    ‘ Prints a custom report for this form.
    Dim nPage as Integer
    Dim nLine as Integer

    nPage = 1
    nLine = 1

    ‘ Print the report header on the first page.
    nPage = CustomPrintHdr(nPage)
    Do While Not rstSomeData.EOF
       
    ‘ Code to print a line.
       
    nLine = nLine + 1

            ‘ Is this page full?
       
      If nLine >= 60 Then
       
             nPage = CustomPrintHdr(nPage)
       
             nLine = 1
       
      End If
        rstSomeDate.MoveNext

    Loop

    Exit Sub

    Private Function CustomPrintHdr(byval nPage as Integer) _
       
    as Integer
       
    ‘ Prints the header for custom print.
       
    ‘ Set return value.
       
    CustomPrintHdr = nPage + 1

            ‘ Code to print header.

    Exit Function

    Note a couple of things. First, I was able to declare the shared variable nPage as local to the CustomPrint routine because I pass it as a parameter to CustomPrintHdr. Also, I was able to pass nPage by value because I return the new page number from CustomPrintHdr function.

    There’s one more thing to notice. This is a bad example. I can just as easily increment nPage in the CustomPrint routine. I did this on purpose to show something that you should watch for. Are you sure you need that parameter? You don’t if you can update the value in the calling program.

    Now, here’s an even better way. Define the header routine as a subroutine within CustomPrint. You do this when you know that this routine will be needed only by the main routine. This is almost always the case with report headers and other similar applications.

    The big advantage to this is that all of the variables defined in the calling routine are visible and updateable by the sub routine. Here’s what the code looks like:

    Private Sub CustomPrint
    ‘ Prints a custom report for this form.

    Dim nPage as Integer
    Dim nLine as Integer
    nPage = 1
    nLine = 1

    ‘ Print the report header on the first page.
    GoSub PrintHdr

    Do While Not rstSomeData.EOF
    ‘ Code to print a line.
        nLine = nLine + 1
       
    ‘ Is this page full?
       
    If nLine >= 60 then
       
         GoSub PrintHdr
       
         nLine = 1
       
    End If

        rstSomeData.MoveNext
    Loop

    Exit Sub ‘ DON’T FORGET THIS EXIT!

    PrintHdr:
    ‘ Subroutine to print header for custom print.
    ‘ Code to print header.
    nPage = nPage + 1

    Return

    Exit Sub

    Note that, by using a subroutine, we’ve created a single function that has conditional branching and that is fully encapsulated. This code stands on it’s own as long as rstSomeData is defined.

    In the previous example, CustomPrint and CustomPrintHdr are dependent on each other to work properly. By using subroutines all of the code necessary for the report is wrapped very neatly in the CustomPrint call.

    Some programmers would say that the use of subroutines is a bad practice. I believe it’s a great tool and, like all great tools it can be abused. But when it’s used correctly it does a great job. By far, the biggest advantage is that all of the variables defined either by the main routine or the sub routines are encapsulated within the main routine and are visible to all code that needs them with no parameter passing.

  8. Do use the simplest control you can to accomplish the task at hand. It’s very easy to get into the habit of using a more complex object than necessary because we are familiar with it. For instance, if you need to display read only data on a form, a label will suffice just fine. Label objects are much simpler, smaller and faster than any kind of text box so, in this case, using a label makes smaller, faster, cleaner code.

In a nutshell, that’s OOP and how we use it.

Return To Top