Object-Oriented Concepts
Home - About Us
 

Object-Oriented Design

If a programming language is referred to as an object-oriented language then it should contain these three traits:
  • Encapsulation
  • Inheritance
  • Polymorphism

Each of these traits in examined in the following sections.

Encapsulation

At the core of an object is its data. In addition to containing data, an object also has methods which can view and modify the data.  Encapsulation refers to the encapsulation, or protection, of an object's data.  This protection is usually accomplished by making the data private, which prevents direct access of the data by any code outside of the object. Access to the data, for viewing or modification, is accomplished by putting public methods in the object.  Since the method is part of the object it can access the private data, and since the method is a public method it can be accessed via code outside of the object.  Therefore, outside code can access a public method, which in turn can access the private data. The public method should be written in so that it always maintains the integrity of the data.  Forcing all outside code to access an object's private data via only a handful of well-written public methods makes the data well-protected (i.e., encapsulated).

In the example below the class Account exemplifies encapsulation.  The data is declared private (a balance field and a taxId field).  These two private data members are accessed by a handful of public methods (a constructor, deposit, withdrawal, getBalance and getId methods).  The SmallBank class demonstrates a typical usage of Account objects.

import java.awt.*;
import java.awt.event.*;

/**
   The SmallBank class creates a couple of Account objects to demonstrate how to 
   use the Account objects.
*/

public class SmallBank
{
   public SmallBank()
   {        
      Account first = new Account(1112223333, 0.0);
      Account second = new Account(333440000, 500.0);
      
      second.deposit(200.00);
      second.deposit(150.00);
      second.withdraw(50.00);
      
      second.deposit(-1.00);
      
      first.deposit(5.00);
   
      System.out.println("Balance for the first object: $" + first.getBalance());
      System.out.println("Balance for the second object: $" + second.getBalance());
   }
   
   public static void main(String args[])
   {
      System.out.println("Starting application");
      SmallBank bank = new SmallBank();
      System.out.println("Ending application");
   }
}

/**
 This Account class demonstrates an encapsulated object:
 
 To fully protect the data in the object:
  -  The data fields are all "private", so they can ONLY BE ACCESSED VIA THE PUBLIC METHODS.
  -  Only valid objects should be constructed -- invalid constructor arguments
     cause an exception to be thrown, which the client code should catch (and act
     accordingly).
  -  Deposit and withdraw error check the amount
  -  There is only a "getID" method.  No "setID" method is allowed.  If someone wants an
     account with a different ID, they must create a new account object.
*/

class Account
{  
   private double balance;
   private int taxId;

   public Account(int tmpTaxID, double tmpBalance)
   {
      if (tmpBalance < 0.0)
         throw new IllegalArgumentException("Negative balance not allowed: " + tmpBalance);
      else
         balance = tmpBalance;
         
      if (tmpTaxID < 0)
         throw new IllegalArgumentException("Negative tax IDs not allowed: " + tmpTaxID);
      else
         taxId = tmpTaxID;
   }
   
   public void deposit (double amount)
   {
      if (amount >= 0.0)
         balance += amount;
   }
   
   public void withdraw (double amount)
   {
      if (amount >= 0.0)
         balance -= amount;
   }
   
   public double getBalance()
   {
      return balance;
   }
   
   public int getID()
   {
      return taxId;
   }
}

Inheritance

In the example below, the class Animal describes a simple set of data values and operations that are needed to describe a barn yard animal. To keep things simple, the data was was limited to the animals name, and the operations are just setName, getName, and sound (the sound operation gives the animals sound). Notice that the sound method is declared "abstract". The abstract keyword forces any subclass of the Animal class to define a sound method (or else the subclass must also define itself as abstract). Since the Animal class is meant to have only the general properties of a general animal, and is not intended to represent any specific kind of animal, an implementation of sound is not supplied by the Animal class. Since there is an abstract method in the Animal class, the entire class has to be labeled abstract. An abstract class can never be instantiated -- its only purpose is to be the building block of other classes that can be instantiated (these non-abstract classes are referred to as concrete classes).

The Cow class extends Animal, and supplies the required sound method to give the expected sound of a cow. The Cat class also extends Animal, and its sound method gives the sound of a cat. The properties and methods that are common to both the Cow and the Cat objects are kept in their common Animal superclass (in this case the name value and the associated set and get methods). The method(s) that need to be different in each specific subclass (i.e., the sound method) are supplied separately in the Cow and Cat classes.

public class Farm
{
	public static void main(String [] args)
	{
		Cow guernsey = new Cow("Two Percent");
		Cat tabby = new Cat("Stripes");
		Cat barnCat = new Cat("Mouser");
	
		tabby.setName("Sleepy");
		System.out.println("This cow's name is: " + guernsey.getName());
	
		guernsey.sound();  
		tabby.sound();
		barnCat.sound();

		guernsey.topSpeed();  // cow only
		tabby.attitude();     // cat only
	}
}


abstract class Animal
{
	private String name;

	public Animal(String name)
	{
		this.name = name;
	}

	public void setName(String newName)
	{
		this.name = newName;
	}

	public String getName()
	{
		return name;
	}

	abstract public void sound();
}


class Cow extends Animal
{
	public Cow(String name)
	{
		super(name);
	}

	public void sound()
	{
		System.out.println("Moooo.  Mooooooooooo.");
	}

	public void topSpeed()
	{
		System.out.println("Three miles an hour.");
	}
}


class Cat extends Animal
{
	public Cat(String name)
	{
		super(name);
	}

	public void sound()
	{
		System.out.println("Meow.");
	}

	public void attitude()
	{
		System.out.println("Feed me.  Then I'll sleep.");
	}
}

Polymorphism

If you have a reference to an object (with, for example, the name ref) and you are going to call a method named run in the referenced object, then you would write a line of code such as this one:

	ref.run();

Usually the compiler can identify (and bind to) the run method that will get executed (this is called early binding). Here is the UML class diagram for a very Simle class:

Simple UML class diagram

with the corresponding code for the Simple class, plus a small driver class named SimpleApp:

public class SimpleApp
{
   public static void main(String [] args)
   {
      Simple ref = new Simple();
      ref.run();
   }
}

class Simple   // Make the the super class to two subclasses
{
   public void run()
   {
      System.out.println("Running, running.");
   }
}

This is a visualization of the object created by SimpleApp:

Simple object diagram

If the run method is defined in a super class, and that class has a couple of subclasses (for example, SubA and SubB) and the subclasses override the run method:

UML class diagram

The code below implements the classes SubA and SubB:

public class SimpleApp
{
   public static void main(String [] args)
   {
      Simple ref = new Simple();
      ref.run();
      
      SubA sa = new SubA();
      sa.run();
      
      Simple core = (Simple) new SubA();
      core.run();
   }
}

class Simple   // Make the the super class to two subclasses
{
   public void run()
   {
      System.out.println("Running, running.");
   }
}

class SubA extends Simple
{
   public void run()
   {
      System.out.println("AAAAAaaaaaaaa");
   }
}

class SubB extends Simple
{
   public void run()
   {
      System.out.println("B!");
   }
}

In the code shown above, two SubA objects are created. One is referenced by a SubA reference sa and the other by a Simple reference core. Both references are used to call the run method. The call sa.run( ) directly calls the SubA version of run. The call core.run( ) initially references the run method in Simple, but since the run method has been overridden in the subclass SubA, the rules of polymorphism dictate that the subclass's run method get called (i.e., this also runs the SubA run method). Therefore, both sa.run( ) and core.run( ) generate the output:

	AAAAAaaaaaaaa

The term polymorphism means "many shapes". One superclass reference variable can possibly reference more than one kind of subclass object, and thus be used to call different instances of a given method (such as the run method above) depending on which subclass object is currently being referenced. For example, in the code shown below, a random number generator is used to determine whether the reference ref will be referencing a SubA object or a SubB object:

public class SimpleApp
{
   public static void main(String [] args)
   {
      Simple ref;    // refrence to the super class Simple

      if (Math.random() < 0.5)
         ref = new SubA();
      else
         ref = new SubB();


      ref.run();    // What is printed here???
   }
}

class Simple   // Make the the super class to two subclasses
{
   public void run()
   {
      System.out.println("Running, running.");
   }
}

class SubA extends Simple
{
   public void run()
   {
      System.out.println("AAAAAaaaaaaaa");
   }
}

class SubB extends Simple
{
   public void run()
   {
      System.out.println("B!");
   }
}

Since the call to Math.random( ) randomly generates a number between 0.0 and 1.0, about half the time a SubA object is created and half the time a SubB object is created. Therefore, at compile time, there is no way to know which run method is to be called by this line of code:

	ref.run();

It is only possible to determine which run method will be executed at the time the line of code is performed, by following the reference to the object and discover if it is a SubA object to execute a SubA run method, or if it is a SubB object to execute a SubBrun method.  It is impossible to determine the correct run method at compile time.  Determining the correct method at run-time is called late binding and this the trait known as polymorphism.

SubA or SubB object?

If you run the latest version of the SimpleApp application many times, about half the time the output will be

	AAAAAaaaaaaaa

and about half the time it will be

	B!

Exercises

  1. Modify the Farm class's main method so that it creates an array of ten farm animals (Cats and Cows). One array can be used to keep track of both types of animals (the array should be of type Animal). Set the array up so that it contains a random mixture of Cows and Cats. Then use a for loop to have each animal report its sound. The array has the name arr, then the loop should look something like this:

       for (int i = 0; i < arr.length; i++)
          arr[i].sound();
    

 

Home - About Us
Copyright © 2006 by Kiowok, Ann Arbor, Michigan, USA