Object-Oriented Modeling of a User Interface


It is best to read these notes after the object modeling lecture. They might not make sense if you read them beforehand.
These provide notes a summary of the important points associated with object-oriented modeling. Object-oriented modeling emphasizes the point that you should create explicit objects for each of the important elements in your view, and that these objects should take care of such things as rendering themselves and indicating whether or not they contained a mouse point. Later in the semester we will flesh out the object model by adding display managers and event handlers. Your projects should make use of object-oriented modeling.


Modeling View Elements as Objects

When designing your interface, you want to identify the important elements in your graphical interface and define classes for each of them. For example, in a game you are likely to have text strings for scores, timers, and instructions, and certain types of game pieces. You should therefore create a class for labels and a class for each type of game piece. If you had a boxes and arrows editor, then you might create separate classes for the boxes and the arrows. If there were different types of boxes, as you might have in a flow chart, then you might create a superclass for an abstract box and then concrete boxes for each of the box types. Even feedback objects should typically be modeled as objects and have their own draw methods.

Next you should decide assign properties to each of your objects. The properties allow you to customize the appearance of your objects. Typical properties include location and size, and often include color and line style. Each type of object might have its own unique properties as well. Labels might need a string and a font. An image class might need a source property that specifies where to find the bitmap, tiff, gif, or jpeg file for that image.

Finally you should write the draw and contains methods for each of your classes. The draw class should take the graphical context as its parameter and should use the use the various properties of the object to set up the graphics context and then draw the object to the screen. The paintComponent method should be a high-level manager that simply calls the draw methods of the various objects. Rarely should paintComponent have to resort to setting up the graphics context or making toolkit calls, such as drawRect, to draw objects.

The contains method should take the x ande y values of a mouse event and return true or false based on whether or not the object contains the mouse point.


Composition

If you want to inherit an object's implementation but not its interface, use composition. Composition is implemented by making object A be a container for another object, B, and having A delegate all of its implementation to B's methods. We say that A is composed from B.

The classic example of wanting to inherit an object's implementation but not its interface is based on a circle and an ellipse. A circle should be a restricted version of an ellipse, with the width and height constrained to be equal. As such it seems natural to make a circle be a subclass of an ellipse. Unfortunately, the circle does not want to inherit the setWidth or setHeight methods from an ellipse. Instead it wants to provide a setRadius and/or setDiameter method. However, the circle does want to inherit all of the ellipse's implementation.

Instead of making a circle a subclass of an ellipse, you should use composition to define the circle. This involves making a circle be a container object for an ellipse. For example:

class circle extends AbstractShape {
  ellipse my_ellipse;
}
Then implement each of circle's methods by delegating their implemention to the appropriate method in the ellipse:
  public draw(Graphics g) {
    my_ellipse.draw(g);
  }
  public setLeft(int left) {
    my_ellipse.setLeft(left);
  }
  public getLeft() { return my_ellipse.getLeft(); }
 
  public setRadius(int r) {
    my_ellipse.setWidth(2*r);
    my_ellipse.setHeight(2*r);
  }
  public getRadius() { return my_ellipse.getWidth(); }
Notice that in getRadius it does not matter whether we call the ellipse's getWidth or getHeight methods, because both will return the same value. Also note that setRadius calls two of the ellipse's methods, in order to ensure that the width and height will be the same.


Factorization

When designing an inheritance hierarchy I find it easiest to construct it from the bottom-up (i.e., from the leaves to the root). I often define all of the concrete classes that I will need and assign them instance variables and methods. I then try to factor out common instance variables and methods and create a new superclass to contain those instance variables and methods. This allows me to avoid duplicating code in different classes. For example, a rectangle, an ellipse, an image, and a text object all have instance variables for left, top, width, height, and visible. Hence I would factor out these instanced variables and place them in a new superclass called BoxShape. Later I might find that the visible variable is a common feature of both a BoxShape, a LineShape, and a PolyLine, and hence I can factor out that variable and promote it to a new superclass, called AbstractShape. In a similar manner, the draw and contains methods end up in the AbstractShape class, because they are common to every shape in the hierarchy. Get and Set methods for the left, top, width, and height classes would appear in the BoxShape class, because those methods are common to all shapes in the BoxShape sub-hierarchy.

You must also be prepared to be flexible and if necessary, move elements around your inheritance hierarchy. For example, you might initially make a Circle be a subclass of BoxShape, but because you end up using composition, rather than inheritance, to implement it, it eventually becomes a subclass of AbstractShape.

Finally, you should not be afraid to create an implemention for a method that will be used by most, but not all, of the subclasses. For example, it would be right to create an implementation for the containsMousePoint method and place it in the BoxShape class, because it would be used by the rectangle, text, and image classes. The ellipse class might choose to override the containsMousePoint method with its own implementation, but at least three classes would share the containsMousePoint code placed in the BoxShape class.