Chapter 4: Object Oriented Programming
There are two kinds of people: those who divide everything into two categories and those who don't. Likewise there are two kinds of programming: procedural and object oriented. In this chapter we'll begin to understand what objects are and why they make programming easier and less prone to errors.
A procedural program is written in the style you are probably most familiar with: one in which there are arithmetic and logical statements, variables, functions and subroutines. Data are declared somewhere at the top of a module or a procedure and more data are passed in and out of various functions and procedures using argument lists.
This style of programming has been successfully utilized for a very long time as programming goes but it does have some drawbacks. For example, the data must be passed correctly between procedures, making sure that it is of the correct size and type, and the procedures and their calling arguments may often need to be revised as new function is added to the program during development.
Object-oriented programming differs in that a group of procedures are grouped around a set of related data to construct an object. An object is thus a collection of data and the subroutines or methods that operate on it. Objects are usually designed to mimic actual physical entities that the program deals with: customers, orders, accounts, graphical widgets, etc.
More to the point, most of how the data are manipulated inside an object is invisible to the user and only of concern inside the object. You may be able to put data inside an object and you may be able to ask to perform computations, but how it performs them and on exactly what internal data representation is invisible to you as you create and use that object. Once someone creates an complete, working object, it is less likely that programmers will modify it. Instead they will simply derive new objects based on it. We'll be taking up the concept of deriving new objects in Chapter 6.
Objects are really a lot like C structures or Visual Basic Types except that they hold both functions and data. However, objects are just the structures. In order to use them in programs, we have to create variables of that object type. We call these variables instances of the object.
Let's take a very simple example. Suppose that we want to draw a rectangle on our screen. We need to design code for drawing a rectangle and specifying what size it is (as well as where). Now, our first thought might have been to simply write a little subroutine to draw the rectangle, and then draw each rectangle we want by calling this subroutine:
Call DrawRect (10, 10, 200, 150) Call DrawRect (40, 40, 150, 100)
A complete Visual Basic Program to draw two rectangles when the "Draw" button is clicked is:
Option Explicit
Private Sub Draw_Click()
Call DrawRect(10, 10, 200, 100)
Call DrawRect(40, 40, 150, 75)
End Sub
'-----------------------------------------------------
Sub DrawRect(ByVal x As Integer, ByVal y As Integer, _
ByVal width As Integer, ByVal height As Integer)
'convert from pixels to twips
Dim Tpp As Integer
Tpp = Screen.TwipsPerPixelX
x = x * Tpp
y = y * Tpp
width = width * Tpp
height = height * Tpp
Line (x, y)-(x + width, y + height), vbBlue, B
End Sub
The window this program displays is show in Figure
4-1. The code for this program is on the example disk as rect1.vbp
in the \chapter4 directory.
Figure 4-1: Window displayed by procedural Visual
Basic Rectangle drawing program.
The problem with this program is that the drawing routine has to know a lot about the display characteristics. We call the routine using pixel coordinates, but we must convert the coordinates to twips. This DrawRect routine is publicly available for modification throughout the program, and if we need more arguments, such as color or line thickness, we would just have to add them to an ever growing list of subroutine arguments. Further, if we wanted to draw some other shapes, we'd have to go through the same pixel-to-twip conversion for that routine as well, in addition to adding arguments to it if we decided we neede to specify color or line thickness.
If we were to rewrite this program using object-oriented style, we would design a rectangle object which could draw itself. We wouldn't have to know how it did the drawing or whether the algorithm or calling parameters changed from time to time.
In VB version 4.0, we would do this by creating a rectangle class with a draw method inside it.
'A rectangle class 'remembers its position and shape 'and knows how to draw itself on a form Private x As Integer, y As Integer Private width As Integer, height As Integer Private Tpp As Integer Private frm As Form Private color As Long '------------------------------------------------ Public Sub setColor(c As Long) color = c End Sub '------------------------------------------------ Public Sub draw() frm.Line (x, y)-(x + width, y + height), color, B End Sub '------------------------------------------------ Private Sub Class_Initialize() Tpp = Screen.TwipsPerPixelX color = vbBlue 'set default color End Sub '------------------------------------------------ Public Sub setForm(f As Form) Set frm = f 'remember form to draw on End Sub '------------------------------------------------ Public Sub reshape(xpos%, ypos%, w%, h%) x = xpos% * Tpp y = ypos% * Tpp width = w% * Tpp height = h% * Tpp End Sub
Note that this VB Rectangle class contains both private data and subroutines for manipulating it.
Note that we added a setColor function or method to allow us to change the color of the object if we wanted to. Then our main calling program, simply creates two different rectangle objects and draws them when the button is clicked. Each instance of the rectangle object remembers its position and size and draws itself on the form on command.
'Main Rectangle Drawing Program Dim rect1 As New Rectangle 'create 2 rectangles Dim rect2 As New Rectangle '---------------------------------------------- Private Sub Form_Load() rect1.setForm Form1 'tell them where to draw rect2.setForm Form1 End Sub '---------------------------------------------- Private Sub draw_Click() 'specify sizes and positions rect1.Reshape 10, 10, 200, 100 rect2.Reshape 40, 40, 150, 75 rect1.draw 'and draw them rect2.draw End Sub
In this program we create two instances of
the Rectangle class, named rect1 and rect2. We tell them where
they are to draw, using the SetForm method. Then, we set their
size and position properties using the Reshape method and then
tell them to draw themselves using the draw method. This program
is called rect2.vbp on the example disk. The display generated
by this program is illustrated in Figure 4.2.
Figure 4-2: Rectangle drawing using object oriented
Visual Basic program.
Now, at first, this might seem like more work, but it pays off well. The rectangle object encapsulates all of the rectangle knowledge and you as the user of that object only have to know to set its shape and to draw it. Thus, the overall effort to maintain your program is greatly reduced! Further each copy, or instance, of the Rectangle class contains its own size, location and color data so it can always draw itself. This simplification of our overall programming effort becomes even more obvious as our objects become more complex.
Let's look at how we would do the same thing in Java. The procedure is quite analogous:
A Java object is also called a class. Remember that in our very first simple program in the chapter 3, we used the key word "class" in creating the outer wrapper of our example program. Each Java class is an object which can have as many instances as you like.
When you write a Java program, the entire program is one or more classes. The main class represents the running program itself, and it must have the same name as the program file. In the rectangle example, the program is called Rect1.java and the main class is called Rect1.
Classes in Java contain data and functions, which are called methods. Both the data and the methods can have either a public or a private modifier, which determines whether program code outside the class can access them. Usually we make all data values private and write public methods to store data and retrieve it from the class. This keeps programs from changing these internal data value accidentally by referring to them directly.
If we want users of the class to be able to use a method, we, of course, must make it public. If on the other hand, we have functions which are only used inside the class, we would make them private.
While a Java program can be made up of any number of .java files, each file can contain only one public class and it must have the same name as the file itself. There can be any number of additional classes within the file which are not declared as public. These would normally be used only by the public class in that file. You do not declare classes as private: they either have a public modifier or none at all.
We use the new operator in Java to create an instance of a class. For example to create an instance of the Button control class, we could write:
Button Draw; //a Button object //create button with "Draw" caption Draw = new Button("Draw");
Remembering that we can also declare a variable just as we need it, we could also write somewhat more compactly:
Button Draw = new Button("Draw");
Don't be confused by the fact that the variable name and the caption of the button are the same: they are unrelated, but convenient in many programs.
Remember, while we can create new variables of the primitive types (such as int, float, etc.) we must use the new operator to create instances of objects. The reason for this distinction is that objects take up some block of memory. In order to reserve that memory, we have to create an instance of the object, using the new operator.
When we create an instance of a class we write ourselves, we usually need to write code that initializes variables inside the object. This code is put in the class's constructor routine. A constructor routine has the same name as the class, is always public, and has no return type (not even void).
class Rectangl
{
private int xpos, ypos;
private int width, height;
private Graphics g; //where
to draw
public Rectangl(Graphics gr) //constructor
{
g = gr; //copy where to draw
}
In the above example, our Rectangl class has the handle to the current window's graphics object passed to it, and it saves that handle in a private variable g. We might also set the rectangle position, size and color to default values in the constructor to make sure that they never contain irrational values.
class Rectangl
{
private int xpos, ypos;
private int width, height;
private Graphics g; //where to draw
private Color c;
public Rectangl(Graphics gr)
{
g = gr; //copy where to draw
c = Color.blue; //default color
width = 200; //default size
height = 100;
x = 0;
y = 0; //default position;
}
In the example below, we see a complete Rectangl class, including its draw routine.
import java.awt.*;
//Simple Rectangle drawing
example
public class Rect1 extends Frame
{
private Button Draw; //Button on window
private Rectangl rect1; //two rectangles
private Rectangl rect2;
//--------------------------------------------
public Rect1() //window class constructor
{
super("Rect1 window"); //create window with title
addNotify(); //connect to OS window system
reshape(50, 50, 475, 225);
//size of window
Draw = new Button("Draw"); //create button
add(Draw); //add to window
Draw.reshape( 178, 148,
82, 32); //set button shape
//Create rectangles and tell them where to draw
rect1 = new Rectangl(getGraphics());
rect2 = new Rectangl(getGraphics());
//define their shapes
rect1.reshape(10, 10, 200, 100);
rect2.reshape(40, 40, 150, 75);
show(); //display window
}
//--------------------------------------------
//click event is received here
public boolean action(Event evt, Object obj)
{
clickedDraw(); //button clicked
return true;
}
//--------------------------------------------
public void clickedDraw()
//button clicked
{
rect1.draw(); //draw two rectangles
rect2.draw();
}
} //end of Rect1 class
//--------------------------------------------
public static void main(String args[])
//program starts here
{
new Rect1(); //create instance of Rect1 class
}
The program Rect1 also contains the definition for the Rectangl class. We spell it without the "e" because Java already has a class named "Rectangle."
class Rectangl
{
private int xpos, ypos;
private int width, height;
private Graphics g;
public Rectangl(Graphics gr)
{
g = gr; //where to draw
}
//--------------------------------------------
public void reshape(int x, int y, int w, int h)
{
xpos = x; //remember size and posn
ypos = y;
width = w;
height = h;
}
//--------------------------------------------
public void draw()
//draws rectangle at current position
{
g.setColor(Color.blue);
g.drawRect(xpos, ypos, width,height);
}
} //end of Rectangl class
This is a complete working program as shown and is
called Rect1.java on the example disk in the \chapter4 directory.
Let's look at how it works. You can see its window display in
Figure 4-3.
Figure 4-3: The Rect1 program for drawing two rectangle objects.
First, the main routine is where the program
actually starts. If you want to write a stand-alone application
one and only one of its classes must have a main routine, and
its "signature" must be exactly:
public static void main(String arg[])
While that main routine appears to be part of a class, it is actually in a way grafted on (because of the static qualifier) and it is the entry point for the program. All this main routine does is create one instance of the program class Rect1.
new Rect1();
When you create an instance of an object, the code in the constructor routine is executed automatically. This is the place where we will initialize the window variables and create instances of the rectangle object.
public Rect1() //window class constructor
{
The first three lines initialize the window itself, creating a title bar title, attaching it to the underlying windowing system and setting the shape of the window.
super("Rect1 window"); //create window with title
addNotify(); //connect to OS window system
reshape(50, 50, 475, 225);
//size of window
Then we create an instance of the Button class for the Draw button.
Draw = new Button("Draw"); //create button
add(Draw); //add to window
Draw.reshape( 178, 148,
82, 32);//set button shape
Finally, we create two instance of the Rectangl class and define their shapes.
//Create rectangle and tell them where to draw
rect1 = new Rectangl(getGraphics());
rect2 = new Rectangl(getGraphics());
//define their shapes
rect1.reshape(10, 10, 200, 100);
rect2.reshape(40, 40, 150, 75);
show(); //display window
}
Now we have two rectangle objects we can draw whenever we need to by simply telling each one to "draw itself."
rect1.draw(); rect2.draw();
As we noted above, functions inside a class are referred to as methods. These functions can be public, meaning that you can access them from outside the class, private, meaning that they can only be accessed from inside the class and protected, meaning that the methods can be accessed only by other classes in the same file.
A method inside an object is just a function or subroutine. If it returns a value, like a Visual Basic Function, you declare the type of the return
int getSize() { }
If it returns no value, like a VB Subroutine, you declare that it is of type void.
void resize(int width, int height) { }
In either case you must declare the type of each of the arguments. It is usual to use descriptive names for each of these arguments to the casual reader can figure out what each method does.
In object oriented programming, you usually make all of the variables in a class private as we did above with xside and yside. Then you set the values of these variables either as part of the constructor or using additional set and get functions. This protects these variables from accidental access from outside the class and allows you to add data integrity checks in the set functions to make sure that the data are valid.
We could, of course, have made the Rectangl's height and width variables public and set them directly.
rect1.width = 300; rect1.height = 200;
but this gives the class no protection from erroneous data such as:
rect1.width = -50;
So instead, we use accessor functions such as resize to make sure that the data values we send the class are valid:
rect1.resize(300, 200); //call resize method
and then within the class we write this accessor function with some error checking:
public void resize(int w, int h) { if (w > 0 ) width = w; //copy into width if legal if (h > 0) height = h; //and into height if legal }
Our Rectangl class had one constructor, containing a handle to the windowing system's graphics object. However, we can have more than one constructor, and in fact more than one version of any method, as long as they have different argument lists or signatures. So we could declare our rectangle's shape at the same time we create it:
public void Rectangl(Graphics gr, int width, int height) { g = gr; width = w; //save width and height height = h; }
The Java compiler must be able to distinguish these various versions of the same method by either the number or the type of arguments. If two different methods have the same number and type of arguments, the compiler will issue an error message.
All primitive types (int, long, float, boolean) are passed into methods by value. In other words their values are copied into new locations which are then passed to the subroutine. So, if you change the value of some argument to a method, it will not be changed in the original calling program.
For example, suppose we need to swap the values of two integers several times in some program. We might be tempted to write a private swap method like this:
private void swap (int x, int y) { //this looks like it will swap integers, //but it won't int temp = x; x = y; y = temp; }
and then call this method from the main class:
int a = 5; //assign two values int b = 10; swap (a, b); //integers passed by value System.out.println("a=" + a + " b=" + b);
However, we find that a still is 5 and b still is 10. And of course the reason why this won't work is that the memory locations containing the arguments x and y are copies of the original calling parameters, and switching them will not switch the originals.
Objects, on the other hand, are called reference types, because they are passed into methods by reference rather than by value. While actual pointers to memory locations don't exist at the programmer level in Java, these references are, of course, pointers to the block of memory that constitutes an instance of an object.
Now as we noted earlier, there are object versions of the primitive types, called Integer, Long, Float, and Boolean. We might at first imagine that we could write a method which would swap objects since they are passed into the method by reference.
private void Swap(Integer x, Integer y) { //swaps Integer objects, but to no avail Integer temp = new Integer(x.intValue()); x = y; y = temp; }
However, if we write a program to use this method, we find, sadly, that this method still does not affect the calling parameters.
Integer A = new Integer(a); Integer B = new Integer(b); Swap (A, B); //objects passed by reference System.out.println("a=" + A + " b=" + B);
Why is this? What's going on here? Well, remember that the values passed into the Integer class Swap method are pointers to the original objects A and B. Let's suppose that they have the address values 100, and 110. Then when we create the new Integer temp let's suppose its address is 300. After the Swap method completes, x will have the value 110 and y will have the value 300. In other words, we changed the values of the pointers, but did not change the contents of the objects. In addition, these are just local copies of the pointers to these objects, so we haven't swapped the references in the calling program in any case. In fact, the Integer class does not have any methods to change its contents, only to create new Integers, which will not help us in this case.
So even though objects are passed into methods by reference, changing the values of the pointers we used does not change the contents of the objects themselves. If we want to make an effective swap, we would have to create an object whose contents we could change.
For example, we could create a Pair class with a swap method:
class Pair { Object a; //two objects we will swap Object b; //------------------------------------- public Pair(Object x, Object y) { a = x; b = y; } //------------------------------------- public Pair(int x, int y) { a = new Integer(x); b = new Integer(y); } //------------------------------------- public void swap() { Object temp = a; //swaps values of objects a = b; b = temp; } //------------------------------------- Object geta() { return a; //returns values of objects } //------------------------------------- Object getb() { return b; } } //end Pair class
and accessor methods geta and getb to obtain the values after swapping. Then we could call these methods as follows:
Pair p = new Pair(A, B);//copy into object p.swap(); //swap values inside object A =(Integer)p.geta(); //get values out B =(Integer)p.getb(); System.out.println("a=" + A + " b=" + B);
And, if you think this is silly in this case, you are right. After all, swapping the values of two numbers or objects only requires 3 lines of code in any case. However, this does illustrate an important concept. If you want to change values, you have to do it inside the object, not by switching copies of the references to the objects.
Object-oriented programs are often said to have three major properties:
Encapsulation - we hide as much of what is going on inside methods in the object.
Polymorphism - Many different objects might have methods having identical names, such as our draw method. While they may do the same thing, the way each is implemented can vary widely. In addition, there can be several methods within a single object with the same name but different sets of arguments.
Inheritance - objects can inherit properties and methods from other objects, allowing you to build up complex programs from simple base objects. We'll see more of this in the chapters that follow.
First let's review some terminology:
We've written our own rectangle drawing class, now
and explored most of the thicket of object-oriented jargon. In
the next chapter we'll look
at some useful built-in classes that
round out the Java language and then go on to the last important
new concept: inheritance.
[Top of page] | [Previous chapter] |
[Next Chapter] |
[Java expertise]
Java and
all Java-based trademarks and logos are trademarks or registered
trademarks of Sun Microsystems, Inc. in the U.S. and other countries.