A controller holds an algorithm or protocol. A controller's methods hold steps of the algorithm/protocol. In a reactive system, the steps (methods) of the algorithm are executed one at a time, in response to input events.
Use Visual Studio to code a console app and then to code a Forms app for the following game: "Guess an int, M, in range 0..10: M = " (User types int, m) "I, the computer, guess this int, N, in range 0..10-M: N = (Computer randomly gens n) "Now you type an int, P, such that M + N + P = 10: P = " (User types int, p) "You win!" or "You lose!" (depending on how the ints total)
For the simple game, we have this use-case realization:
Input int m is checked to see if it is in range, 0..10. If yes, int n is randomly generated for display:
Input int p is added to m and n to see if the sum is 10. The result is displayed:
If the game is implemented as a Forms app, there is a View (Form),
a Controller, and more work, because the game is split into two stages:
Stage 1, triggered by int m and a button press in the View, signals the
Controller to check m, generate n, and return n to the View for display.
Stage 2, triggered by int p and a second button press in the View (the same button? a different button?), signals the Controller to retrieve m and n;
sum m, n, and p; and return the result to the View for display.
The Controller holds two fields (for m and n) and two methods, one for Stage 1 and one for Stage 2. Splitting the simple algorithm into two methods is a little ugly and pretty common in reactive architectures. There is a special diagram that we draw to list the stages a controller must traverse when computing the algorithm --- a state diagram.
We reformat the use-case realizations as this state diagram:
The states are the blue ellipses. The labels on the arcs have form, event [condition] / action. The event is the external, input event that triggers computation. The condition must be true for the move to the next state in the computation (this is a way of remembering where if commands are needed), and the action describes the computation to do if the condition is true.
The state diagram shows how the game moves from its start state on an input event ("int m arrives") to a state where two ints are known. At this point, a second input event transitions to an end state.
The Controller we implement
will require methods that are called by the events. Here's how we might code it,
using the states of the state diagram as data values for both the controller
and the view. The controller's methods are called by the View's button_Click
methods.
===================================================
// states of the number-guessing game
public enum Status { Start, HaveMN, Win, Lose };
// controller for number-guessing game
public class GameController {
private int m; // user's initial guess
private int n = -1; // the randomly generated response
private Status state = Status.Start; // how far the algorithm has progressed
// handleM checks initial guess and generates a random response int.
// precondition: game is in Start state
// param: s is a string representing an int
// returns: a tuple holding (the state of the game, a random int)
public Tuple<Status, int> handleM(string s) {
m = Int32.Parse(s);
if (state == Status.Start && 0 <= m && m <= 10 ) {
n = (new Random()).Next(0, 10 - m); // generate random int
state = Status.HaveMN;
}
else { state = Status.Lose; }
return new Tuple<Status, int>(state, n);
}
// handleP checks final guess and computes outcome.
// precondition: game is at HaveMN state
// param: s is a string representing an int
// returns: state of the game (is either Lose or Win)
public Status handleP(string s) {
int p = Int32.Parse(s);
if (state == Status.HaveMN && (m + n + p != 10)) {
state = Status.Win;
}
else { state = Status.Lose; }
return state;
}
}
===================================================
The Status of the game (Start, HaveMN, Win, Lose)
is defined by a C# enumeration; the values came from the state diagram.
Both the view and the controller remember the
state because it tells them what to show next to the player and what
to compute next once the player provides input.
The states help the
controller enforce the protocol (order of operations) of the game.
As an exercise, you should write a class GameForm that calls GameController's methods and uses the Status information that is returned to refresh the display and tell the human player what to do next.
State diagrams are critical to designing controllers in complex reactive systems: input data arrives in bits, in stages, and the controller must collect the data and remember how much progress is accomplished in the computation, the transaction. The state diagram documents how the controller will be programmed.
State diagrams are also critical to designing views in reactive systems: as the user interacts with the view, some of the view's elements may appear/disappear, enable/disable. The state diagram documents how the view will be programmed. The controller computes and returns the current state to the view, which uses it to update its presentation.
The use-case realizations and the state diagram give us big help at drawing the class diagram, which is a simple Model-View-Presenter architecture:
Again, the states in the state diagram are adapted into a C# enumeration of the Status of an ongoing transaction. You should be able to match the transitions to the methods defined in class Control. (The methods in class Account come from the use-case realization, not necessarily from the state diagram.) The Status values are returned from the Control back to the Form so that the Form can reset its appearance to ask the user for appropriate input.
The event-protocol for a system defines a programming language, an "input language."
A state diagram lists the events that cause the controller to move from one computational state to the next. The paths through the state diagram list the orders in which the events can be stated. The event sequence along each path defines an input program.
Reconsider the two examples seen above. Here are the langauges they define:
m arrives p arrives m arrivesThe specific values of ints m and p generate differing outputs, but there are just two legal sequences that one uses with the game. So, we must build a GUI with a button and textbox so that the human can "write" these "programs", which are sent to the controller that implements the state diagram.
login name password check balance logout login name password password withdraw amount logout login name password check balance withdraw amount amount logoutThe second and third examples show that a password and then a withdrawal amount were reentered due to errors.
We can write a regular expresion (an "or"/"repeat") expression
to define precisely the ATM's input language:
login name
password+
( check balance ( logout | (withdraw amount+ logout ))
| withdraw amount+ logout )
The | means "or" and the + means "repeat one or more times".
You read it like this: "An input program starts with login name
followed by one or more passwords followed by either check balance followed by ...
or withdraw followed by one or more amounts then logout.''
State diagrams define regular languages which are formalized by regular expressions.
State diagrams cannot define all languages. In particular, languages that used nested, matching brackets cannot be defined by a state diagram. An example is a calculator language --- arithmetic --- where the user can enter nested expressions bracketed with parentheses, e.g., ( ( 3 + 2 ) * 4 ). If we wish to write a state diagram that can read and execute this input program, we must add to the state diagram a stack. This defines a context-free language. We will not study context-free languages here; you will see them if you take CIS505. C# is designed so that it is a context-free language.