CSC 509 Lecture Notes Weeks 5 and 6

CSC 509 Lecture Notes Weeks 5 and 6
MVP Design Discussion



  1. Where Fisher's coming from
    1. At heart, he's a functional programmer (e.g., pure Lisp, ML).
    2. Functional programmers tend to distrust anything that unlocalizes data, such as:
      1. Inheritance
      2. Lexically nested scopes
      3. General broadcast of information to a potentially unknown set of listeners
    3. So, in the broader scheme of things, bear in mind that you're getting an MVP design style from what some (many) might call a warped point of view.
    4. It has some things to offer, when compared and contrasted to other styles; add it to your personal collection of design styles to mull over.

  2. Continuing with MVP design discussion using the Rolodex example, from Notes Week 4.

  3. MV exception handling details
    1. Figure 1 illustrates a localized use of exception handling to process user input errors in the Rolodex GUI dialog.
      
      

      Figure 1: Localized exception handling of input errors.


      
      
      
    2. The figure shows the Model function Rolodex.add throwing an AddInputErrors exception to the companion View function OKAddButtonListener.actionPerformed.
    3. The exception is raised when one or more input errors is detected, based on the processing done the the Rolodex.add model function.

  4. Illustration of Java exception-handling details
    1. Here is the code for OKAddButtonListener.actionPerformed(), corresponding to the design of the AddInputErrors exception shown in Figure 2:

      public void actionPerformed(ActionEvent e) {
      
          try {
              r.add(new Card(
                  new Name(acd.getName()),    // Card name as a String
                  new Id(acd.getId()),        // Card id as an Integer
                  new Age(acd.getAge()),      // Card age as an Integer
                  new Sex(acd.getSex()),      // Sex as an enum literal
                  new Address(acd.getAddr())  // Address as a String
              ));
          }
          catch (AddInputErrors aie) {
              /*
               * Display zero or more error messages, as appropriate.
               */
              rmui.showErrorDialog(aie.getErrorList());
          }
      
      }
      
    2. Here is the code for companion model function that potentially throws the exception.
      /**
       * Add the given card to this if a card of the same id is not already there
       * and there is room for another card.
       *                                                                   <pre>
       * pre:
       *      // There is no card in the input this with the same id as the given
       *      // input card.
       *      not CardAlreadyThere(c)
       *
       *          and
       *
       *      // The values of the given card fields are valid.
       *      CardIsValid(c)
       *
       *          and
       *
       *      // There is room for another card.
       *      (cl.Len() < MAXCARDS)
       *
       * post:
       *      // If none of the preconditions is violated, then a normal
       *      // postcondition holds, else an exception is thrown.
       *      if (not AddPrecondsViolated(c)) (
       *          // A card is in the output this iff it is in the input this or
       *          // it's the new card to be added.
       *          forall (Card c') this'.In(c') iff (In(c') or (c' == c))
       *
       *              and
       *
       *          // The size of this is increased by 1.
       *          (cl'.Length() == cl.Length() + 1)
       *      )
       *      else (
       *          // An AddInputException is thrown if any of the preconditions
       *          // is violated.  The value of the exception flag is set for
       *          // each violated precondition via the ValidataAddInput
       *          // function.
       *          (throw == aie) and (aie == ValidateAddInput(c));
       *                                                                   </pre>
       */
      public void add(Card c) throws AddInputErrors {
      
          /*
           * Clear out the input error data.
           */
          aie.Clear();
      
          /*
           * Throw a precond violation if a card of the given id is already in
           * this.data.
           */
          if (cl.find(c.getId()) != null) {
              aie.SetAlreadyThereError();
              throw aie;
          }
      
          /*
           * Throw a precond violation if the validly check fails for one or more
           * of the card fields.  The exception value is a tuple of values for
           * each of the invalid fields.
           */
          if (validateAddInput(c).anyErrors().booleanValue()) {
              throw aie;
          }
      
          /*
           * If preconditions are met, add the given card to this.data using the
           * inherited CardList.add function.
           */
          cl.add(c);
      
      }
      

  5. Precondition enforcement -- "by contract" style and "defensive programming" style.
    1. At the specification level, failure of an operation precondition renders the operation simply "undefined".
      1. For an abstract specification, this is a perfectly reasonable definition of precondition failure.
      2. However, at the design and implementation level, precondition failure must be dealt with more concretely.
      3. There are two basic approaches such concretization.
    2. Approach 1: A precondition is a guaranteed assumption that will always be true before a function to which it is attached is executed.
      1. This approach is can be called the "programming by contract" approach.
      2. In this approach, the code of the function does not enforce its own precondition, but rather the precondition must be enforced by all callers of the function.
      3. Such enforcement can be formally verified or implemented with runtime checks.
    3. Approach 2: A precondition must be checked by the function to which it is attached.
      1. This approach can be called the "defensive programming" approach.
      2. In this approach, the code of the function includes logic to enforce its precondition.
      3. The enforcement can:
        1. Assert unconditional failure on any precondition violation.
        2. Return an appropriate "null" value as the function return value or in an output parameter.
        3. Output an appropriate error report to stderr or the user view screen.
        4. Throw an appropriate exception (see below for further discussion).
    4. In Model/View communication, it is most appropriate to use the exception handling approach, as illustrated in the example above.
    5. We will discuss the issue of when and how to use exception handling in a design in upcoming lectures.

  6. Some details of the MV design for Inferno tools
    1. Here are designs for high level classes InfernoTool and InfernoToolUI:
      
      package inferno;
      
      import mvp.*;
      
      /****
       *
       * Class InfernoTool is a subclass of Model that encapsulates data and
       * functions common to all Inferno tools.
       *
       */
      
      public class InfernoTool extends Model {
      
          /**
           * Construct this with the given instance of the Infero parent tool.
           */
          public InfernoTool(mvp.View v, Inferno inferno) {
              super(v);
              this.inferno = inferno;
          }
      
          /**
           * Launch this with the given artifact.  Viewwise, launching entails adding
           * tool-specific menu(s) and showing the initial tool-specific display
           * window(s), if any.
           */
          public void launch(Artifact a) {}
      
          /**
           * Close this.  Viewwise, closing entails removing tool-specific menus and
           * closing all open tool-specific windows.
           */
          public void close(Artifact a) {}
      
          /**
           * Perform processing required when this tool gains focus, typically by
           * means of the user having clicked on its main display window.  Per the
           * specs, gaining focus means showing all tool palettes and canvases, and
           * specializing the command menus for the focused-upon tool.  Details of
           * actions in the view are handled by the companion InfernoToolUI.gainFocus
           * and InfernoToolUI.looseFocus, q.q.v.
           */
          public void gainFocus() {}
      
          /**
           * Complement of gainFocus.
           */
          public void loseFocus() {}
      
      
          /**
           * Return this' Inferno.
           */
          public Inferno getInferno() {
              return inferno;
          }
      
          /**
           * Return this' File model.
           */
          public File getFile() {
              return file;
          }
      
          /**
           * Return this' Edit model.
           */
          public Edit getEdit() {
              return edit;
          }
      
      
          /** Parent model for all tools */
          protected Inferno inferno;
      
          /** File model that all inferno tools must have.  In each tool instance,
           *  this.file refers to a specialized File.  E.g., in the DesignTool,
           *  DesignTool.file refers to a design.File object. */
          protected inferno.File file;
      
          /** Edit model that all inferno tools must have.  In each tool instance,
           *  this.edit refers to a specialized Edit.  E.g., in the DesignTool,
           *  DesignTool.edit refers to a design.Edit object. */
          protected inferno.Edit edit;
      
      }
      
      
      package inferno;
      
      import javax.swing.*;
      import mvp.*;
      
      /****
       *
       * Class InfernoToolUI is a subclass of View that encapsulates data and
       * functions common to all Inferno tool UIs.  It is an abstract class.
       *
       */
      
      public class InfernoToolUI extends mvp.View {
      
          /**
           * Construct this given companion InfernoTool model
           */
          public InfernoToolUI(Screen s, InfernoTool it) {
              super(s, it);
          }
      
          /**
           * Add the given pulldown menu to standard locale on the inferno menubar.
           */
          public void addMenu(JMenu jm) {}
      
          /**
           * Remove given pulldown menu from the inferno menubar.  Do nothing if the
           * given menu is not in the menubar.
           */
          public void removeMenu(JMenu jm) {}
      
          /**
           * Perform view processing required when this tool gains focus.
           */
          public void gainFocus() {}
      
          /**
           * Complement of gainFocus.
           */
          public void looseFocus() {}
      
      
          /** Menubar to which tool-specific menus are added. */
          protected JMenuBar mb;
      }
      
    2. Each Inferno tool should be designed to operate both standalone as well as integrated.
      1. In former case, the tool has its own File, Edit, and tool-specific menus, plus its own Main function; a standalone inherits directly from Model, not InfernoTool.
      2. In latter case, the tool inherits the menubar (from InfernoToolUI), adds its own tool-specific menu(s), and implements the launch function in place of Main; an integrated tool inherits from InfernoTool.




index | lectures | handouts | examples | doc