© Edward Sciore 2019
Edward ScioreJava Program Designhttps://doi.org/10.1007/978-1-4842-4143-1_11

11. Model, View, and Controller

Edward Sciore1 
(1)
Newton, MA, USA
 

This last chapter of the book takes up the issue of how to separate a program’s computation-related responsibilities from its presentation-related ones. You may recall that Chapter 1 first addressed this issue when it created version 2 of the banking demo. Version 2 contained the new classes BankClient, which embodied the presentational responsibilities, and Bank, which embodied the computational ones.

It turns out that Chapter 1 didn’t go far enough. This chapter argues that programs should also isolate the computation classes from the presentation classes, and for this you need classes to mediate between them. The computation, presentation, and mediator classes are called model, view, and controller. This chapter introduces the MVC pattern, which is the preferred way to organize these classes in a program. The chapter also discusses the advantages of using the MVC pattern and gives several examples.

The MVC Design Rule

A program has two general areas of interest. The first is how it interacts with the user, requesting inputs and presenting outputs. The second is how it computes the outputs from the inputs. Experience has shown that well-designed programs keep the code for these two areas separate from each other. The input/output portion is called the view. The computation portion is called the model.

This idea is expressed by the design rule “Separate the model from the view.” In an object-oriented environment, this rule implies that there should be classes devoted to computing the results, and classes devoted to presenting the results and requesting input. Moreover, there should not be a class that does both.

The view and model have different concerns. The view needs to be a visually attractive interface that is easy to learn and use. The model should be functional and efficient. As these concerns have nothing in common, it follows that the view and model should be designed independently of each other. Consequently, the model should know nothing about how the view displays its results and the view should know nothing about the meaning of the values it displays.

In order to maintain this isolation, a program must have code that connects the model with the view. This code is called the controller . The controller understands the overall functionality of the program and mediates between the view and the model. It knows which methods of the model correspond to each view request and what values from the model to display in the view.

These ideas are codified in the following Model-View-Controller design rule, otherwise known as the MVC rule. This rule, which is a special case of the Single Responsibility rule, asserts that a class should not combine model, view, or controller responsibilities.

The Model-View-Controller Rule

A program should be designed so that its model, view, and controller code belong to distinct classes.

For example, consider the version 18 banking demo. The Bank class is part of the model, as are the classes and interfaces it depends on. As these classes contain no view or controller code, they satisfy the MVC rule. On the other hand, the BankClient and InputCommands classes do not satisfy the MVC rule because each of them combines view and controller code. This situation is illustrated in Listings 11-1 and 11-2.

Listing 11-1 shows the portion of InputCommands that defines the constant DEPOSIT. The lambda expression contains view code (the calls to the scanner and System.out.print) as well as controller code (the call to bank.deposit).
public enum InputCommands implements InputCommand {
   ...
   DEPOSIT("deposit", (sc, bank, current)->{
      System.out.print("Enter deposit amt: ");
      int amt = sc.nextInt();
      bank.deposit(current, amt);
      return current;
   }),
   ...
}
Listing 11-1

A Fragment of the Version 18 InputCommands Enum

Listing 11-2 shows the beginning of the BankClient class and two of its methods. To its credit, the class contains mostly view code. The only issue is that two of its variables, bank and current, refer to the model. Although the class does not use these variables in any significant way, they do not belong in the view.
public class BankClient {
   private Scanner scanner;
   private boolean done = false;
   private Bank bank;
   private int current = 0;
   ...
   private void processCommand(int cnum) {
      InputCommand cmd = commands[cnum];
      current = cmd.execute(scanner, bank, current);
      if (current < 0)
         done = true;
   }
}
Listing 11-2

A Fragment of the Version 18 BankClient Class

The version 19 banking demo rectifies the problems with these classes by moving their controller code to the new class InputController. Listing 11-3 gives some of its code.
public class InputController {
   private Bank bank;
   private int current = 0;
   public InputController(Bank bank) {
      this.bank = bank;
   }
   public String newCmd(int type, boolean isforeign) {
      int acctnum = bank.newAccount(type, isforeign);
      current = acctnum;
      return "Your new account number is " + acctnum;
   }
   public String selectCmd(int acctnum) {
      current = acctnum;
      int balance = bank.getBalance(current);
      return "Your balance is " + balance;
   }
   public String depositCmd(int amt) {
      bank.deposit(current, amt);
      return "Amount deposited";
   }
   ...
}
Listing 11-3

The Version 19 InputController Class

The controller has a method for each input command. The view will call these methods, supplying the appropriate argument values. The controller is responsible for performing the necessary actions on the model. It is also responsible for constructing a string describing the result and returning it to the view. The controller also manages the variable current, which holds the current account.

Listings 11-4 and 11-5 give the version 19 code for BankClient and InputCommands. These classes constitute the view. They use the scanner for input and System.out for output. BankClient passes the controller to InputCommands, and InputCommands delegates all model-related activity to the controller.
public class BankClient {
   private Scanner scanner;
   private InputController controller;
   private InputCommand[] commands = InputCommands.values();
   public BankClient(Scanner scanner, InputController cont) {
      this.scanner = scanner;
      this.controller = cont;
   }
   public void run() {
      String usermessage = constructMessage();
      String response = "";
      while (!response.equals("Goodbye!")) {
         System.out.print(usermessage);
         int cnum = scanner.nextInt();
         InputCommand cmd = commands[cnum];
         response = cmd.execute(scanner, controller);
         System.out.println(response);
      }
   }
   ...
}
Listing 11-4

The Version 19 BankClient Class

public enum InputCommands implements InputCommand {
   QUIT("quit", (sc, controller)->{
      sc.close();
      return "Goodbye!";
   }),
   NEW("new", (sc, controller)->{
      printMessage();
      int type = sc.nextInt();
      boolean isforeign = requestForeign(sc);
      return controller.newCmd(type, isforeign);
   }),
   SELECT("select", (sc, controller)->{
      System.out.print("Enter acct#: ");
      int num = sc.nextInt();
      return controller.selectCmd(num);
   }),
   DEPOSIT("deposit", (sc, controller)->{
      System.out.print("Enter deposit amt: ");
      int amt = sc.nextInt();
      return controller.depositCmd(amt);
   }),
   ...
}
Listing 11-5

The Version 19 InputCommands Enum

The main class BankProgram must be revised to accommodate the view and controller classes. Its code appears in Listing 11-6. This class is best understood as belonging to neither the model, controller, nor view. Instead, its job is to create and configure the model, controller, and view classes. The bold code in Listing 11-6 highlights the order in which these classes are created. BankProgram first creates the model object (which is of type Bank). It then creates the controller, passing it a reference to the model. Finally, it creates the view, passing it a reference to the controller.
public class BankProgram {
   public static void main(String[] args) {
      SavedBankInfo info = new SavedBankInfo("bank19.info");
      Map<Integer,BankAccount> accounts = info.getAccounts();
      int nextacct = info.nextAcctNum();
      Bank bank = new Bank(accounts, nextacct);
      ...
      InputController controller = new InputController(bank);
      Scanner scanner = new Scanner(System.in);
      BankClient client = new BankClient(scanner, controller);
      client.run();
      info.saveMap(accounts, bank.nextAcctNum());
   }
}
Listing 11-6

The Version 19 BankProgram Class

Figure 11-1 shows a class diagram depicting the relationship between these model, view, and controller classes. Note that even though the view and the model consist of multiple classes, there is one class that acts as the “primary” class for the purposes of configuration. This situation is generally true for all MVC designs.
../images/470600_1_En_11_Chapter/470600_1_En_11_Fig1_HTML.jpg
Figure 11-1

A class diagram for the MVC-based banking demo

Theoretically, the distinction between a model and a view is clear cut: something belongs in the model if its functionality is independent of how it is presented, and something belongs in the view if it is irrelevant to the model. In practice, however, making these distinctions can require careful analysis. The banking demo provides some examples.

One example is the notion of the current account. I argued earlier that it should not be part of the view. But should it be part of the controller or the model? The answer depends on whether the current account is relevant to only the particular view or is inherent to the model. The key for me was to realize that each session of the bank client could have a different current account, and I didn’t want the model to be responsible for managing session-specific data. This indicated to me that the current account belongs in the controller and not the model.

For another example, consider the numbers that BankClient assigns to input choices. Input commands are assigned a number from 0–7, account types a number from 1–3, and the ownership specification is 1 for “domestic” and 2 for “foreign.” The view is responsible for assigning numbers for the commands and the domestic/foreign selections, but the model decides the account type numbers. Why?

The criterion is whether the meaning of an input value is relevant to the model. If the model doesn’t care then the view should be responsible for determining the meaning of the values. This is the case for the command and ownership numbers, since the model never sees them. The account types, on the other hand, are manipulated by the model and thus must be decided by the model.

Multiple Views for a Model

One advantage of separating model from view is that you can create different programs that use the same model. For example, the banking model could be used by a program for customers (e.g., online banking), another program for bank employees, and another for bank executives. To write each program, it suffices to write the view plus a controller that hooks the view into the existing model.

The separation between model and view also makes it easier to modify a view so that it has a different user interface. For example, BankClient could be modified to use command names instead of numbers, or to support voice commands, or to have a GUI-based interface. This last option will be considered later in the chapter.

The version 18 banking demo has four programs that use the banking model: BankProgram, FBIClient, IteratorStatProgram, and StreamStatProgram. The last three programs do not satisfy the MVC rule. They are all quite simple—they have no input, and their output just prints the result of some test queries. Does it make sense to rewrite their code to use MVC? The simplest of the programs is StreamStatProgram, which has the associated class StreamAccountStats. These classes were initially discussed in Chapter 6. Let’s rewrite them and see what happens.

Listing 11-7 gives the first two methods of StreamAccountStats. It is primarily model code; the only issue is that the methods call System.out.println.
public class StreamAccountStats {
   private Bank bank;
   public StreamAccountStats(Bank b) {
      bank = b;
   }
   public void printAccounts6(Predicate<BankAccount> pred) {
      Stream<BankAccount> s = bank.stream();
      s = s.filter(pred);
      s.forEach(ba->System.out.println(ba));
   }
   public void printAccounts7(Predicate<BankAccount> pred) {
      bank.stream()
          .filter(pred)
          .forEach(ba->System.out.println(ba));
   }
   ...
}
Listing 11-7

The Original StreamAccountStats Class

Listing 11-8 shows the version 19 revision, which is called StreamStatModel . The two printAccounts methods have changed. The prefix “print” in their name has been renamed “get” to reflect that their return type is now String instead of void. In addition, their use of the forEach method must be modified to use reduce, so that it can create a single string from the individual calls to ba.toString.
public class StreamStatModel {
   private Bank bank;
   public StreamStatModel(Bank b) {
      bank = b;
   }
   public String getAccounts6(Predicate<BankAccount> pred) {
      Stream<BankAccount> s = bank.stream();
      s = s.filter(pred);
      Stream<String> t = s.map(ba->ba.toString());
      return t.reduce("", (s1,s2)->s1 + s2 + " ");
   }
   public String getAccounts7(Predicate<BankAccount> pred) {
      return bank.stream()
                .filter(pred)
                .map(ba->ba.toString())
                .reduce("", (s1,s2)->s1 + s2 + " ");
   }
   ...
}
Listing 11-8

The Revised StreamStatModel Class

The original code for StreamStatProgram appears in Listing 11-9. It contains both view and controller code. The controller code consists of calling the model methods. The view code consists of printing their results.
public class StreamStatProgram {
   public static void main(String[] args) {
      ... 
      StreamAccountStats stats = ... 
      Predicate<BankAccount> pred = ba -> ba.fee() == 0;
      ...
      System.out.println("Here are the domestic accounts.");
      stats.printAccounts6(pred);
      System.out.println("Here are the domestic accounts
                          again.");
      stats.printAccounts7(pred);
   }
}
Listing 11-9

The Original StreamStatProgram Class

Version 19 of the banking demo contains the view class StreamStatView. Its code, shown in Listing 11-10, calls controller methods instead of model methods. Note that the view is not aware of the predicate because the predicate refers to the model.
public class StreamStatView {
   StreamStatController c;
   public StreamStatView(StreamStatController c) {
      this.c = c;
   }
   public void run() {
      ...
      System.out.println("Here are the domestic accounts.");
      System.out.println(c.getAccounts6());
      System.out.println("Here are the domestic accounts
                          again.");
      System.out.println(c.getAccounts7());
   }
}
Listing 11-10

The Version 19 StreamStatView Class

The StreamStatController class appears in Listing 11-11. It implements each of the three view methods in terms of the model. It also creates the predicate.
public class StreamStatController {
   private StreamStatModel model;
   Predicate<BankAccount> pred = ba -> ba.fee() == 0;
   public StreamStatController (StreamStatModel model) {
      this.model = model;
   }
   public String getAccounts6() {
      return model.getAccounts6(pred);
   }
   public String getAccounts7() {
      return model.getAccounts7(pred);
   }
   ...
}
Listing 11-11

The Version 19 StreamStatController Class

Finally, Listing 11-12 gives the code for the version 19 StreamStatProgram class. The class configures the model, view, and controller, and then calls the view’s run method.
public class StreamStatProgram {
   public static void main(String[] args) {
      SavedBankInfo info = new SavedBankInfo("bank19.info");
      Map<Integer,BankAccount> accounts = info.getAccounts();
      int nextacct = info.nextAcctNum();
      Bank bank = new Bank(accounts, nextacct);
      StreamStatModel m = new StreamStatModel(bank);
      StreamStatController c = new StreamStatController(m);
      StreamStatView v = new StreamStatView(c);
      v.run();
   }
}
Listing 11-12

The Version 19 StreamStatProgram Class

Compare the MVC versions of the StreamStatProgram classes to their original code. You may be surprised at how much cleaner and better organized the MVC version is. Although it contains more code than the original version, each individual class is short and easily modified. The moral is that even for small programs, an MVC design is worth considering.

MVC in Excel

Excel is an example of a commercial program that follows the MVC design rule. The model consists of the cells of the spreadsheet. Each of the spreadsheet’s charts is a view of the model. Figure 11-2 shows a screenshot depicting a chart and its underlying cells.
../images/470600_1_En_11_Chapter/470600_1_En_11_Fig2_HTML.jpg
Figure 11-2

A spreadsheet’s model and view

Excel maintains a strict separation between the model and the views. The cells don’t know about the charts. Each chart is an “object” that sits on top of the cells.

The creation of an Excel chart has two aspects. The first aspect is what the chart looks like. Excel has tools to specify different chart types, colors, labels, and so on. These tools correspond to view methods; they let you make the chart look attractive, independent of whatever data it represents.

The second aspect is what data the chart displays. Excel has a tool called “Select Data” for specifying the chart’s underlying cells. This tool corresponds to the controller. Figure 11-3 gives a screenshot of the controller window for Figure 11-2. The “Name” text field specifies the cell that contains the chart title; “Y values” specifies the cells containing the population values; and “Horizontal (Category) axis labels” specifies the cells containing the years.
../images/470600_1_En_11_Chapter/470600_1_En_11_Fig3_HTML.jpg
Figure 11-3

A chart’s controller

Separating the model and view provides a lot of flexibility. A range of cells can be the model for many different charts, and a chart can be the a view for many different cell ranges. The controller links them together.

JavaFX Views and Controllers

Consider again the AccountCreationWindow class that appeared in Figure 10-3. This class displays JavaFX controls that let a user choose the type of bank account to create. However, the class is not connected to the bank model. Clicking the CREATE ACCT button has no effect except to change the text of the title label. In other words, this program is purely view code, which is entirely appropriate because JavaFX is for creating views.

How to create the controller that would connect the view to the bank model? Before attacking this question, let’s begin with a simple JavaFX example that illustrates the issues. The program Count1 displays a window containing two buttons and a label, as shown in Figure 11-4. The label displays the value of the variable count. The two buttons increment and decrement the count.
../images/470600_1_En_11_Chapter/470600_1_En_11_Fig4_HTML.jpg
Figure 11-4

The initial screen of the Count1 program

Listing 11-13 gives the code for Count1. This code does not satisfy the MVC design rule. The model consists of the variable count. The updateBy method updates the count (an operation on the model), but also changes the text of the label (an operation on the view).
public class Count1 extends Application {
   private static int count = 0;
   private static Label lbl = new Label("Count is 0");
   public void start(Stage stage) {
      Button inc = new Button("Increment");
      Button dec = new Button("Decrement");
      VBox p = new VBox(8);
      p.setAlignment(Pos.CENTER);
      p.setPadding(new Insets(10));
      p.getChildren().addAll(lbl, inc, dec);
      inc.setOnAction(e -> updateBy(1));
      dec.setOnAction(e -> updateBy(-1));
      stage.setScene(new Scene(p));
      stage.show();
   }
   private static void updateBy(int n) {
      count += n; // model code
      lbl.setText("Count is " + count); // view code
   }
   public static void main(String[] args) {
      Application.launch(args);
   }
}
Listing 11-13

The Count1 Class

Version 2 of the counting demo separates the code into model, view, and controller classes. The main class Count2 is responsible for creating these classes and connecting them to each other. Its code appears in Listing 11-14.
public class Count2 extends Application {
   public void start(Stage stage) {
      CountModel model = new CountModel();
      CountController controller = new CountController(model);
      CountView view = new CountView(controller);
      Scene scene = new Scene(view.getRoot());
      stage.setScene(scene);
      stage.show();
   }
   public static void main(String[] args) {
      Application.launch(args);
   }
}
Listing 11-14

The Count2 Class

The structure of this class is similar to the structure of the Version 19 BankProgram and StreamStatProgram classes. First the model is created. Then the controller is created, passing in the model. Then the view is created, passing in the controller.

The class calls the view method getRoot, which returns the root of its node hierarchy. This root is passed into the Scene constructor, and then to the stage (via its setScene method).

The model consists of a single class named CountModel, whose code appears in Listing 11-15. The class has a variable count that holds the current count, and methods getCount and updateBy that get and update the count.
public class CountModel {
   private int count = 0;
   public void updateBy(int n) {
      count += n;
   }
   public int getCount() {
      return count;
   }
}
Listing 11-15

The CountModel Class

The view consists of a single class, called CountView. Its code appears in Listing 11-16.
class CountView {
   private Pane root;
   public CountView(CountController cont) {
      root = createNodeHierarchy(cont);
   }
   public Pane getRoot() {
      return root;
   }
   private Pane createNodeHierarchy(CountController cont) {
      Button inc = new Button("Increment");
      Button dec = new Button("Decrement");
      Label  lbl = new Label("Count is 0");
      VBox p = new VBox(8);
      p.setAlignment(Pos.CENTER);
      p.setPadding(new Insets(10));
      p.getChildren().addAll(lbl, inc, dec);
      inc.setOnAction(e -> {
         String s = cont.incrementButtonPressed();
         lbl.setText(s);
      });
      dec.setOnAction(e ->
              lbl.setText(cont.decrementButtonPressed()));
      return p;
   }
}
Listing 11-16

The CountView Class

Most of the view code is devoted to the mundane task of creating the node hierarchy. More interesting is how the view uses its two button handlers to interact with the controller. The increment button handler calls the controller’s incrementButtonPressed method. This method does what it needs to do (which in this case is to tell the model to increment the count), and then returns a string for the view to display in its label. Similarly, the decrement button handler calls the controller’s decrementButtonPressed method, and displays its return value.

Note that the two handlers have the same structure. I wrote their code differently from each other simply to illustrate different coding styles.

The controller class is named CountController. Its code appears in Listing 11-17. The controller is responsible for translating events on the view into actions on the model, and translating return values from the model to displayable strings on the view.
class CountController {
   private CountModel model;
   public CountController(CountModel model) {
      this.model = model;
   }
   public String incrementButtonPressed() {
      model.updateBy(1);
      return "Count is " + model.getCount();
   }
   public String decrementButtonPressed() {
      model.updateBy(-1);
      return "Count is " + model.getCount();
   }
}
Listing 11-17

The CountController Class

Note how the controller mediates between the model and the view, which enables the view to be unaware of the model. The view knows “hey, my button got pushed,” but it doesn’t know what to do about it. So the view delegates that job to the controller. Moreover, the view agrees to display whatever value the controller returns.

In 2003, an Apple engineer sang a wonderful song about MVC at the Apple developer’s conference, and his performance was recorded for posterity. You can find the video by searching YouTube for “MVC song.” As you watch the performance, you’ll probably get drawn in by the catchy melody. But pay special attention to the lyrics, which succinctly convey the true beauty of MVC.

Extending the MVC Architecture

This chapter has thus far presented three examples of MVC programs: BankProgram (Listing 11-6), StreamStatProgram (Listing 11-12), and Count2 (Listing 11-14). Each of these programs has a similar architecture, in which the view talks to the controller who talks to the model. This architecture is simple and straightforward.

Although this architecture handles single-view programs fine, it fails miserably with programs that have multiple views. For an example, consider version 3 of the counting demo, which adds a second view to the version 2 demo. Figure 11-5 shows a screenshot of the program after a few button clicks.
../images/470600_1_En_11_Chapter/470600_1_En_11_Fig5_HTML.jpg
Figure 11-5

A screenshot of the Count3 program

The two views each have a pane within the window. The second view is a “watcher” view. It keeps track of how many times the count was changed, and displays whether the count is even or odd. But how can the watcher view know when the model has changed? The answer is to use the observer pattern! The model needs to broadcast changes made by the counting view so that the watcher view can observe them. The model therefore needs to be modified to be an observable.

Define an observer interface, called CountObserver. This interface will have one observer method, update, which pushes the new count to its observers. Its code appears in Listing 11-18.
public interface CountObserver {
   public void update(int count);
}
Listing 11-18

The CountObserver Interface

The class CountModel needs to manage an observer list. Its updateBy method will broadcast the new count to the observers on the list. Listing 11-19 shows the resulting changes to the class.
public class CountModel {
   private int count = 0;
   private Collection<CountObserver> observers
                            = new ArrayList<>();
   public void addObserver(CountObserver obs) {
      observers.add(obs);
   }
   private void notifyObservers(int count) {
      for (CountObserver obs : observers)
         obs.update(count);
   }
   public void updateBy(int n) {
      count += n;
      notifyObservers(count);
   }
   public int getCount() {
      return count;
   }
}
Listing 11-19

The CountModel Class

The watcher’s controller will be the model observer. When the controller receives a notification from the model, it will determine the changes that need to be made to its view and pass those changes to the view. Unfortunately, this behavior is not currently possible because the watcher controller does not know who its view is! To address this problem, the watcher view and its controller need to be modified: the controller needs a reference to the view, and the view needs to have a method that the controller can call.

By giving the controller a reference to its view, the watcher view and its controller will have references to each other. The view gets its reference via constructor injection. But the controller cannot, because the view has not yet been created when the controller is created. The solution is for the controller to get its reference to the view via method injection. It defines a method setView. When the view is created, it can call the controller’s setView method, passing the controller a reference to itself.

The watcher view defines the method updateDisplay for the controller to call. The method has three arguments, corresponding to three values that the controller will want to pass to the view: the new message for its label, and the desired values of the two checkboxes.

Listing 11-20 gives the code for the controller. Note that the controller is responsible for keeping track of the number of times the model changes, because I decided that this value is not relevant to the model. If you feel otherwise, you should change the model so that it keeps that information.
public class WatcherController
                    implements CountObserver {
   private WatcherView view;
   private int howmany = 0;
   public WatcherController(CountModel model) {
      model.addObserver(this);
   }
   // called by the view
   public void setView(WatcherView view) {
      this.view = view;
   }
   // called by the model
   public void update(int count) {
      howmany++;
      boolean isEven = (count%2 == 0);
      boolean isOdd = !isEven;
      String msg = "The count has changed "
                  + howmany + " times";
      view.updateDisplay(msg, isEven, isOdd);
   }
}
Listing 11-20

The WatcherController Class

Listing 11-21 gives the code for the watcher view. Its constructor calls the controller’s setView method, thereby establishing the two-way connection between the view and controller. The updateDisplay method sets the value of the view’s three controls. Note that the view has no idea what these values mean.
class WatcherView {
   private Label lbl
            = new Label("The count has not yet changed");
   private CheckBox iseven
            = new CheckBox("Value is now even");
   private CheckBox isodd = new CheckBox("Value is now odd");
   private Pane root;
   public WatcherView(WatcherController controller) {
      root = createNodeHierarchy();
      controller.setView(this);
   }
   public Pane root() {
      return root;
   }
   public void updateDisplay(String s, boolean even,
                             boolean odd) {
      lbl.setText(s);
      iseven.setSelected(even);
      isodd.setSelected(odd);
   }
   private Pane createNodeHierarchy() {
      iseven.setSelected(true);
      isodd.setSelected(false);
      VBox p = new VBox(8);
      p.setAlignment(Pos.CENTER);
      p.setPadding(new Insets(10));
      p.getChildren().addAll(lbl, iseven, isodd);
      return p;
   }
}
Listing 11-21

The WatcherView Class

The main program , Count3, configures the two views into a single window. In order to deal with the multiple views, the code places the node hierarchies of the two views into a single HBox pane. Listing 11-22 gives the code. The statements that combine the views are in bold.
public class Count3 extends Application {
   public void start(Stage stage) {
      CountModel model = new CountModel();
      // the first view
      CountController ccontroller
                        = new CountController(model);
      CountView cview   = new CountView(ccontroller);
      // the second view
      WatcherController wcontroller
                        = new WatcherController(model);
      WatcherView wview = new WatcherView(wcontroller);
      // Display the views in a single two-pane window.
      HBox p = new HBox();
      BorderStroke bs = new BorderStroke(Color.BLACK,
                              BorderStrokeStyle.SOLID,
                              null, null, new Insets(10));
      Border b = new Border(bs);
      Pane root1 = cview.root(); Pane root2 = wview.root();
      root1.setBorder(b); root2.setBorder(b);
      p.getChildren().addAll(root1, root2);
      stage.setScene(new Scene(p));
      stage.show();
   }
   public static void main(String[] args) {
      Application.launch(args);
   }
}
Listing 11-22

The Count3 Class

The MVC Pattern

Although WatcherController needs to be a model observer, CountController does not. Since it is the only view that can change the model, it knows exactly when and how the model will change. At least for now, that is. But what if the program happens to add another view that can change the model? Then the value displayed by CountView can wind up being incorrect. This kind of bug can be very difficult to detect. If we want CountView to always display the current count, regardless of what other views exist, then CountController also needs to be a model observer.

To be a model observer, CountController must implement the update method. Its code for update will construct a message describing the new count and send it to the view. Consequently, the button handler methods incrementButtonPressed and decrementButtonPressed should now be void, as they are no longer responsible for constructing the message. In addition, the controller needs a reference to the view. It therefore implements the method setView using the same technique as WatcherController. Listing 11-23 gives the revised code.
class CountController implements CountObserver {
   private CountModel model;
   private CountView view;
   public CountController(CountModel model) {
      this.model = model;
      model.addObserver(this);
   }
   // Methods called by the view
   public void setView(CountView view) {
      this.view = view;
   }
   public void incrementButtonPressed() {
      model.updateBy(1);
   }
   public void decrementButtonPressed() {
      model.updateBy(-1);
   }
   // Method called by the model
   public void update(int count) {
      view.setLabel("Count is " + count);
   }
}
Listing 11-23

The Revised CountController Class

The class CountView needs to be modified to correspond to the changes in its controller. The view constructor calls the controller's method setView, and the view implements the method setLabel for the controller to call. The code appears in Listing 11-24.
class CountView {
   private Label lbl = new Label("Count is 0");
   private Pane root;
   public CountView(CountController controller) {
      root = createNodeHierarchy(controller);
      controller.setView(this);
   }
   public Pane root() {
      return root;
   }
   public void setLabel(String s) {
      lbl.setText(s);
   }
   private Pane createNodeHierarchy(CountController cont) {
      Button inc = new Button("Increment");
      Button dec = new Button("Decrement");
      ... // create the node hierarchy, having root p
      inc.setOnAction(e -> cont.incrementButtonPressed());
      dec.setOnAction(e -> cont.decrementButtonPressed());
      return p;
   }
}
Listing 11-24

The revised CountView Class

To understand the effect of these changes, consider what now happens to the count view and count controller when the Increment button is clicked.
  • The view calls the controller’s incrementButtonPressed method.

  • That method calls the model’s updateBy method.

  • That method updates the count and calls notifyObservers, which calls the controller’s update method.

  • That method formats the string for the view to display, and calls the view’s setLabel method.

  • That method modifies the text of its label to be the current count.

This sequence of method calls has the same effect as in the Count2 controller. The difference is that the controller calls a pair of void methods instead of a single method that returns a value. This added complexity is necessary to guarantee that the count will be updated in all views, no matter which view does the updating.

The insight that controllers should be observers is the basis for the MVC Design Pattern. This pattern asserts that an application should be structured similarly to Count3. In particular: the model should be an observable and all controllers should be model observers; the controllers talk directly to the model; and each view/controller pair can talk directly to each other. This pattern is expressed by the class diagram of Figure 11-6.
../images/470600_1_En_11_Chapter/470600_1_En_11_Fig6_HTML.jpg
Figure 11-6

The MVC pattern

Communication using the MVC pattern works as follows:
  • An action on a view (such as a button click) gets communicated to its controller.

  • The controller translates that action to a method call on the model.

  • If that method call is a request for data, then the model returns the requested data directly to the controller, which forwards it to its view.

  • If that method call causes the model to change, the model notifies its observers.

  • Each controller, being a model observer, decides if the update is relevant to its view. If so, it calls the appropriate view methods.

Many GUI applications rely on the MVC pattern to synchronize their views. To illustrate I will use two examples from the MacOS interface on my computer, but similar examples can be found in Windows or Linux.

For the first example, consider the file manager. Open two file manager windows and have them display the contents of the same folder. Go to one of the windows and rename a file. You will see the file get renamed automatically in the other window. Now open an application, create a file, and save it to that folder. You will see that file appear automatically in both file manager windows.

For the second example, consider the correspondence between a text document and its pdf version. Open a document in a text editor. Save the document as a pdf file and open the pdf version in a pdf viewer. Change the text document, and resave it as pdf. The version displayed in your pdf viewer will automatically change.

In both examples, the computer’s file system serves as the model. File manager windows and pdf viewers are views of the file system. Each view has a controller that observes the file system. When the file system changes, it notifies its observers. When a file manager controller is notified, it determines if the changes affect the files being displayed, and if so, updates its view. When a pdf controller is notified, it determines if the changes affect the file that it is displaying, and if so, tells the view to reload the new contents of the file.

MVC and the Banking Demo

The time has finally come to develop a JavaFX-based interface to the banking demo. The interface will have one window that contains three views: a view for creating a new account; a view for managing a selected account; and a view that displays all account information. Figure 11-7 displays a screenshot of the three views.
../images/470600_1_En_11_Chapter/470600_1_En_11_Fig7_HTML.jpg
Figure 11-7

The JavaFX interface to the banking demo

You have already encountered the view titled “Create a New Bank Account.” The user selects the desired account type, specifies whether the account is domestic or foreign-owned, and clicks the button. A new account is created.

In the view titled “Access an Existing Account,” the user specifies the current account by entering the account number in the text field of the top pane and clicking the “Select Account” button. The account balance then appears in the text field below it and the domestic/foreign choice box in the bottom pane is set to the corresponding value of the account. After selecting an account, the user can make a deposit to it, request a loan authorization, or change its ownership status. Throughout, the account balance is always kept up to date. When a deposit occurs or interest is accrued, the balance is updated.

The view titled “Manage All Accounts” displays all the accounts in a text area using the output of their toString method. The view also has a button for executing the bank’s addInterest method. The account display is automatically kept up to date. Whenever the state of a bank account changes, the listing is refreshed.

The program is structured using the MVC pattern. Its main class is called FxBankProgram, and it has three view classes and three controller classes. The Bank class is the model. Recall that Bank was revised in Chapter 10 to be an observable (see Listing 10-18), and requires no further modification. The following subsections will examine FxBankProgram and each view/controller pair.

The Class FxBankProgram

This class configures the views into a single JavaFX window. Listing 11-25 gives the code. The JavaFX Application class has the two methods init and stop in addition to start. The launch method first calls init, then start, and then stop. The purpose of init is to initialize values needed by the application. Here, init creates the node hierarchies for each of the three views and saves their roots in the variables root1, root2, and root3. It also creates the model and the three controllers. The stop method saves the status of the bank.
public class FxBankProgram extends Application {
   private SavedBankInfo info =
           new SavedBankInfo("bank19.info");
   private Map<Integer,BankAccount> accounts =
           info.getAccounts();
   Bank bank = new Bank(accounts, info.nextAcctNum());
   private Pane root1, root2, root3;
   public void start(Stage stage) {
      VBox left = new VBox();
      left.getChildren().addAll(root1, root2);
      HBox all = new HBox(left, root3);
      stage.setScene(new Scene(all));
      stage.show();
   }
   public void init() {
      Auditor aud = new Auditor(bank);
      bank.addObserver(BankEvent.DEPOSIT,
            (event,ba,amt) -> {
               if (amt > 10000000)
                  bank.makeSuspicious(ba.getAcctNum());
            });
      CreationController c1 = new CreationController(bank);
      AllController c2 = new AllController(bank);
      InfoController c3 = new InfoController(bank);
      CreationView v1 = new CreationView(c1);
      AllView v2 = new AllView(c2);
      InfoView v3 = new InfoView(c3);
      BorderStroke bs = new BorderStroke(Color.BLACK,
                               BorderStrokeStyle.SOLID,
                               null, null, new Insets(10));
      Border b = new Border(bs);
      root1 = v1.root(); root2 = v2.root(); root3 = v3.root();
      root1.setBorder(b); root2.setBorder(b);
                          root3.setBorder(b);
   }
   public void stop() {
      info.saveMap(accounts, bank.nextAcctNum());
   }
   public static void main(String[] args) {
      Application.launch(args);
   }
}
Listing 11-25

The FxBankProgram Class

The Create Account View

The “create account” view class is called CreationView. Its code appears in Listing 11-26. The code is similar to the AccountCreationWindow class from Chapters 9 and 10, except that it now has a controller to talk to.
public class CreationView {
   private Pane root;
   private Label title = new Label("Create a New Bank Acct");
   public CreationView(CreationController controller) {
      controller.setView(this);
      root = createNodeHierarchy(controller);
   }
   public Pane root() {
      return root;
   }
   public void setTitle(String msg) {
      title.setText(msg);
   }
   private Pane createNodeHierarchy(CreationController cont) {
      ... // Create the hierarchy as in Listing 9-9. Root is p1.
      btn.addEventHandler(ActionEvent.ACTION, e -> {
         cont.buttonPressed(chbx.getSelectionModel()
                                .getSelectedIndex(),
                            ckbx.isSelected());
         String foreign = ckbx.isSelected() ? "Foreign " : "";
         String acct = chbx.getValue();
         title.setText(foreign + acct + " Account Created");
      });
      return p1;
   }
}
Listing 11-26

The CreationView Class

The view talks to its controller via the button handler. The handler calls the controller’s buttonPressed method, passing it the values of the choice box and the check box. After an account has been created, the controller will call the view’s setTitle method, passing it the message to be displayed.

The controller is called CreationController. Its code appears in Listing 11-27. Its buttonPressed method calls the bank’s newAccount method to create the account.
public class CreationController implements BankObserver {
   private Bank bank;
   private CreationView view;
   public CreationController(Bank bank) {
      this.bank = bank;
      bank.addObserver(BankEvent.NEW, this);
   }
   // methods called by the view
   void setView(CreationView view) {
      this.view = view;
   }
   public void buttonPressed(int type, boolean isforeign) {
      bank.newAccount(type+1, isforeign);
   }
   // method called by the model
   public void update(BankEvent e, BankAccount ba, int amt) {
      view.setTitle("Account " + ba.getAcctNum()
                               + " created");
   }
}
Listing 11-27

The CreationController Class

The controller, like all controllers that follow the MVC pattern, is a model observer. Recall from Chapter 10 that Bank supports four events. The controller registers itself with the bank as an observer of NEW events. When the controller receives an update notification, it constructs a message for the view to display and calls the view’s setTitle method.

The Account Information View

The “account information” view class is called InfoView. Its code appears in Listing 11-28.
public class InfoView {
   private Pane root;
   private TextField balfld = createTextField(true);
   private ChoiceBox<String> forbx = new ChoiceBox<>();
   public InfoView(InfoController controller) {
      controller.setView(this);
      root = createNodeHierarchy(controller);
   }
   public Pane root() {
      return root;
   }
   public void setBalance(String s) {
      balfld.setText(s);
   }
   public void setForeign(boolean b) {
      String s = b ? "Foreign" : "Domestic";
      forbx.setValue(s);
   }
   private Pane createNodeHierarchy(InfoController cont) {
      ... // Create the hierarchy, with p1 as the root.
      depbtn.setOnAction(e ->
                controller.depositButton(depfld.getText()));
      loanbtn.setOnAction(e ->
                respfld.setText(controller.loanButton(
                                       loanfld.getText())));
      forbtn.setOnAction(e ->
                controller.foreignButton(forbx.getValue()));
      selectbtn.setOnAction(e ->
              controller.selectButton(selectfld.getText()));
      return p1;
   }
}
Listing 11-28

The InfoView Class

The view has four buttons , and the handler for each one calls a different controller method. Note that the values sent to the controller methods are strings even if the values denote numbers. The controller is responsible for translating a value to its proper type because it understands how the value is to be used.

The loan authorization button is different from the others in that it requests a value from the model. Thus its controller method is not void. The view displays the return value in its “loan response” text field.

The view’s controller is called InfoController . Its code appears in Listing 11-29. It has a method for each button of the view; each method performs the necessary actions on the model for its button. For example, the depositButton method calls the bank’s deposit method. The selectButton method retrieves the BankAccount object for the current account, and tells the view to set the displayed value of the balance text field and ownership choice box.
public class InfoController implements BankObserver {
   private Bank bank;
   private int current = 0;
   private InfoView view;
   public InfoController(Bank bank) {
      this.bank = bank;
      bank.addObserver(BankEvent.DEPOSIT, this);
      bank.addObserver(BankEvent.INTEREST, this);
      bank.addObserver(BankEvent.SETFOREIGN, this);
   }
   // methods called by the view
   public void setView(InfoView view) {
      this.view = view;
   }
   public void depositButton(String s) {
      int amt = Integer.parseInt(s);
      bank.deposit(current, amt);
   }
   public String loanButton(String s) {
      int loanamt = Integer.parseInt(s);
      boolean result = bank.authorizeLoan(current, loanamt);
      return result ? "APPROVED" : "DENIED";
   }
   public void foreignButton(String s) {
      boolean b = s.equals("Foreign") ? true : false;
      bank.setForeign(current, b);
   }
   public void selectButton(String s) {
      current = Integer.parseInt(s);
      view.setBalance(
                Integer.toString(bank.getBalance(current)));
      String owner = bank.getAccount(current).isForeign() ?
                                     "Foreign" : "Domestic";
      view.setForeign(bank.isForeign(current));
   }
   // method called by the model
   public void update(BankEvent e, BankAccount ba, int amt) {
      if (e == BankEvent.SETFOREIGN &&
             ba.getAcctNum() == current)
         view.setForeign(ba.isForeign());
      else if (e == BankEvent.INTEREST ||
             ba.getAcctNum() == current)
         view.setBalance(
                Integer.toString(bank.getBalance(current)));
   }
}
Listing 11-29

The InfoController Class

The controller registers itself as the observer of three bank events: DEPOSIT, INTEREST, and SETFOREIGN. Its update method checks its first argument to determine which event caused the update. For an INTEREST event, the controller gets the balance of the current account and sends it to the view’s setBalance method. For a DEPOSIT or SETFOREIGN event, the controller checks to see if the affected account is the current account. If so, it gets the balance (or ownership) of the current account and sends it to the view.

The All Accounts View

The “all accounts” view class is called AllView. Listing 11-30 gives its code. The handler for the Add Interest button simply calls the controller’s interestButton method. When the controller decides to refresh the display of accounts, it calls the view’s setAccounts method.
public class AllView {
   private Pane root;
   TextArea accts = new TextArea();
   public AllView(AllController controller) {
      controller.setView(this);
      root = createNodeHierarchy(controller);
   }
   public Pane root() {
      return root;
   }
   public void setAccounts(String s) {
      accts.setText(s);
   }
   private Pane createNodeHierarchy(AllController cont) {
      accts.setPrefColumnCount(22);
      accts.setPrefRowCount(9);
      Button intbtn = new Button("Add Interest");
      intbtn.setOnAction(e -> cont.interestButton());
      VBox p1 = new VBox(8);
      p1.setAlignment(Pos.TOP_CENTER);
      p1.setPadding(new Insets(10));
      Label title = new Label("Manage All Accounts");
      double size = title.getFont().getSize();
      title.setFont(new Font(size*2));
      title.setTextFill(Color.GREEN);
      p1.getChildren().addAll(title, accts, intbtn);
      return p1;
   }
}
Listing 11-30

The AllView Class

The view displays the list of accounts in a text box. The problem with this design decision is that a single account value cannot be updated individually. The setAccounts method must therefore replace the entire list with a new one. The next two sections will examine other controls that will produce a better implementation.

The controller is called AllController . Its code appears in Listing 11-31. The controller is an observer of all four Bank events. Whenever an event of any type occurs, the controller refreshes the displayed accounts by calling the method refreshAccounts. This method iterates through the bank accounts and creates a string that appends their toString values. It then sends this string to the view.
public class AllController implements BankObserver {
   private Bank bank;
   private AllView view;
   public AllController(Bank bank) {
      this.bank = bank;
      bank.addObserver(BankEvent.NEW, this);
      bank.addObserver(BankEvent.DEPOSIT, this);
      bank.addObserver(BankEvent.SETFOREIGN, this);
      bank.addObserver(BankEvent.INTEREST, this);
   }
   // methods called by the view
   public void setView(AllView view) {
      this.view = view;
      refreshAccounts(); // initially populate the text area
   }
   public void interestButton() {
      bank.addInterest();
   }
   // method called by the model
   public void update(BankEvent e, BankAccount ba, int amt) {
      refreshAccounts();
   }
   private void refreshAccounts() {
      StringBuffer result = new StringBuffer();
      for (BankAccount ba : bank)
         result.append(ba + " ");
      view.setAccounts(result.toString());
   }
}
Listing 11-31

The AllController Class

Observable List Views

Using a text area to implement the list of all accounts is unsatisfying: it looks bad, and it needs to be completely refreshed even if a single account changes. JavaFX has a control, ListView, that is more satisfactory. Figure 11-8 shows a screenshot of it in the “All Accounts” view.
../images/470600_1_En_11_Chapter/470600_1_En_11_Fig8_HTML.jpg
Figure 11-8

The Manage All Accounts screen

The difference between a list view and a text area is that the list view displays the contents of a Java List object. Each line of the list view corresponds to an element of the list, and displays the result of calling the toString method of that element.

The classes AllView2 and AllController2 rewrite AllView and AllController to use the ListView control . Listing 11-32 gives the code for AllView2, with new code in bold.
public class AllView2 {
   private Pane root;
   ListView<BankAccount> accts = new ListView<>();
   public AllView2(AllController2 controller) {
      root = createNodeHierarchy(controller);
      accts.setItems(controller.getAccountList());
   }
   ...
}
Listing 11-32

The AllView2 Class

There are only two new lines of code. The first line creates a new ListView object. The second line specifies the list that it should display, which in this case is the list returned by the controller’s getAccountList method.

AllView2 no longer needs a method to update its ListView control . Instead, the control and its list are connected by the observer pattern. The list is an observable. The ListView control is an observer of its list. When the controller changes the list, the list notifies the control and the control updates itself automatically.

This feature simplifies the view–controller interaction. The controller no longer needs to explicitly manage the view updating. When the model notifies the controller that the accounts have changed, the controller only needs to modify its list. The view figures out the rest.

The code for the controller is named AllController2, and appears in Listing 11-33. The variable accounts holds the observable list of BankAccount objects. The JavaFX class FXCollections contains several static factory methods for creating observable objects; the method observableArrayList creates an observable list that wraps an ArrayList object.
public class AllController2 implements BankObserver {
   private Bank bank;
   private ObservableList<BankAccount> accounts
                 = FXCollections.observableArrayList();
   public AllController2(Bank bank) {
      this.bank = bank;
      bank.addObserver(BankEvent.NEW, this);
      bank.addObserver(BankEvent.DEPOSIT, this);
      bank.addObserver(BankEvent.SETFOREIGN, this);
      bank.addObserver(BankEvent.INTEREST, this);
      for (BankAccount ba : bank)
         accounts.add(ba); // initially populate the list
   }
   public ObservableList<BankAccount> getAccountList() {
      return accounts;
   }
   public void interestButton() {
      bank.addInterest();
   }
   public void update(BankEvent e, BankAccount ba, int amt) {
      if (e == BankEvent.INTEREST)
         refreshAllAccounts();
      else if (e == BankEvent.NEW)
         accounts.add(ba);
      else {
         int i = accounts.indexOf(ba);
         refreshAccount(i);
      }
   }
   private void refreshAccount(int i) {
      // a no-op, to force the list to notify its observer
      accounts.set(i, accounts.get(i));
   }
   private void refreshAllAccounts() {
      for (int i=0; i<accounts.size(); i++)
         refreshAccount(i);
   }
}
Listing 11-33

The AllController2 Class

The controller observes four kinds of event, and its update method performs a different action based on the event. For an INTEREST event, the controller calls refreshAllAccounts, so that the view will redisplay each element of the list. For a NEW event, the controller adds the new bank account to the list. For DEPOSIT and SETFOREIGN events, the controller refreshes the list element having the specified account number.

Note that a DEPOSIT or SETFOREIGN event changes the state of a list element, but does not actually change the list. This is a problem, because the list will not notify the view unless it changes. The refreshAccount method solves the problem by setting the new value of a list element to be the same as the old value. Although that operation has no effect on the list element, the list recognizes it as a change to the list, and notifies the view to redisplay the element.

Observable Table Views

The ListView control displays the information about each BankAccount object in a single cell. It would be more visually pleasing if the account information could be displayed as a table, with each value in its own cell. This is the purpose of the TableView control . Figure 11-9 shows a screenshot of the view revised to use a TableView control.
../images/470600_1_En_11_Chapter/470600_1_En_11_Fig9_HTML.jpg
Figure 11-9

Manage All Accounts as a table view

This view is named AllView3, and its code appears in Listing 11-34. The variable accts is now of type TableView. A TableView control observes a list, the same as ListView . Its method setItems connects the control with that list. Because the mechanism is exactly the same as with the ListView control, AllView3 can use the controller AllController2 the same as AllView2.
public class AllView3 {
   private Pane root;
   TableView<BankAccount> accts = new TableView<>();
   public AllView3(AllController2 controller) {
      root = createNodeHierarchy(controller);
      TableColumn<BankAccount,Integer> acctnumCol
                = new TableColumn<>("Account Number");
      acctnumCol.setCellValueFactory(p -> {
         BankAccount ba = p.getValue();
         int acctnum = ba.getAcctNum();
         Property<Integer> result
                = new SimpleObjectProperty<>(acctnum);
         return result;
      });
      TableColumn<BankAccount,Integer> balanceCol
                  = new TableColumn<>("Balance");
      balanceCol.setCellValueFactory(p ->
              new SimpleObjectProperty<>
                              (p.getValue().getBalance()));
      TableColumn<BankAccount,String> foreignCol
                  = new TableColumn<>("Owner Status");
      foreignCol.setCellValueFactory(p -> {
         boolean isforeign = p.getValue().isForeign();
         String owner = isforeign ? "Foreign" : "Domestic";
         return new SimpleObjectProperty<>(owner);
      });
      accts.getColumns().addAll(acctnumCol, balanceCol,
                                foreignCol);
      accts.setItems(controller.getAccountList());
      accts.setPrefSize(300, 200);
   }
   ...
}
Listing 11-34

The AllView3 Class

The difference between TableView and ListView is that a TableView control has a collection of TableColumn objects. The method getColumns returns this collection.

A TableColumn object has a header string, which is passed into its constructor. A TableColumn object also has a “cell value factory.” The argument to this object is a method that computes the display value of the cell for a given list element. The argument p to the method denotes an element of the observable list, which here is an object that wraps BankAccount. Its method getValue returns the wrapped BankAccount object. The SimpleObjectProperty class creates a property from its argument object.

For example, consider the first lambda expression in Listing 11-34, which computes the value of the column acctnumCol.

   p -> {
      BankAccount ba = p.getValue();
      int acctnum = ba.getAcctNum();
      Property<Integer> result =
                new SimpleObjectProperty<>(acctnum);
      return result;
   }
This lambda expression unwraps p, extracts the account number from the unwrapped bank account, wraps the value as a property, and returns it. The lambda expression can be expressed more succinctly as follows:
   p -> new SimpleObjectProperty<>(p.getValue().getAcctNum())

Summary

The MVC design rule states that each class in a program should have either model, view, or controller responsibilities. Designing programs in this way can require discipline. Instead of writing a single class to perform a task you might need to write three classes, so as to separate the model, view, and controller aspects of the task. Although creating these classes will undoubtedly require more effort, they come with significant benefits. The separated concerns make the program more modular and more easily modifiable, and therefore more in line with the fundamental design principle.

The MVC pattern describes an effective way to organize the model, view, and controller classes. According to the pattern, a program will have one model and possibly several views. Each view has its own controller, and uses the controller as a mediator to help it communicate with the model. Each controller has a reference to its view and to the model, so that it can send view requests to the model and model updates to the view. The model, however, knows nothing about the controllers and views. Instead, it communicates with them via the observer pattern.

The MVC pattern choreographs an intricate dance between the model, controllers, and views. The purpose of this dance is to support flexibility and modifiability of the program. In particular, the views are independent of each other; you can add and remove a view from an MVC program without impacting the other views.

This flexibility has enormous value. This chapter gave several examples of commercial MVC-based software—such as Excel, pdf viewers, and file managers—and describes features they have that are possible because of their MVC architecture.

Although this chapter has given enthusiastic support for the MVC pattern, the reality is that the pattern has no single agreed-upon definition. The definition given in this chapter is just one of several ways that have been used to organize models, views, and controllers. Regardless of their differences, however, the central feature of all MVC definitions is their following use of the observer pattern: Controllers make update requests to the model, and the model notifies its observers of the resulting state changes. I prefer using the controllers as model observers, but it is also possible to use the views, or even a combination of views and controllers. There are also different approaches for how a view can be connected to its controller.

As with all the design patterns in this book, there are always tradeoffs to be made. A good designer will adjust the connections among the MVC components to fit the needs of a given program. In general, the more deeply you understand how and why the MVC pattern works, the more freedom you will have to make the adjustments that will lead you to the best possible design.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.145.82