|
III. Object-Oriented Programming
Introduction
As we've said before, Object-Oriented
Programming (OOP) took and extended the concepts of procedural programming to
a new level. It also incorporated into itself the basic concept of data structures.
In this chapter, we'll talk
about the many aspects of OOP and their uses. In the next chapter, we will discuss
focus on advanced uses of data structures.
A. Crash Course of Objects
in Java
What are objects?
Once again, OOP stands for
Object-Oriented Programming, and so naturally it revolves around the concept
of "objects." But what is an object in computer science? An object
is something that can store data and also perform certain processes with data.
The things the object can do are described in its methods, which we've
discussed earlier. The data an object stores are generally called fields
or data members.
For example, let's consider
an object "Song" which depicts a song. The following figure indicates
possible methods and fields of Song.

The fields of Song
are basic variables that describe the object like title, author, length, etc.
The methods of Song are play and stop, which are operations the
object can perform.
Implementing Objects
So how do you write code
for an object? This is where the concept of a class comes into hand. A class
is code that describes an object. You might recall that when we wrote programs
last section, in the beginning of the code we have a line public
class Something
with the "Something" being the name of the program. Does that make
Something
a class? It does, and in fact, a program in Java is a class. When the program
is compiled and executed, the main
method of the class is automatically called.
This is what a class might
look like in Java.
class TwoNumClass {
// declaration of fields
private int int1;
private int int2;
// methods
public int GetSumInts ()
{
return int1+int2;
}
public void SetNumOne (int a)
{
int1 = a;
}
public int GetNumOne ()
{
return int1;
}
public void SetNumTwo (int a)
{
int2 = a;
}
public int GetNumTwo ()
{
return int2;
}
}
The first part of the class
contains the declaration of its fields. Note that we put the word keyword private
in each declaration. This is done so that when an outside class tries to access
an object described by our class, it will not be able to directly alter or even
access the value of the fields. This is a common practice, but if you don't
want this type of behavior, you can either not specifically put in any modifier
or use public
or friendly,
which we will describe in more detail later.
But then how does one modify
the private fields' values when they use the object? That's why there are methods
like NumOne above that serve to set the value of the fields.
Furthermore, to allow for
retrieval of the fields' values, we have methods like GetNumOne
above that return the value of the private field.
Creating Objects
Once you've written a class,
you can use it as the prototype to create objects. There are two steps to this.
First, you define a variable as an object. Then, you create a new object in
memory and have your variable point to it.
The definition step is:
(modifiers) <Object type> <Object name>;
The creation step is:
<Object name> = new <Object type>(arguments);
However, these two steps
are generally put together for simplicity.
(modifiers) <Object type> <Object name> = new <Object
type>(arguments);
Thus, if we want to create
an object that we described in our TwoNumClass
earlier, we can say:
TwoNumClass object1 = new TwoNumClass();
In technical terms, we have
just created an instance or object of the class TwoNumClass.
Now we can access the methods of the class. This is done using the "dot
operator" which basically means a plain dot put after the name of the object
variable. Then we follow the dot with the name of the method we want to call
or the field we want to access (however in this particular example, we cannot
access the fields as they were set as private in the class). For
example, say we want to set the values of the two numbers. We call the SetNumOne
and SetNumTwo
methods. Then we can use GetNumOne
and GetNumTwo
to access the values. Lastly, we try out the GetSumInts
method. Note that for all the methods we have to put the empty parenthesis.
When testing, remember to put this code in the main
method of whatever class your program corresponds to and the class definitions
outside of that class.
object1.SetNumOne(12);
object1.SetNumTwo(21);
System.out.println("1st number is "+object1.GetNumOne());
System.out.println("1st number is "+object1.GetNumTwo());
System.out.println("The sum is "+object1.GetSumInts());
Constructors
It is desirable to be able
to set the initial values of important fields right when an instance of the
class is declared. This can be done with a special type of method called a constructor.
When you make a constructor, you give it the same name as the class. You would
usually set the parameters so that the user can pass in the values of the important
fields. Then inside, you put in the initializations. For example, we could implement
a constructor so that we can specify the initial values of int1
and int2
whenever we create
an instance of TwoNumClass.
// TwoNumClass constructor
TwoNumClass(int a, int b) // note: you should NOT
// put in a return type
{
int1 = a;
int2 = b;
}
This code has to be put
inside of the class, usually where the methods are for convenience. The constructor
can only be used when you create a new object. That is, you cannot call it again
later just like any method to change the values of the fields. The following
is an example of the correct use of our constructor.
TwoNumClass object2 = new TwoNumClass(10,20);
You can set more than one
constructors in the same class, to correspond with different parameters. For
example, you might have one that just sets the first number to allow for just
one integer in the parameter. If you do not make any constructor, Java automatically
creates a default one for you that won't do anything special. However, once
you have set just one constructor, if the user entered in no parameters or a
different set of parameters, your program will not run correctly as Java no
longer provides a default constructor and tries to run your constructor but
with non-matching parameters. Therefore, you must create your own default constructor
with empty parantheses for the parameters.
To simplify coding for additional
constructors, sometimes the this
method is used. Putting this
in the beginning of a constructor with appropriate arguments will call another
constructor you put in your class. For example,
// All the TwoNumClass constructors
TwoNumClass()
{
this(0,0); // everything set to 0 if no arguments
}
TwoNumClass(int a)
{
this(a,0); // if 1 arg. is provided, set to 1st no.
}
TwoNumClass(int a, int b) // original constructor
{
int1 = a;
int2 = b;
}
A Revisit of Scope
Now that you know about
classes, there is a lot of stuff to uncover that we have previously avoided.
Remember the "modifiers" public,
private,
and static
that always lurked in declarations? What exactly do they mean?
- A
public
method or data member is assessible by any outside class using the object.
- A
private
method or data member is assessible solely by methods of the same class.
- A
static
method or data member is one that is shared among all created instances of
a the class. That is, say you created two objects of the same class; the two
will share the same static methods and data members. static
methods and data members can be both public
or private.
In Java, the default (if
you don't specify anything in particular) is public.
So when would you make something
private?
As said before, sometimes you want to make variables that contribute to the
inner workings of your class invisible. Furthermore, if you leave your data
members private and write public methods that allow people using the class to
modify things, if you ever you change the object, you could continue to have
the same methods work by just re-coding the methods to fit the new way your
data's organized. Then the code written earlier to use the object can be reused
with little or no modification.
It is important to know
that static methods cannot access non-static
variables or methods. That's why if you try to call a non-static
method from main,
you will get an error message. This is due to the fact that a static
method is shared among all instances of a class, so if a static
method were allowed to call a non-static variable,
it would not know from which instance of the class the non-static
variable should be taken.
This concept is illustrated in this diagram:

But what are static
variables and methods good for? First of all, you don't have to create an object
of the class to use its static methods as they are not instance-dependent.
All you have to do is put the class name followed by a period and the name of
the static method or data to use it. Some of the utility classes
provided by Java (such as those for math, etc.) code their methods as static
so that you don't have to create an instance of the class to use them. All you
would have to do is "include" the package containing that class in
the beginning of your program.
Since static methods are
not instance-dependent, how then could you initialize them? Besides the initialize
as your create method that we usually use (e.g. static
int num1=10;),
for more complex problems, one can use a static
"initialization block." Just put your initialization code within
braces {} in your class with the keyword static
in front of the braces. That way, all code within the braces will be marked
as static
initialization code and run before an instance is created (note: without putting
the keyword static,
you would have a regular initialization block that is run whenver an instance
is created).
Class Methods/Variables
vs. Instance Methods/Variables
Instead of "data members"
or "fields," sometimes you hear people say "instance variables"
or "class variables." While the two are frequently used interchangeable,
there is actually a different between them. A class variable is a variable
declared in the class (a data member) as static;
that is, the variable is associated with the class, not a particular
instance of the class. An instance variable, on the other hand,
is a variable that is created and associated with a particular instance
of the class. The same can be3 said about class methods and instance
methods.
B. Encapsulation
One of the true powers of
OOP comes from the concept of encapsulation. What is encapsulation? Encapsulation
refers to the practice of writing code that use classes without knowing their
inner workings. One would use solely the methods provided by the class, and
if the the class is ever changed for improvement, the old code would still be
supported so long as the programmers keep the original methods' names and parameter
lists intact.
Encapsulations also allow
for multiple ways to do the same thing. For example, remember our discussions
of factorials earlier? We implemented a recursive and an iterative way to do
the factorial. However, if we had just put the method in a factorial class and
named the method Factorial
regardless of which implementation the code is using, another person can just
call the Factorial method and get the job done with. If we then
later change the algorithm for the Factorial
method, the other person can still use the same method call and argument.
If you have had some background
in procedural programming, you might think: this sounds a lot like "modularity"!
Modularity refers to the procedural programming concept of splitting
a big task into modules that can be reused. Indeed, at first glance, encapsulation
resembles the traditional modularity concept, but there is really a world of
difference with the power of OOP.
First of all, you can have
multiple instances of a single class, whereas in procedural programming you
do not have an option to use a procedure more than one time simultaneously.
This feature is can be very important. Also, classes are no longer limited to
code that solely performs operations; they can contain multiple methods and
data members and thus are at further from procedures.a step further from procedures.
In sum, encapsulation is
one of the major basic principles of OOP--a very important one.
C. Inheritance
Another important principle
of OOP is called "inheritance." But to understand inheritance, first
you have to understand the concept of a class hierarchy--the representation
of one class superseded by a base class. The following diagram illustrates a
class hierarchy, using sample characters from Blizzard's 1998 hit game Starcraft.

There's a base class called
Person with some common properties of all units (e.g. health, attack
power, etc.). Then, there are three "subclasses"-- Terran,
Protoss, and
Zerg
that extends the base class Person with additional properties unique
to each race. For example, in the game, Zerg units continuously regenerate health
points. This could be demonstrated by adding a method to the Zerg
class that will do the regeneration calculations. Protoss units, on the other
hand, have not only the regular health meter but also a unique "shield
meter," and we can add fields for this in the Protoss
class.
Then each race has their
different units, each with different attacks and attack powers, and here we
could set all those values and also add additional methods for any unique spells
or things the unit can do.
In correct OOP terminology,
Person is the superclass (also known as the base class
and the parent class) of Terran, and Marine
is the subclass (derived class/child class) of Terran
and so forth.
But why make the superclasses?
We do that to reduce work. That way we don't have to code and recode everything
when we do the final classes for each unit. This is a very useful feature of
OOP and is the basic concept behind inheritance.
How does one use inheritance
in Java? You can use the extends
keyword. When you declare subclass with extends to some superclass,
the subclass will automatically inherit all the methods and fields of the superclass.
Then, inside the subclass' code, you only put in the differences. You
can add in more methods and fields, or you can put another version of a previously
made method with the same name. All you have to do for that is just write the
method with the same name and Java will automatically use the new version for
the subclass. You can have more than one level of inheritance, like in our diagram,
where Marine
is a subclass of Terran
which is a subclass of Person.
The following code shows
a working example of inheritance. First we have to construct a Person
class:
class Person
{
// Instance Variables
String Type;
int TotalHealth;
int CurHealth;
int XPos;
int YPos;
int GdStrength;
int AirStrength;
// constructor, setting initial position
public Person(int a, int b)
{
XPos=a;
YPos=b;
}
public void GdAttack(Person a)
{
a.CurHealth -= GdStrength;
}
public void GdAttack(Protoss a)
{
if(a.CurShield>0) {
a.CurShield -= GdStrength;
}
else {
a.CurHealth -= GdStrength;
}
}
}
Several things of importance
here. First of all, I have a list of (greatly simplified) fields common to all
units, such as those related to health and attack strength. There are also the
x and y positions, which are needed in a real game to denote where the units
are on the screen.
For methods, we have only
put in a crude ground attack method named GdAttack
which will take off health away from the target object by the amount of the
attacking object's strength.
You will notice that there
are two GdAttack
methods. The first one has parameter Person
while the second has paramter Protoss.
This is because in the game, a unit of type Protoss will actually have shield
layer that is harmed before the actual health is reduced, and in the second
GdAttack
we reduce the CurShield
field (which you will see in the Protoss
class definition below).
If Java sees two methods
of the same name in a class, but with a different signature (parameter
data type, name, number, etc.), it will call the appropriate one according to
what arguments were passed in during the call. In this case, if the parameter
is simply of type Person
and not the subclass Protoss,
it will evaluate using the first method. This type of behavior is a hallmark
of OOP and is called method overloading.
Alas, here are the rest
of the classes, Protoss,
Terran,
Dragoon,
and Marine.
Please note the use of the super method in the subclasses' constructors,
which will automatically call the constructor of the base class above it, using
whatever parameters are passed in.
Protoss
class Protoss extends Person
{
public Protoss(int a, int b)
{
super(a,b);
}
int TotalShield;
int CurShield;
public void RegenShield()
{
if (CurShield<=TotalShield) {
CurShield++;
}
}
}
Terran
class Terran extends Person
{
public Terran(int a, int b)
{
super(a,b);
}
}
Dragoon
class Dragoon extends Protoss
{
public Dragoon(int a, int b)
{
super(a,b);
}
{
Type="Dragoon";
TotalHealth=100;
CurHealth=100;
TotalShield=80;
CurShield=80;
GdStrength=20;
AirStrength=20;
}
}
Marine
class Marine extends Terran
{
public Marine(int a, int b)
{
super(a,b);
}
{
Type="Marine";
TotalHealth=40;
CurHealth=40;
GdStrength=6;
AirStrength=6;
}
}
As you can see, we've added
in the new fields of CurShield
and TotalShield
as well as a RegenShield
method in the Protoss class. While this looks cool and demonstrates the theoretical
uses of OOP programming, this is not always the ideal solution. For example,
it is sometimes useful to create an array of the base class (e.g. Person[])
and then assign subclass objects (e.g. Dragoon)
to the elements of the array. However, once you do this, you will not be able
to access any additional methods of the subclass (e.g. RegenShield)
as the object is of the superclass type (Person),
which does not have those methods. Sometimes, there are ways to get around this,
with what is called object type casting, but that is not a good way to
handle this. As a result, many people find that it's actually better to include
all the important fields and methods in the base class, regardless of
whether all the subclasses use them. For those that don't, one can just set
the values to 0. So for instance, we could have had the shield variables in
Person, but just set them as 0 for non-Protoss classes.
Nevertheless, we have done
things the more obvious way to demonstrate inheritance in a clear fashion.
Below is code used to test
our classes.
public class sc
{
public static void main(String args[])
{
Dragoon obj1 = new Dragoon(100,100);
Marine obj2 = new Marine(101,101);
System.out.println(obj1.Type+"'s position:
"+obj1.XPos+", "+obj1.YPos);
System.out.println(obj2.Type+"'s position:
"+obj2.XPos+", "+obj2.YPos);
System.out.println("obj2: "+obj2.CurHealth
+"/"+obj2.TotalHealth);
obj1.GdAttack(obj2);
System.out.println("obj2: "+obj2.CurHealth
+"/"+obj2.TotalHealth);
obj2.GdAttack(obj1);
System.out.println("obj1: "+obj1.CurShield+
"/"+obj1.TotalShield+", "+
obj1.CurHealth+"/"+
obj1.TotalHealth);
}
}
This ends our discussion
of inheritance.
D. Polymorphism
In our Person
class, we saw two definitions of the GdAttack
method with different signatures. We said that was called "method overloading."
In OOP, there is a very important concept somewhat similar to method overloading
called polymorphism. Polymorphism refers to the behavior we can observe
by having the methods with the same name and signature in a superclass
and its subclasses. This type of automation is the beauty behind polymorphism.
It is particularly useful when you have many subclasses that you want to share
a method with but each class has a different version of the method. Then, you
might have variable of the superclass type that you assign a subclass to and
call the method. Normally, the superclass method is called, but if you write
the code properly, the subclass method is called (even though the variable itself
is of the superclass type). That way, no matter which subclass type was assigned
to the variable, the matching method will be called.
The following code demonstrates
the concept behind polymorphism. It uses the Random
class provided by Java. Random's
NextInt()
method will generate a random number within the range you provide as an argument.
To use random, we have to import the java.util.Random
library.
import java.util.Random;
class Student
{
public void ShowName()
{
}
}
class Jack extends Student
{
public void ShowName()
{
System.out.println("This is Jack");
}
}
class Joe extends Student
{
public void ShowName()
{
System.out.println("This is Joe");
}
}
class Jill extends Student
{
public void ShowName()
{
System.out.println("This is Jill");
}
}
public class poly
{
public static void main(String args[])
{
Student[] Student = {new Jack(), new Joe(), new Jill() };
Student test;
Random select = new Random();
for (int i=0;i<5;i++)
{
test = Student[select.nextInt(Student.length)];
test.ShowName();
}
}
}
Note the empty definition
of ShowName
in the base class Student.
We have to do this for proper polymorphic behavior since test
is of type Student
and if Student
has no definition for the method, an error will be generated.
If the code is executed
properly, you will get 5 randomly picked names of the three choices.
This ends our discussion
of Object-Oriented Programming. There are a few additional OOP concepts
that we haven't covered, but many of them are Java-specific and we leave
the task to learn them to the reader.
(Please note: All
Starcraft-related names and terms usd in our examples in this section
eare trademarked to Blizzard Entertainment and are not endorsed by Thinkquest
nor Team C0120962)
|