The Framework

Layered architectures divide applications into three layers:

How much can be done in the framework package? How much can be done without knowing anything about the business and presentation layers.

Terminology: Applications built on top of a reusable framework are called customizations. The person or team developing the customization are called customizers. They may be domain experts with little knowledge about software architecture or other details dealt with by the framework.

The Model

Usually business data and logic will reside in a network of business package classes accessible from some root class that we refer as the model.

The model needs to be saved to and read from files (serialized and de-serialized) and it needs to inform observers when it's modified. Because these are functions of any model, we can implement them in the framework package:

abstract public class Model extends Observable implements Serializable {
 
  private String fileName = null;
  private Boolean unsavedChanges = false;
  // called by customization:
  public void changed() {
     unsavedChanges = true;
     this.setChanged();
     this.notifyObservers();
     this.clearChanged();
  }
  // etc.
}

The root package in the business package extends the model:

class Brick extends Model { ... }

class Maze extends Model { ... }

Views

Views of the model in the presentation package have much in common. We can move these commonalities to the framework package:

abstract public class View extends JComponent implements Observer {
  protected Model model;
  public View(Model model) { setModel(model);}
  public View() { this(null); }
  public void update(Observable subject, Object msg) {repaint();}
  public void setModel(Model model) {
     if (this.model != null) this.model.deleteObserver(this);
     this.model = model;
     if (this.model != null) {
        this.model.addObserver(this);
        this.update(model, null); // update myself
     }
  }
  // etc.
}

Each view in the presentation package inherits a protected model reference they can cast:

public class SideView extends View {
 
  public SideView(Brick brick) { super(brick); }

  public void paintComponent(Graphics gc) {
     if (model != null && model instanceof Brick) {
        Brick brick = (Brick) model;
        // draw a red rectangle, etc.
     }
  }
}

Commands

The Commands-as-Objects pattern represents commands as objects that know how to execute themselves:

abstract public class Command {
  protected Model model;
  abstract public void execute();
  // constructors, etc.
}

In the presentation layer the programmer creates a new class for each type of command:

public class SetHeight extends Command {
  private Double newHeight;
  public SetHeight(Brick brick, Double newHeight) {
     super(brick);
     this.newHeight = newHeight;
  }
  public SetHeight(Brick brick) { this(brick, null); }
  public void execute() {
     if (model != null && model instanceof Brick) {
        Brick brick = (Brick)model;
        if (newHeight == null) {
           // newHeight = ask user for new height
        }
        brick.setHeight(newHeight);
     }
  }
}

The Command Processor

Commands are executed by a singleton command processor:

public class CommandProcessor {
  public static void execute(Command cmmd) { cmmd.execute(); }
}

The command processor can do additional work such as implementing an undo/redo mechanism in the framework.

A listener simply creates a command and ships it to the command processor:

public void actionPerformed(ActionEvent ae) {
     JTextField source = (JTextField)ae.getSource();
     String cmmd = ae.getActionCommand();
     Command command = null;
     try {
        double dim = Double.valueOf(source.getText());
        if (cmmd == "Set Height") command = new SetHeight(brick, dim);
        else if (cmmd == "Set Width") command = new SetWidth(brick, dim);
        else if (cmmd == "Set Length") command = new SetLength(brick, dim);
        CommandProcessor.execute(command);
     } catch (NumberFormatException e) {
        // ...
     }
  }

The AppPanel

A control panel (JPanel in Swing) contains controls (buttons, text fields, etc.) and views.  (Think of the dashboard of a car.)

A framework AppPanel is the JPanel framed by the AppFrame (see below):

public class AppPanel extends JPanel implements Observer {
  protected Model model;
  protected ActionListener listener;
  protected Set<View> views;
  public void update(Observable subject, Object msg) {
     // override in a subclass if desired
  }
  public void setModel(Model model) {
     if (this.model != null) this.model.deleteObserver(this);
     this.model = model;
     if (this.model != null) this.model.addObserver(this);
     for(View view: views) view.setModel(model);
  }
  public void add(View view) {
     super.add(view);
     views.add(view);
  }
 
  // etc.
}

By my definition, an app panel provides both input mechanisms (in the form of controls) and output mechanisms (in the form of views). Views are model observers, so they will get notified when the model changes. But some controls, like text fields, also provide output (e.g., the current height, width, and length of the brick) and so need to be updated when the model changes. For this reason the app panel also needs to be notified when the model changes.

I keep track of all of the views in the framework to simplify setModel because if the app panel gets a new model (this happens with the open and new commands), it needs to pass it on to all of the views.

The default listener for the app panel is the app frame because often the app panel controls duplicate menu items that the frame already listens to. But the listener can easily be replaced in the customization.

The

public class BrickPanel extends AppPanel {
 
  private JTextField heightField, widthField, lengthField;

  public BrickPanel(Brick brick, ActionListener listener) {
     super(brick, listener);
     // add text fields, labels, and views
  }

  public void update(Observable subject, Object msg) {
     Brick brick = (Brick)model;
     // update text fields
  }
}

In BrickCAD the dimensions of a brick can be changed from the menus. The brick panel will be notified and can update its fields.

Factories

The framework knows how to assemble the application, but doesn't know how to build the components specified in the customization. This is a perfect application for the Abstract Factory Pattern:

public interface AppFactory {
  public Model makeModel();
  public AppPanel makePanel(Model model, ActionListener listener);
  public String[] getEditCommands();
  public Command makeEditCommand(Model model, String type);
  public String getTitle();
  public String[] getHelp();
  public String about();
}

The AppFactory is customized in the presentation layer:

class BrickFactory implements AppFactory {
  public Model makeModel() { return new Brick(); }
  public String[] getEditCommands() {
     return new String[] { "Set Height", "Set Width", "Set Length"};
  }
  public Command makeEditCommand(Model model, String type) {
     if (type == "Set Height") return new SetHeight(model);
     // etc.
  }
  public String getTitle() { return "Brick CAD"; }
  // etc.
}

AppFrame

The factory is used by the AppFrame to assemble the application:

public class AppFrame extends JFrame implements ActionListener {
 
  private AppFactory factory;
  private Model model;
  private AppPanel panel;
 
  public AppFrame(AppFactory factory) {
     this.factory = factory;
     model = factory.makeModel();
     panel = factory.makePanel(model, this);
     getContentPane().add(panel);
     setJMenuBar(createMenuBar());
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     setTitle(factory.getTitle()); 
     setSize(500, 500);
  }
 
  public void display() { this.setVisible(true); }
 
  public void setModel(Model model) {
     // used by open and new, details below
  }
 
  protected JMenuBar createMenuBar() {
     JMenuBar bar = new JMenuBar();
     // add file, edit, and help menus
     JMenu fileMenu = // ...
     JMenu helpMenu = // ...
     JMenu editMenu =
         Utilities.makeMenu("Edit", factory.getEditCommands(), this);
     // now add menus to bar
     return bar;
  }

  public void actionPerformed(ActionEvent ae) {
     String cmmd = ae.getActionCommand();
    
     if (cmmd == "Save") {
        Utilities.save(model, false);
     } else if (cmmd == "SaveAs") {
        Utilities.save(model, true);
     } else if (cmmd == "Open") {
        Model newModel = Utilities.open(model);
        setModel(newModel);
     } else if (cmmd == "New") {
        Utilities.saveChanges(model);
        setModel(factory.makeModel());
        // needed cuz setModel sets to true:
        model.setUnsavedChanges(false);

     } else if (cmmd == "Quit") {
        Utilities.saveChanges(model);
        System.exit(1);
     } else if (cmmd == "About") {
        Utilities.inform(factory.about());
     } else if (cmmd == "Help") {
        Utilities.inform(factory.getHelp());
     } else {
        Command command = factory.makeEditCommand(model, cmmd);
        CommandProcessor.execute(command);
     }
  }
}

To start an application main creates a brick factory, uses it to create an app frame, then displays the app frame:

class BrickCad {
  public static void main(String[] args) {
     AppFrame app = new AppFrame(new BrickFactory());
     app.display();
  }
}

Notes

·       The abstract factory is used to build the model, app panel, and edit menu. It also makes edit commands and provides information about help, about, and title.

·       Because they're so generic, we don't need command objects to represent the file and help menu items. These can be executed directly by the app frame.

·       The app frame assumes it is the listener for the app panel. This can be changed in the customization.

New and Open

The "New" and "Open" items on the file menu produce a new model, which is passed to the app frame's setModel method. There are two strategies for implementing this method.

Update Components Method

The app frame's setModel calls the app panel's setModel:

public void setModel(Model model) {
  this.model = model;
  panel.setModel(model);
}

The setModel of the app panel sets the model of all views

public void setModel(Model model) {
  model.deleteObserver(this);
  this.model = model;
  model.addObserver(this);
  for(View view: views) view.setModel(model);
}

Each view removes itself from the old model before attaching itself to the new one. Then, it updates itself:

public void setModel(Model model) {
  if (this.model != null) this.model.deleteObserver(this);
  this.model = model;
  if (model != null) {
     this.model.addObserver(this);
     this.update(this.model, null);
  }
}

Copy Method

The copy method is a bit easier, but requires some work by the customizer. The idea is to reuse the old model's memory location by simply copying the fields of the new model into the old one. That way we don't need to update the model references of all of the components.

public void setModel(Model model) {
  this.model.copy(model)
}

We need to add a copy method to the Model class:

public void copy(Model other) {
  this.fileName = other.fileName;
  this.unsavedChanges = other.unsavedChanges;
}

Of course this must be overridden in the Brick class:

public void copy(Model other) {
  super.copy(other);
  Brick b = (Brick)other;
  height = b.height;
  width = b.width;
  length = b.length;
  changed();
}

This method can also be used to implement an undo/redo mechanism in the framework. The idea is that each command makes a copy of the model before modifying it. To undo the command the model copies the copy.

Utilities

Recall that utility (aka service) class members are static. They are functions not methods. What's the difference? Methods describe object behaviors. There's always an implicit parameter named "this" that points to the object exhibiting the behavior. Think of bank account methods such as deposit and withdraw. On the other hand, all of a function's parameters are explicit.

Useful framework functions can be placed in a utilities class: Utilities.java.

Additional functions might include parsers for converting strings to numbers, a random number generator, a log, a color selector, etc.