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?