The Observer interface and the Observable class in the java.util package are Java's implementation of the publisher-subscriber design pattern.
Brick CAD
Brick CAD is a CAD-CAM system for designing bricks. Luckily, bricks are fairly easy to design. They only have three attributes: height, width, and length. Brick CAD allows users to view bricks four different ways: front view, side view, top view, and control panel view. Of course multiple instances of each view are allowed:
Using a control panel view to update the brick's dimensions automatically updates all views of the brick.
Static Structure
Brick CAD refines the model-view-controller architecture by making models observables and views observers:
Brick
Bricks are pretty simple observables:
public int getLength()
{ return length; }
public int getWidth()
{ return width; }
public int getHeight()
{ return height; }
public void setDim(int
l, int w, int h)
{
length = l;
width = w;
height = h;
setChanged();
notifyObservers();
}
public void hideViews()
{
setChanged();
notifyObservers("QUITTING");
}
}
I decided to make views panels. Another option is to make views frames:
public void finalize() { myBrick.deleteObserver(this); }
class MyWindowListener
extends WindowAdapter
{
public void windowClosing(WindowEvent we) { setVisible(false); }
}
public void update(Observable
brick, Object arg)
{
repaint();
if (arg != null && arg.equals("QUITTING"))
{
myBrick.deleteObserver(this);
setVisible(false);
}
}
protected void updateMetrics()
{
Dimension d = getSize();
Insets in = getInsets();
int clientWidth = d.width - in.right - in.left;
int clientHeight = d.height - in.bottom - in.top;
int xUpperLeft = in.right;
int yUpperLeft = in.top;
xCenter = xUpperLeft + clientWidth/2;
yCenter = yUpperLeft + clientHeight/2;
}
protected int xCenter,
yCenter;
protected Brick
myBrick;
protected Color
brickRed = new Color(220, 10, 60);
}
The side, front, and top views only differ in their titles and the brick dimensions they are interested in:
public void paint(Graphics
g)
{
int d1 = myBrick.getLength();
int d2 = myBrick.getWidth();
int x = xCenter - d1;
int y = yCenter - d2;
g.setColor(brickRed);
g.fillRect(x, y, d1, d2);
}
}
class PanelView extends BrickView
{
private TextField heightField, widthField, lengthField;
public PanelView(Brick brick, Frame parent)
{
super(brick, parent, "Panel View");
setLayout(new GridLayout(4, 1));
heightField = new TextField(10);
heightField.setText("" + brick.getHeight());
LabeledTextField ltf1 =
new LabeledTextField("Height",
heightField);
add(ltf1);
widthField = new TextField(10);
widthField.setText("" + brick.getWidth());
LabeledTextField ltf2 =
new LabeledTextField("Width",
widthField);
add(ltf2);
lengthField = new TextField(10);
lengthField.setText("" + brick.getLength());
LabeledTextField ltf3 =
new LabeledTextField("Length",
lengthField);
add(ltf3);
Button apply = new Button("Apply");
Panel p = new Panel();
p.add(apply);
add(p);
apply.addActionListener(new ApplyListener());
}
public void update(Observable brick, Object arg)
{
super.update(brick, arg);
lengthField.setText("" + myBrick.getLength());
widthField.setText("" + myBrick.getWidth());
heightField.setText("" + myBrick.getHeight());
}
class ApplyListener implements ActionListener
{
public void actionPerformed(ActionEvent
e)
{
int w = Tools.toInt(widthField.getText());
int h = Tools.toInt(heightField.getText());
int l = Tools.toInt(lengthField.getText());
myBrick.setDim(l,
w, h);
}
}
}
This is a useful component that belongs in my util package. It creates a 2X1 panel containing a label and a text field:
public LabeledTextField(String
lab, TextField tf)
{
setLayout(new GridLayout(1, 2));
Panel p1 = new Panel();
myLabel = new Label(lab);
p1.add(myLabel);
add(p1);
myField = tf;
Panel p2 = new Panel();
p2.add(tf);
add(p2);
}
}
The application is identified with a frame called BrickCAD. The frame has three menus and keeps a reference to the current brick being modeled:
protected Menu fileMenu,
viewMenu, helpMenu;
protected MenuBar
mbar;
protected Object[]
fileItems, viewItems, helpItems;
protected String
aboutText = "Brick CAD version 1.0";
protected Brick
currentBrick = null;
// view menu
viewItems =
new Object[] { "Side", "Top", "Front", "Panel" };
viewMenu = Core.makeMenu("View", viewItems, this);
mbar.add(viewMenu);
// help menu
helpItems = new Object[] { "About" };
helpMenu = Core.makeMenu("Help", helpItems, this);
mbar.add(helpMenu);
setMenuBar(mbar);
}
if(arg.equals("Quit")) handleQuit();
else if (arg.equals("New Brick")) handleNew();
else if (arg.equals("Side")) handleSide();
else if (arg.equals("Front")) handleFront();
else if (arg.equals("Top")) handleTop();
else if (arg.equals("Panel")) handlePanel();
else if (arg.equals("About")) handleAbout();
else System.err.println("Selected item = " + arg);
}
protected void handleAbout()
{
MessageDialog mg = new MessageDialog(this, "About", aboutText);
mg.setVisible(true);
}
protected void handleFront()
{ ... }
protected void
handleTop() { ... }
protected void
handlePanel() { ... }