Objects and Classes

All objects are defined by a class. A class is essentially a template from which the object is created, which means that we can create many objects based on a single class. Each object is referred to as an instance of the class.

The word class is pretty descriptive, since we're basically classifying our objects. For instance, if we have a couple of objects, Squirrel and Rabbit, they could be instances of a Mammal class.

To use a more business-like example, we may have a Customer class. From this class we could create instances to represent each our customers, such as Fred Smith and Mary Jones. Each actual customer would have its own object, but all the objects would be created from the same template, the Customer class:

In Visual Basic, we define classes using class modules. We can then create objects based on the class module, with each object being an instance of the class.

Class modules are like form or standard modules, in that they have a declaration section followed by a series of Sub, Function and Property subroutines. The difference is that the variables and code in a class module can only be used by creating an instance of the class.

Let's take a look at how Visual Basic's class modules are used to provide a template for our objects.

Objects Contain Data

All objects contain data about themselves. For instance, an object representing a simple cardboard box might have data about the box's height, width, and depth. The object might also know whether the box is open or closed. Furthermore, we might have many box objects, each with its own height, width and depth. Each of these objects could be an instance of a Box class.

Data values in objects are stored in instance variables. Each instance of an object has its own set of variables, separate from the variables of any other object, even those of the same class. In this way, two objects of the same class can each have their own individual data, so each of our earlier box objects can have its own height, width, and depth.

In Visual Basic, a class's instance variables are stored as module-level variables inside a class module. For example:

Option Explicit

Private dblHeight As Double
Private dblWidth As Double
Private dblDepth As Double
Private blnOpen As Boolean

As with any other module, module-level variables in a class module can be declared as either Public or Private.

Private variables are only accessible to code within the class module, and they are the preferable way to manage data within objects. For any client code to manipulate a Private variable, it must call our object's code to do the manipulation; it can't do anything directly. This gives us a lot of control, since we can implement business or validation rules against any values that the client code attempts to change.

Instance variables declared as Public are accessible to code within our object, but they are also directly accessible to any client code written to use our object. Use of Public instance variables is very bad practice. Any client code can directly manipulate a Public instance variable, without going through any of our object's code. This means our object gives up all control over the value, trusting the author of the client code not to break any rules or do anything that is invalid.

One of the primary principles of object design is encapsulation, a concept we'll explore throughout the rest of the book. Encapsulation means that objects should hide their internal design and data from the code that uses them.

Private variables help by forcing any client code to use our code to change our object's data, thus allowing us to change how the object is implemented at any time with little or no impact on other programs that use this object. Public variables directly break the principal of encapsulation, providing a client with direct and uncontrolled access to an object's data.

Objects Have Behaviors

Of course, the whole idea behind having an object is so our programs can interact with it. If we're encapsulating an object's data then we need to provide the ability for the client programmer to access the information that the object holds.

Objects need to provide an interface to allow their client programs to gain access to these services. Just as in the real world, where objects interact - it is our aim in programming with objects to facilitate and control their interaction - via properties, methods and events. These form the interface the object presents to the world.

In Visual Basic, an object's interface is composed of Property, Sub and Function routines, as well as any events declared using the Event keyword. Any of these routines declared as Public within the class module become part of the object's interface.

In reality, an object can have many interfaces, allowing client code to use the object in different ways as appropriate. We'll cover this in more detail in the next section.

Properties

A property is an attribute that describes the object. Objects often have many properties to provide client code with access to all of their attributes.

It's important to make that an object's properties are not the same as its data. Objects often contain data that would not be considered to be an attribute of the object. For instance, a Person object may have an Age property, but the underlying data might be a birth date. The birth date itself may or may not be available as a property of the object.

Properties allow us, as the object's designer, to pick and choose what information we want to make available about our object. We can also choose which property values can be changed by our object's users. Some of that information may come directly from our object's data, while other information may come from calculated values or other sources.

So in our earlier box example we may not care to let other programs see the box's dimensions, but we may want them to be able to get at the box's volume:

Public Property Get Volume() As Double
  Volume = dblHeight * dblWidth * dblDepth
End Property

Visual Basic provides us with three different types of Property routines. Property Get routines are used to retrieve a property from an object. Then there are two routines to put values into a property, depending on whether the value is a reference to an object or not. Property Let routines allow client code to put any value into a property other than a reference to an object, while Property Set routines are used only for object references.

Properties can be read-only (Property Get), write-only (Property Let/Set) or read-write (both properties). Even if we include a Property Let or Property Set routine for a property, we can include code in that routine to validate the new value or check any other business rules before allowing the client code to change the value.

Methods

Objects, like their real-world counterparts, need to provide services (or functions) when they interact. Using their own data, or data passed as parameters to the method, they manipulate information to yield a result.

Methods are simply routines that we code within the class to implement the services we want to provide. Some methods return values or provide information back to the calling code. These are called interrogative methods. Others, called imperative methods, just perform a service and return nothing to the calling code.

In Visual Basic, methods are implemented using Sub or Function routines within the class module that defines our object. Sub routines may accept parameters, but they do not return any result value when they are complete. Function routines can also accept parameters, and they always generate a result value that can be used by the calling code.

The difference between a Function routine and a Property Get routine is quite subtle. Both return a value to the calling code and, either way, our object is running a subroutine defined by our class module to return the value.

The difference is less programmatic than it is a design choice. We could create all our objects without any Property routines at all, just using methods for all interactions with the object. However, Property routines are obviously attributes of the object, while a Function might be an attribute or a method. By carefully implementing all attributes as Property Get routines, and any interrogative methods as Function routines, we will create more readable and understandable code.

Using our now familiar box example, programs may need to open or close the box - so we need to provide a way to do this:

Public Sub OpenBox()
  blnOpen = True
End Sub

Public Sub CloseBox()
  blnOpen = False
End Sub

With this approach, our client code might look like this:

  Dim objBox As New Box

  objBox.Open
  objBox.Close

Where this gets more useful is when we add code to check our rules. We shouldn't be able to open an already open box, or close an already closed box. With absolutely no impact to our client code, we can add the following rules to the Box class:

Public Sub OpenBox()
  If Not blnOpen Then Err.Raise vbObjectError, _
    "Box already open"
  blnOpen = True
End Sub

Public Sub CloseBox()
  If Not blnOpen Then Err.Raise vbObjectError, _
    "Box already closed"
  blnOpen = False
End Sub

In this case, we're simply raising an error if the client code tries to call either the OpenBox method when the box is already open or the CloseBox method when it's already closed. The real key, here, is that the implementation of these methods is totally encapsulated within our class module. We were able to make these changes without having to make any changes to our client code.

Events

In object-oriented design, the concept of an event is pervasive. In OO parlance, objects act in response to events, such as a user providing a value, or another object calling one of our object's methods. In the Visual Basic 5.0 environment, we have access to a somewhat different type of event.

Any Visual Basic developer is used to writing code to respond to events. Controls on forms generate events continually, and we write code behind those events to take appropriate action. For instance, a CommandButton control provides a Click event, firing it any time the user clicks on the button. We then write code to respond appropriately:

Private Sub Command1_Click()
  MsgBox "The user clicked the button"
End Sub

Before Visual Basic 5.0, there was no way for a Visual Basic developer to cause events, only to respond to them. Now we can use the Event statement to declare an event and the RaiseEvent command to fire it off.

Going back to our Box class, let's allow the user to stretch our box by implementing a Stretch method:

Public Sub Stretch(Percentage As Double)
  dblHeight = dblHeight * Percentage
  dblWidth = dblWidth * Percentage
  dblDepth = dblDepth * Percentage
End Sub

As a convenience, perhaps we want to raise an event to let any client programs know that our box has a new size. Visual Basic makes this very easy. We just need to add an Event declaration in the (General) (Declarations) section of our Box class module:

Event Stretched()

Events are always Public in scope, since they're always raised back to whatever client code has a reference to our object.

With our event declared, we can now fire it off using the RaiseEvent command:

Public Sub Stretch(Percentage As Double)
  dblHeight = dblHeight * Percentage
  dblWidth = dblWidth * Percentage
  dblDepth = dblDepth * Percentage
  RaiseEvent Stretched
End Sub

We'll go into much more detail about events in Chapter 3.

Multiple Interfaces

So far, we've looked at an object as having a single interface composed of its Public Sub, Function, and Property routines, along with any events it has declared. With Visual Basic 5.0, things are a bit more complex. Our objects can actually have many different interfaces simultaneously, although a client can only use one at a time.

If we create an object from a class we built in Visual Basic, that object will have at least the one interface we defined in our class module. In this simple case, it appears as though our program has direct access to the object, although in reality we're getting at the object through an interface defined by the class:

With Visual Basic 5.0, a client program never has direct access to an object, only to one of the object's interfaces.

Sometimes, one interface isn't enough. Our object may represent more than one real world entity, and we need to be able to model this in Visual Basic. For instance, a Customer object is great, but a real customer is also a person. To model this in our application, we might need our Customer object to also act like a Person object from time to time.

To accomplish this, we can just add the Person interface to our object. Then a client program can use our object through the Customer interface with its set of properties and methods. It might also use the object through the Person interface, with its own separate set of properties and methods. This way, the client can use the object in whichever way is appropriate at any given time:

As we've seen, when we create a class in Visual Basic, its Public members comprise the interface for our object. However, we can also use the Implements statement to add another interface to our object. The Implements keyword requires that we supply a class name with which it will work.

With our Box example, for instance, we might also include an interface for a more generic Container class. To do this, we'd need to add an Implements statement in the general declarations section of our Box class module:

Implements Container

As soon as we add this line, Visual Basic will require that we add code to our class module to implement all the Public properties and methods from the Container class. Suppose the Container class module contains a single method, called PutIn, and a property, called IsOpen:

Public Sub PutIn(Item As Object)

End Sub

Public Property Get IsOpen() As Boolean

End Property

The property and method may or may not have code in them. As I've shown them in this example, there's no code. It doesn't really matter to our Box class, since we need to add our own code in the Box class module regardless. For instance, the Box class's implementation of PutIn may add any new items into a Private collection variable, and IsOpen may just return our blnOpen flag:

Option Explicit
Implements Container

Private colItems As New Collection
Private dblHeight As Double
Private dblWidth As Double
Private dblDepth As Double
Private blnOpen As Boolean

Public Sub OpenBox()
  If Not blnOpen Then Err.Raise vbObjectError, _
    "Box already open"
  blnOpen = True
End Sub

Public Sub CloseBox()
  If Not blnOpen Then Err.Raise vbObjectError, _
    "Box already closed"
  blnOpen = False
End Sub

Private Sub Container_PutIn(Item As Object)
  colItems.Add Item
End Sub

Private Property Get Container_IsOpen() As Boolean
  Container_IsOpen = blnOpen
End Property

There are a couple interesting things about these new routines we've added. First off, they are declared as Private routines, so at first glance they don't appear to be available to any client code. This is deceptive, however, since they are available to clients through the Container interface.

If these routines were declared as Public, they'd be part of the Box interface. That's not what we are after though. Instead of declaring them as Public, we declare them as Private, and then indicate that they belong to the Container interface by putting Container_ in front of the name of the routine.

We'll discuss multiple interfaces in more detail later in the book.

© 1997 by Wrox Press. All rights reserved.