It is often said that there are four main concepts in the area of object-oriented programming:
Each of these concepts plays a significant role in VB.NET programming at one level or another. Encapsulation and abstraction are “abstract” concepts providing motivation for object-oriented programming. Inheritance and polymorphism are concepts that are directly implemented in VB.NET programming.
Simply put, an abstraction is a view of an entity that includes only those aspects that are relevant for a particular situation. For instance, suppose that we want to create a software component that provides services for keeping a company’s employee information. For this purpose, we begin by making a list of the items relevant to our entity (an employee of the company). Some of these items are:
Note that we include not only properties of the entities in question, such as FullName, but also actions that might be taken with respect to these entities, such as IncSalary, to increase an employee’s salary. Actions are also referred to as methods, operations, or behaviors. We will use the term methods, since this term is used by VB.NET.
Of course, we would never think of including an IQ property, since this would not be politically correct, not to mention discriminatory and therefore possibly illegal. Nor would we include a property called HairCount, which gives the number of hairs on the employee’s right arm, because this information is of absolutely no interest to us, even though it is part of every person’s being.
In short, we have abstracted the concept of an employee — we have included only those properties and methods of employees that are relevant to our needs. Once the abstraction is complete, we can proceed to encapsulate these properties and methods within a software component.
The idea of encapsulation is to contain (i.e., encapsulate) the properties and methods of an abstraction, and expose only those portions that are absolutely necessary. Each property and method of an abstraction is called a member of the abstraction. The set of exposed members of an abstraction is referred to collectively as the public interface (or just interface) of the abstraction (or of the software component that encapsulates the abstraction).
Encapsulation serves three useful purposes:
It permits the protection of these properties and methods from any outside tampering.
It allows the inclusion of validation code to help catch errors in the use of the public interface. For instance, it permits us to prevent the client of the employee software component from setting an employee’s salary to a negative number.
It frees the user from having to know the details of how the properties and methods are implemented.
Let us consider an example that involves the Visual Basic Integer data type, which is nicely encapsulated for us by VB. As you undoubtedly know, an integer is stored in the memory of a PC as a string of 0s and 1s called a binary string. In Visual Basic, integers are interpreted in a form called two’s-complement representation, which permits the representation of both negative and non-negative values.
For simplicity, let us consider 8-bit binary numbers. An 8-bit binary number has the form a7a6a5a4a3a2a1a0, where each of the axs is a 0 or a 1. We can think of it as appearing in memory as shown in Figure 4-1.
In the two’s-complement representation, the leftmost bit, a7 (called the most significant bit), is the sign bit. If the sign bit is 1, the number is negative. If the sign bit is 0, the number is positive.
The formula for converting a two’s-complement representation a7a6a5a4a3a2a1a0 of a number to a decimal representation is:
decimal rep. = -128a7 + 64a6 + 32a5 + 16a4 + 8a3 + 4a2 + 2a1 + a0
To take the negative of a number when it is represented in two’s-complement form, we must take the complement of each bit (that is, change each 0 to a 1 and each 1 to a 0) and then add 1.
At this point you may be saying to yourself, “As a programmer, I don’t have to worry about these details. I just write code like:
x = -16 y = -x
and let the computer and the programming language worry about which representation to use and how to perform the given operations.”
This is precisely the point behind encapsulation. The details of how signed integers are interpreted by the computer (and the compiler), as well as how their properties and operations are implemented, are encapsulated in the integer data type itself and are thus hidden from us, the users of the data type. Only those portions of the properties and operations that we need in order to work with integers are exposed outside of the data type. These portions form the public interface for the Integer data type.
Moreover, encapsulation protects us from making errors. For instance, if we had to do our own negating by taking Boolean complements and adding 1, we might forget to add 1! The encapsulated data type takes care of this automatically.
Encapsulation has yet another important feature. Any code that is written using the exposed interface remains valid even if the internal workings of the Integer data type are changed for some reason, as long as the interface is not changed. For instance, if we move the code to a computer that stores integers in one’s-complement representation, then the internal procedure for implementing the operation of negation in the integer data type will have to be changed. However, from the programmer’s point of view, nothing has changed. The code:
x = -16 y = -x
In VB.NET, the methods of an interface are realized as functions. On the other hand, a property, as we see later in this chapter, is realized as a private variable that stores the property’s value together with a pair of public functions — one to set the variable and one to retrieve the variable. These functions are sometimes referred to as accessor methods of the property. It is the set of exposed functions (ordinary methods and accessor methods) that constitute the interface for an abstraction.
In general, a software component may encapsulate and expose more than
one abstraction — hence, more than one interface. For example, in
a more realistic setting, we might want a software component designed
to model employees to encapsulate an interface called
IIdentification (the initial
“I” is for interface) that is used
for identification purposes. This interface might have properties
such as name, Social Security number, driver’s
license number, age, birthmarks, and so on. Moreover, the software
component might also encapsulate an interface called IEducation for
describing the employee’s educational background.
Such an interface might implement properties such as education level,
degrees, college attended, and so on.
The interface of each abstraction exposed by a software component is
also referred to as an interface of the software component. Thus, the
Employee component implements at least two interfaces:
Note, however, that the term interface is often used to refer to the
set of all exposed properties and methods of a
software component, in which case a component has only one interface.
Referring to our original Employee abstraction, its interface might consist of the functions shown in Table 4-1. (Of course, this interface is vastly oversimplified, but it is more than sufficient to illustrate the concepts.)
Table 4-1. Members of the Employee interface
FullName: GetFullName(), SetFullName( )
Address: GetAddress(), SetAddress( )
EmployeeID: GetEmployeeID(), SetEmployeeID( )
Salary: GetSalary(), SetSalary( )
Using the term interface as a set of functions, while quite common, poses a problem. Just listing the functions of the interface by name (as done previously) does not provide enough information to call those functions. Thus, a more useful definition of interface would be the set of signatures of the public functions of a software component.
By way of example, consider the following sorting function:
Function Sort(a( ) as Integer, iSize as Integer) as Boolean For i = 1 to iSize For j = i+1 to iSize If a(j) < a(i) Then swap a(i), a(j) Next j Next I Sort = True End Function
The first line in this definition:
Function Sort(a( ) as Integer, iSize as Integer) as Boolean
is the function declaration. It supplies information on the number and types of parameters and the return type of the function. The body of the function:
For i = 1 to iSize For j = i+1 to iSize If a(j) < a(i) Then swap a(i), a(j) Next j Next i Sort = True
represents the implementation of the function. It describes how the function carries out its intended purpose.
Note that it is possible to alter the implementation of the function
without changing the declaration. In fact, the current function
implementation sorts the array
a using a simple
selection-sort algorithm, but we could replace that sorting method
with any one of a number of other methods (bubble sort, insertion
sort, quick sort, and so on).
Now consider a client of the Sort function. The client only needs to know the function declaration in order to use the function. It need not know (and probably doesn’t want to know) anything about the implementation. Thus, it is the function declaration, and not the implementation, that forms the interface for the function.
The signature of a function is the function name and return type, as well as the names, order, and types of its parameters. A function declaration is simply a clear way of describing the function’s signature. Note that Microsoft does not consider the return type of a function to be part of the function’s signature. By signature, they mean what is generally termed the function’s argument signature. The reasons for doing this become clearer later in the chapter when we discuss overloading, although it would have been better (as usual) if they were more careful with their terminology.
Function GetFullName(lEmpID As Long) As String Sub SetFullName(lEmpID As Long, sName As String) . . . Sub IncSalary(sngPercent As Single) Sub DecSalary(sngPercent As Single)