17.13. Controller
Problem What first object beyond the UI layer receives and coordinates ("controls") a system operation?System operations were first explored during the analysis of SSD. These are the major input events upon our system. For example, when a cashier using a POS terminal presses the "End Sale" button, he is generating a system event indicating "the sale has ended." Similarly, when a writer using a word processor presses the "spell check" button, he is generating a system event indicating "perform a spell check."A controller is the first object beyond the UI layer that is responsible for receiving or handling a system operation message.Solution Assign the responsibility to a class representing one of the following choices:
- Represents the overall "system," a "root object," a device that the software is running within, or a major subsystemthese are all variations of a facade controller .
- Represents a use case scenario within which the system event occurs, often named <UseCaseName>Handler, <UseCaseName>Coordinator, or <UseCaseName>Session (use case or session controller ).
- Use the same controller class for all system events in the same use case scenario.
- Informally, a session is an instance of a conversation with an actor. Sessions can be of any length but are often organized in terms of use cases (use case sessions).
Corollary :
Note that "window," "view," and "document" classes are not on this list. Such classes should not fulfill the tasks associated with system events; they typically receive these events and delegate them to a controller.Example Some get a better sense of applying this pattern with code examples. Look ahead in the Implementation section on p. 309 for Java examples of both rich client and Web UIs.The NextGen application contains several system operations, as illustrated in Figure 17.20. This model shows the system itself as a class (which is legal and sometimes useful when modeling).
Figure 17.20. Some system operations of the NextGen POS application.
Figure 17.21. What object should be the Controller for enterItem?
[View full size image]
Represents the overall "system," "root object," device, or subsystem. | Register, POSSystem |
Represents a receiver or handler of all system events of a use case scenario. | ProcessSaleHandler, ProcessSaleSession |
Figure 17.22. Controller choices.
Figure 17.23. Allocation of system operations.
[View full size image]
Guideline Normally, a controller should delegate to other objects the work that needs to be done; it coordinates or controls the activity. It does not do much work itself. |
[12] Various terms are used for a physical POS unit, including register, point-of-sale terminal (POST), and so forth. Over time, "register" has come to embody the notion of both a physical unit and the logical abstraction of the thing that registers sales and payments.
Facade controllers are suitable when there are not "too many" system events, or when the user interface (UI) cannot redirect system event messages to alternating controllers, such as in a message-processing system.If you choose a use case controller, then you will have a different controller for each use case. Note that this kind of controller is not a domain object; it is an artificial construct to support the system (a Pure Fabrication in terms of the GRASP patterns). For example, if the NextGen application contains use cases such as Process Sale and Handle Returns , then there may be a ProcessSaleHandler class and so forth.When should you choose a use case controller? Consider it an alternative when placing the responsibilities in a facade controller leads to designs with low cohesion or high coupling, typically when the facade controller is becoming "bloated" with excessive responsibilities. A use case controller is a good choice when there are many system events across different processes; it factors their handling into manageable separate classes and also provides a basis for knowing and reasoning about the state of the current scenario in progress.In the UP and Jacobson's older Objectory method [Jacobson92], there are the (optional) concepts of boundary, control, and entity classes. Boundary objects are abstractions of the interfaces, entity objects are the application-independent (and typically persistent) domain software objects, and control objects are use case handlers as described in this Controller pattern.A important corollary of the Controller pattern is that UI objects (for example, window or button objects) and the UI layer should not have responsibility for fulfilling system events. In other words, system operations should be handled in the application logic or domain layers of objects rather than in the UI layer of a system. See the "Issues and Solutions" section for an example.
Web UIs and Server-Side Application of Controller
Please see p. 310 for a server-side example using Java Strutsa popular framework.A similar delegation approach can be used in ASP.NET and WebForms: The "code behind" file that contains event handlers for Web browser button clicks will obtain a reference to a domain controller object (e.g., a Register object in the POS case study), and then delegate the request for work. This is in contrast to the common, fragile style of ASP.NET programming in which developers insert application logic handling in the "code behind" file, thus mixing application logic into the UI layer.Server-side Web UI frameworks (such as Struts) embody the concept of the Web-MVC (Model-View-Controller) pattern. The "controller" in Web-MVC differs from this GRASP controller. The former is part of the UI layer and controls the UI interaction and page flow. The GRASP controller is part of the domain layer and controls or coordinates the handling of the work request, essentially unaware of what UI technology is being used (e.g., a Web UI, a Swing UI, …).Also common with server-side designs when Java technologies are used is delegation from the Web UI layer (e.g., from a Struts Action class) to an Enterprise JavaBeans (EJB ) Session object. Variant #2 of the Controller patternan object representing a user session or use case scenariocovers this case. In this case, the EJB Session object may itself delegate farther on to the domain layer of objects, and again, you can apply the Controller pattern to choose a suitable receiver in the pure domain layer.All that said, the appropriate handling of server-side systems operations is strongly influenced by the chosen server technical frameworks and continues to be a moving target. But the underlying principle of Model-View Separation can and does still apply.Even with a rich-client UI (e.g., a Swing UI) that interacts with a server, the Controller pattern still applies. The client-side UI forwards the request to the local client-side controller, and the controller forwards all or part of the request handling to remote services. This design lowers the coupling of the UI to remote services and makes it easier, for example, to provide the services either locally or remotely, through the indirection of the client-side controller.Benefits
- Increased potential for reuse and pluggable interfaces
These benefits ensure that application logic is not handled in the interface layer. The responsibilities of a controller could technically be handled in an interface object, but such a design implies that program code and the fulfillment of application logic would be embedded in interface or window objects. An interface-as-controller design reduces the opportunity to reuse logic in future applications, since logic that is bound to a particular interface (for example, window-like objects) is seldom applicable in other applications. By contrast, delegating a system operation responsibility to a controller supports the reuse of the logic in future applications. And since the application logic is not bound to the interface layer, it can be replaced with a different interface. - Opportunity to reason about the state of the use case
Sometimes we must ensure that system operations occur in a legal sequence, or we want to be able to reason about the current state of activity and operations within the use case that is underway. For example, we may have to guarantee that the makePayment operation cannot occur until the endSale operation has occurred. If so, we need to capture this state information somewhere; the controller is one reasonable choice, especially if we use the same controller throughout the use case (as recommended).
Implementation The following examples use Java technologies for two common cases, a rich client in Java Swing and a Web UI with Struts on the server (a Servlet engine).Please note that you should apply a similar approach in .NET WinForms and ASP.NET WebForms . A good practice in well-designed .NET (often ignored by MS programmers who violate the Model-View Separation Principle) is to not insert application logic code in the event handlers or in the "code behind" files (those are both part of the UI layer). Rather, in the .NET event handlers or "code behind" files, simply obtain a reference to a domain object (e.g., a Register object), and delegate to it.
Implementation with Java Swing: Rich Client UI
This section assumes you are familiar with basic Swing. The code contains comments to explain the key points. A few comments: Notice at that the ProcessSaleJFrame window has a reference to the domain controller object, the Register . At I define the handler for the button click. At I show the key messagesending the enterItem message to the controller in the domain layer.
package com.craiglarman.nextgen.ui.swing;
// imports…
// in Java, a JFrame is a typical window
public class ProcessSaleJFrame extends JFrame
{
// the window has a reference to the 'controller' domain object
private Register register;
// the window is passed the register, on creation
public ProcessSaleJFrame(Register _register)
{
register = _register;
}
// this button is clicked to perform the
// system operation "enterItem"
private JButton BTN_ENTER_ITEM;
// this is the important method!
// here i show the message from the UI layer to domain layer
private JButton getBTN_ENTER_ITEM()
{
// does the button exist?
if (BTN_ENTER_ITEM != null)
return BTN_ENTER_ITEM;
// ELSE button needs to be initialized...
BTN_ENTER_ITEM = new JButton();
BTN_ENTER_ITEM.setText("Enter Item");
// THIS IS THE KEY SECTION!
// in Java, this is how you define
// a click handler for a button
BTN_ENTER_ITEM.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
// Transformer is a utility class to
// transform Strings to other data types
// because the JTextField GUI widgets have Strings
ItemID id = Transformer.toItemID(getTXT_ID().getText());
int qty = Transformer.toInt(getTXT_QTY().getText());
// here we cross the boundary from the
// UI layer to the domain layer
// delegate to the 'controller'
// > > > THIS IS THE KEY STATEMENT < < <
register.enterItem(id, qty);
}
} ); // end of the addActionListener call
return BTN_ENTER_ITEM;
} // end of method
// …
} // end of class
Implementation with Java Struts: Client Browser and WebUI
This section assumes you are familiar with basic Struts. Notice at that to obtain a reference to the Register domain object on the server side, the Action object must dig into the Servlet context. At I show the key messagesending the enterItem message to the domain controller object in the domain layer.
package com.craiglarman.nextgen.ui.web;
// … imports
// in Struts, an Action object is associated with a
// web browser button click, and invoked (on the server)
// when the button is clicked.
public class EnterItemAction extends Action {
// this is the method invoked on the server
// when the button is clicked on the client browser
public ActionForward execute( ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response )
throws Exception
{
// the server has a Repository object that
// holds references to several things, including
// the POS "register" object
Repository repository = (Repository)getServlet().
getServletContext().getAttribute(Constants.REPOSITORY_KEY);
Register register = repository.getRegister();
// extract the itemID and qty from the web form
String txtId = ((SaleForm)form).getItemID();
String txtQty = ((SaleForm)form).getQuantity();
// Transformer is a utility class to
// transform Strings to other data types
ItemID id = Transformer.toItemID(txtId);
int qty = Transformer.toInt(txtQty);
// here we cross the boundary from the
// UI layer to the domain layer
// delegate to the 'domain controller'
// > > > THIS IS THE KEY STATEMENT < < <
register.enterItem(id, qty);
// …
} // end of method
} // end of class
Bloated Controllers
Issues and Solutions Poorly designed, a controller class will have low cohesionunfocused and handling too many areas of responsibility; this is called a bloated controller . Signs of bloating are:
- There is only a single controller class receiving all system events in the system, and there are many of them. This sometimes happens if a facade controller is chosen.
- The controller itself performs many of the tasks necessary to fulfill the system event, without delegating the work. This usually involves a violation of Information Expert and High Cohesion.
- A controller has many attributes, and it maintains significant information about the system or domain, which should have been distributed to other objects, or it duplicates information found elsewhere.
Among the cures for a bloated controller are these two:
- Add more controllersa system does not have to need only one. Instead of facade controllers, employ use case controllers. For example, consider an application with many system events, such as an airline reservation system.It may contain the following controllers:
Use case controllers MakeReservationHandler ManageSchedulesHandler ManageFaresHandler - Design the controller so that it primarily delegates the fulfillment of each system operation responsibility to other objects.
UI Layer Does Not Handle System Events
To reiterate: An important corollary of the Controller pattern is that UI objects (for example, window objects) and the UI layer should not have responsibility for handling system events. As an example, consider a design in Java that uses a JFrame to display the information.Assume the NextGen application has a window that displays sale information and captures cashier operations. Using the Controller pattern, Figure 17.24 illustrates an acceptable relationship between the JFrame and the controller and other objects in a portion of the POS system (with simplifications).
Figure 17.24. Desirable coupling of UI layer to domain layer.
[View full size image]
Figure 17.25. Less desirable coupling of interface layer to domain layer.
[View full size image]
Message Handling Systems and the Command Pattern
Some applications are message-handling systems or servers that receive requests from other processes. A telecommunications switch is a common example. In such systems, the design of the interface and controller is somewhat different. The details are explored in a later chapter, but in essence, a common solution is to use the Command pattern [GHJV95] and Command Processor pattern [BMRSS96], introduced in Chapter 38.Related Patterns
- Command
In a message-handling system, each message may be represented and handled by a separate Command object [GHJV95]. - Facade
A facade controller is a kind of Facade [GHJV95]. - Layers
This is a POSA pattern [BMRSS96]. Placing domain logic in the domain layer rather than the presentation layer is part of the Layers pattern. - Pure Fabrication
This GRASP pattern is an arbitrary creation of the designer, not a software class whose name is inspired by the Domain Model. A use case controller is a kind of Pure Fabrication.