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:

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
- Properties (also called fields, slots, attributes, or member variables)
- Operations (also called methods or member functions). Some people like
to talk about different kinds of operations like
- Constructors
- Destructors
- Accessors (a.k.a. selectors): read-only operations for reading state
- Modifiers (a.k.a. mutators): read-write operations for modifying state
- Iterators (for classes representing some kind of container): perform an
operation on each of the elements of the container object.
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:
- It is easier to write the client: the client code will not be cluttered with
low level details about the type.
- The representation may be more general than necessary, and some protocol
is required to restrict values to a legal range. For example you would
not want to expose a balance field in an account class, since
someone could just assign a negative value. You should guard field
updates with methods that can check these kinds of things.
- Field updates may require side effects that must be done every time,
so the updates should only be permitted through ADT operations that
can ensure that they are done. For example, we don't want direct
updates to a balance field since this would bypass security
checks and transaction logging.
- It allows the representation to change without having to rewrite the client!
For example if a bank maintained a list of accounts, and we later wanted
to change to a map instead, nothing in the clients programs would have
to change since they never referenced the list directly.
In practice this means:
- Always keep the data members (fields) private, and always initialize
them if it makes sense to do so.
- Note that you don't have
to, and often don't want to supply getter and setter methods for
every field. In general, don't "expose" too much; private methods
are sometimes great!
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
- Simpler than mutable ones
- More secure
- Inherently thread-safe
- Able to be shared freely (you only need one instance per
value)
- Never need to be defensively copied
- Able to have their internals freely shared
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:
- You can get around the problem of not being able to
have two constructors with the same signature: use two
methods with different names.
- They don't have to create new objects; they
can return a cached copy.
- They can return an object of a subtype.
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.