Designing and Writing Classes

What is a class? Why are classes important? How do you go about designing and writing classes?

Purpose

Software systems manipulate different kinds of things like accounts, banks, cards, decks of cards, rational numbers, dates, windows, animals, buildings, countries, players, toolbars, menus. In many programming languages, the kinds of things are represented with classes.

A class has a name, a set of properties, and a set of operations. Here is an example of a polygon class, written in the UML:

polygonuml.png

By the way, a good resource for UML diagrams is Scott Ambler's.

Here are the classes in Java

/**
 * An immutable point class.
 */
public class Point {
    public static final Point ORIGIN = new Point(0, 0);
    private double x;
    private double y;
    public Point(double x, double y) {this.x = x; this.y = y;}
    public double getX() {return x;}
    public double getY() {return y;}
    public static Point mid(Point p, Point q) {
        return new Point((p.x + q.x)/2.0, (p.y + q.y)/2.0);
    }
    public double distanceFromOrigin() {return Math.sqrt(x*x + y*y);}
    public Point reflectionAboutOrigin() {return new Point(-x, -y);}
}
/**
 * A polygon containing at least three vertices.
 */
public class Polygon {
    private Point[] vertices;

    public Polygon(Point[] vertices) {
        if (vertices.length < 3) {
            throw new IllegalArgumentException("Need at least 3 vertices");
        }
        this.vertices = new Point[vertices.length];
        System.arraycopy(vertices, 0, this.vertices, 0, vertices.length);
    }

    public double getPerimeter() {
        throw new UnsupportedOperationException("Not done yet");
    }

    public double getArea() {
        throw new UnsupportedOperationException("Not done yet");
    }

    public Point[] getVertices() {
        Point[] result = new Point[vertices.length];
        System.arraycopy(vertices, 0, result, 0, vertices.length);
        return result;
    }

    public void addVertex(int index, double x, double y) {
        throw new UnsupportedOperationException("Not done yet");
    }

    public void updateVertex(int index, double x, double y) {
        if (index < 0 || index > vertices.length) {
            throw new IllegalArgumentException("No such index: " + index);
        }
        vertices[index] = new Point(x, y);
    }

    public void removeVertex(int index, double x, double y) {
        throw new UnsupportedOperationException("Not done yet");
    }
}
Exercise: The polygon constructor used System.arraycopy instead of making new Point objects with the same coordinates as the points in the argument array. This kind of thing is usually suspect, but in this case it is perfectly safe. Explain why initializing with references to arugument objects is sometimes suspect and why it is safe here.
Exercise: Implement all of the unfinished methods.

Aspects of Classes

When we design classes we pay attention to three aspects:

SPECIFICATION
The protocol, interface, "contract", or behavior. Given primarily by constructor and method signatures.
REPRESENTATION
(should be hidden) The low-level structural details. Given by the field declarations.
IMPLEMENTATION
(should be hiden) The bodies of the constructors and methods.
Exercise: Identify the specification, representation, and implementation in the Point class above.

Structure of Classes

The most visible structural components of classes (in most languages) are

Exercise: Identify and describe the properties and operations of the Point class above.

Some Design Considerations

A few tips for class designers follow.

The most important thing about operations

Think: every operation should succeed or fail. If it succeeds, great; if it fails, throw an exception. Do not send back "failure codes" to clients if you can help it. Doing so puts the burden of checking on the client and many times the client programmer forgets the check.

Exercise: Describe how you might implement an operation to get the integer value of a string, knowing that an arbitrary string might not look like a integer at all. Talk about various design alternatives.

Hide the Representation

Hiding the representation is almost always a neccessity, for these four primary reasons:

In practice this means:

Keep Interfaces Small

Don't stuff the class full of too many fields or too many methods. For example if your Customer class has fields called street, city, state and zip as well as name and account number, you should introduce a new class called Address. If you have way too many methods, you may need to think about factoring the responsibilities of the class into two or more classes.

Consider Immutability

Immutable objects (objects whose values never change after they have been created) can be awesome. They are

Exercise: Read the section on immutability in Bloch's text.
Exercise: The point class above is immutable. Write an immutable polygon class using this point class.

Consider Factory Methods

Somtimes you'll want to hide constructors and instead expose methods that return new objects (called static factory methods). Advantages:

Exercise: Read the section on static factory methods in Bloch's text.
Exercise: (Somewhat contrived) Rewrite the point class above to use a private constructor and add a factory method. Add a caching mechanism so that separate point objects with the same value are never created.

Read a Classic Book

Read Effective Java by Joshua Bloch, and/or Effective C++ by Scott Meyers.