Shapes Class


The program below is a text-mode application and a small class hierarchy representing geometric shapes.  The main program allows the user to create an instance of one of the classes one at a time in a loop, and then calls the methods of the object created, demonstrating inheritance and polymorphism in action.  Since the program is text mode, it does not really "draw" any shapes; rather the shapes "describe themselves" with words and numbers.  After you understand this program, try to do the self-test exercises given at the bottom.


// TxtShapes Application
// Jim Henry 6/22/97
// Demonstrates a simple "shapes" class hierarchy demonstrating 
// inheritance and polymorphism.
//
// 9/6/01 - changed input to 1.1 methods; other small changes.

import java.io.*;

//*******************************************************************
// Driver program to create and display Shapes
//
public class TxtShapes
{
//*****************************************
//
public static void main(String args[])
  {
  InputStreamReader inStrReader = new InputStreamReader(System.in);
  BufferedReader    bufReader   = new BufferedReader(inStrReader);

  String s = "";
  Shape shape; // a Shape REFERENCE; no object yet

  while (!s.equalsIgnoreCase("q"))
    {
    // If no object has been created, OR if user has made an invalid choice
    // we don't want to display a Shape below. Thus we use the Shape
    // reference itself as a kind of flag.
    shape = null; 

    // Get user's choice
    System.out.println("R)ectangle C)ircle T)extRectangle Q)uit?");
    s = readln(bufReader);

    // Create a new object. After 1st time through loop, the old
    // Shape object is no longer referenced and will be destroyed
    // by garbage collection.
    // Note that at COMPILE time, the actual type of Shape is unknown.
    // System can only know type at run time.
    // Arguments are hard-coded for simplicity; they could be variables.
    if (s.equalsIgnoreCase("r"))
      shape = new Rectangle(20, 10, "blue");
    else if (s.equals("c"))
      shape = new Circle(15, "red");
    else if (s.equals("t"))
      shape = new TextRectangle(25, 25, "green", "Hello, world");

    // If we have a non-null Shape reference, draw it; else error msg.
    if (shape != null)
      {
      // POLYMORPHISM at work
      shape.draw();
      // all Shapes share the same sayhi() inherited from Shape
      shape.sayhi(); 
      }
    else // shape IS null; anything else except 'q' is an error 
      if (!s.equalsIgnoreCase("q"))
        System.out.println("Invalid choice... try again.");
    } // end while
} // end main()


//*****************************************
// Helper function: reads a line from keyboard
//
static String readln(BufferedReader br)
  {
  String s;

  try
    {
    s = br.readLine();
    return s;
    }
  catch (IOException e)
    {
    System.out.println("I/O Exception in readln()");
    }

  return "";
  } // end readln()
} // end TxtShapes class


//*******************************************************************
// Abstract Shape class - can never itself be instantiated.  Base of
//                        the hierarchy
//
// All classes derived from Shape will have a width, height, and color,
// (these data members are inherited).
// And the ability to "draw" themselves (each must provide actual code
// to do the drawing).
// All will inherit sayhi() - although any could override it
//
abstract class Shape
  {
  int width, height;
  String color;

  // draw() must be written in derived classes so it can be called
  // polymorphically
  public abstract void draw();

  // sayhi() will be inherited in derived classes, but could be overridden  
  public void sayhi()
    {
    System.out.println("hi");
    }
  } // end Shape class


//*******************************************************************
// A Circle: subclass of Shape; has a constructor and a draw() method.
// Note that r is used to determine height and width.
//
class Circle extends Shape
  {
  Circle(int r, String color)
    {
    width = 2*r;
    height = 2*r;
    this.color = new String(color);
    // Alternately: this.color = color;
    // But there is a difference... can you see what it is?
    }

  public void draw()
    {
    System.out.println("I am a Circle with radius " + width + ".");
    System.out.println("I am " + color + ".");
    }
  } // end Circle class


//*******************************************************************
// A Rectangle: subclass of Shape; has a constructor and a draw() method
//
class Rectangle extends Shape
  {
  Rectangle(int w, int h, String color)
    {
    width = w;
    height = h;
    this.color = new String(color);
    }

  public void draw()
    {
    System.out.println("I am a Rectangle " + width + " wide and " + height + " high.");
    System.out.println("I am " + color + ".");
    }
  } // end Rectangle class


//*******************************************************************
// A TextRectangle: is a direct subclass of Rectangle
//
// The constructor calls Rectangle's constructor to store width,
// height and color; then the message is stored.
// The draw() method calls Rectangle's draw() to do most of the
// work and then adds a line to display the "message".
//
class TextRectangle extends Rectangle
  {
  String myMessage;

  TextRectangle(int w, int h, String color, String message)
    {
    super(w, h, color);
    myMessage = new String(message);
    }

  public void draw()
    {
    super.draw();
    System.out.println("Message: " + myMessage);
    }
  } // end TextRectangle class

Self-Study Exercises

1. Add some useful access methods (getXXX() and setXXX()) and calculation methods (area, perimeter,...) to each Shape.  Where should they be defined?  Hint: getWidth() should not be defined in the same place as calculateArea().   Why?

2. Add some new classes to the system - think about how to define them.  For example:

Square - how many arguments are required to define a square?  Should you implement this as a Rectangle with a different constructor?  Or should you create a new class, perhaps derived from Rectangle (since Square is a special kind of Rectangle)?

Triangle - what arguments are in it's constructor?  There are many different triangles with the same base and altitude.  How can you specify a triangle unambiguously?  If you don't supply a width and height in the constructor, could you calculate them from the information given?  This is a bit tricky.

3. Override sayhi() for one class.  (Easy.)

4. Provide an alternate constructor for Circle, allowing the user to specify the circle's bounding rectangle (as 4 ints or 2 Points).  Add a variable to represent radius, and then calculate it and store it in the constructor.  Add getRadius() and setRadius() methods. If a user calls setRadius(), don't forget to update height and width.  If a user calls setHeight(), don't forget to update radius and width.  Is there a better way to do this?

5. Notice that unlike the Line class from the last Self-Study exercise, these Shapes do not have a position.  How would you change these class definitions to implement a position (which could change under program control)?  How about rotation?