Lab 01
In this first lab session you will restructure code, so it complies to MVC pattern. Respecting MVC will be a grading criteria for your TP submission. This lab session provides you with the basic skills to organize your code and practice separation of concern habits.
Warmup
In class, you've seen how to compile and run java class from command line. To get started, do the following:
- Download the sample game.
- Extract the project on disk.
- Open a terminal and
cd
to the project location. Enter themvc-exercise
directory. - Compile the project with
javac *java
- Run the project with
java View
- Inspect the
View
code with a text editor, and change it so the second output shows no longer the top leftx
For now, do not use an IDE
Of course we are going to massively use IDEs later in the course. For now it is important to understand where to place files on disk and what happens when compiling code. Refresh your basics now, I mights ask some related questions in the exam.
MVC
In the previous lecture, we've briefly discussed the MVC pattern, which allows the structuring of code into modules, each module with a specific purpose.
- Model: Represents model state, but contains as little logic as possible.
- Controller: Gives control over model modification, ensures model changes are legit.
- View: Visualizes model state and passes model change requests to the controller.
Image credits: Mozilla MVC Documentation
Existing code
The code you just tested is a mini-game, but
the implementation is sloppy,
because the game has a model
and a view
, but the view
has full read and write access on the model. In other
words, there is no
controller.
- In the current state, the view can arbitrarily modify the data stored in the model, all changes are possible: reasonable changes, as well as nonsense changes.
- In this lab session you'll add a controller, which "controls" how the model is modified by the view. Ultimately, using the controller, the view cannot corrupt the model. The controller serves therefore as a layer of protection.
Game rules
The code provided to you represents a very simple "game". A 4x4 game board and a single figure that can be moved around.
- The model represents a simple 4x4 boolean matrix.
- Boolean values encode empty (
false
) fields, or the field where the figure stands (true
). - Initially, the figure stands on the top left position, so only
[0][0]
istrue
, all other array positions arefalse
.
- Boolean values encode empty (
- There is only one figure on the board, and the view attempts to move it from the initial top left
(0,0)
position to the bottom right(3,3)
position. - The context is a game, where figures must only be moved from one tile to an adjacent tile. Other moves are not allowed.
- Currently, as the model has absolutely no protection, and the sample code provided in the view takes advantage of
that.
- The view could follow the rules, by step by step placing the figure toward the bottom right field, one step at a time.
- However, since there is no controller to enforce following the rules, the view can also just ignore the game rules and place the controller directly on the target field.
Example 1: View respects the game rules and moves figure, one field at a time
// Step 1: move figure to the right
model.board[0][0]=false; // remove figure from top left field
model.board[0][1]=true; // place figure field to the right
// Step 2: move figure to the right, again
model.board[0][1]=false; // remove figure from top left field
model.board[0][2]=true; // place figure field to the right
// Step 3: move figure to the right, again
model.board[0][2]=false; // remove figure from top left field
model.board[0][3]=true; // place figure field to the right
// Step 4: move figure down
model.board[0][3]=false; // remove figure from top left field
model.board[1][3]=true; // place figure field to the right
// Step 5: move figure down, again
model.board[1][3]=false; // remove figure from top left field
model.board[2][3]=true; // place figure field to the right
// Step 6: move figure down, again
model.board[2][3]=false; // remove figure from top left field
model.board[3][3]=true; // place figure field to the right
Example 2: View ignores the game rules, and places figure directly on target field
// Move the figure directly to target position, instead of moving along a path of allowed sequential moves.
// This implementation ignores the game rules.
model.board[0][0]=false; // remove figure from top left field
model.board[3][3]=true; // place figure on bottom right field
Required changes
- Your job is to secure the game, so the view is forced to play by the rules.
- With the changes you make, the above example 2 will no longer be possible.
- In detail, you have to:
- Create a new class "Controller"
- Add 4 method to the controller: (each changing the model state)
public void moveLeft() {...}
public void moveRight() {...}
public void moveUp() {...}
public void moveDown() {...}
- Make sure the view uses the controller to move the figure, i.e. the view no longer directly modifies the
Model
class.
Why do these changes render our implementation safer?
When manually setting individual fields to true
or false
, we can easily make mistakes, or altogether ignore the game rules on purpose. Using dedicated controller methods to navigate the figure is more elegant and ensured to respect the game rules.
Packages
Usually your Model
, View
, and Controller
are not just a single class, but several respective bundles of classes.
Java offers a
concept named packages
to group classes into folder like structures.
- Revise your code from the previous exercise, so every class resides in a dedicated folder, i.e. your codebase should look like this:
What has changed
The classes now reside in dedicated folders, names view
, controller
and model
.
Note that java classes cannot access code in other packages (folders) without a dedicated import
statement:
Example, View.class
needs an extra first line:
What's an import, anyways ? Why not just put everything in one folder ?
Here's a great explanation of imports and how to use them...
Defensive programming
Your code is probably somewhat working now. But is it robust ? Will it crash in corner cases ? Can it still be misused ( hacked) to circumvent MVC protections ?
Sane move commands
Time for some tests: The purpose of the controller is not only to ensure your model remains in a sane state, but also ensure no dangerous commands are executed.
- What happens if moveRight is called 4 times, instead of 3 times in a row ? (That's kind of dangerous, because you'd literally move the figure off the grid.)
- A good controller will make sure your program does not crash.
- Harden your code so the program does not crash when the controller is used to navigate the figure out of the board boundaries.
- Change the controller method return type to
boolean
and returnfalse
when a requested move is not allowed.
Interface protection
- In the next lecture we'll take a closer look at concept named
interfaces
. - For now, it is sufficient to know that interfaces can be used to "hide" some methods, so they cannot be exploited.
How could an interface be used to further secure the implementation?
Right now, the view still has access to the model. While (hopefully) you changed your implementation, so the model is modified using the controller, the view still needs read access to the model (simply to display things). An interface can be used to "hide" all methods (or private fields) that modify model state. This way the view can access the model
to read (display game state), but is forced to use the controller for state changes.
Illustration
We can define an interface ModelReadOnly.java
:
package model;
/**
* Read only interface for the MVC model. It must not be possible to modify model state, using
* the methods offered by this interface.
*
* @author Maximilian Schiedermeier
*/
public interface ModelReadOnly {
/**
* Retruns a 2d representation of the model. Only a single field in the matrix must be true,
* and indicated the position of the figure on the board.
*
* @return 2d boolean array with exatly one field true.
*/
boolean[][] getBoard();
}
Similarly, we tell our existing Model
implementation, that it adheres to the freshly defined interface, but returns a
copy instead of the actual data. For any printing buiseness, this will serve. But if someone tries to hijack our getter
to actually manipulate model statue (i.e. sidestepping the controller) their efforts are futile.
package model;
// This next line has been changed.
// The `implements` keyword links the `ModelReadOnly`
// interface to our implementation.
public class Model implements ModelReadOnly {
// [...]
// Here we make sure to return a *copy* of the model state,
// instead of exposing the model.
@Override
public boolean[][] getBoard() {
boolean[][] boardRepresentation = new boolean[4][4];
for (int y = 0; y < boardRepresentation.length; y++) {
for (int x = 0; x < boardRepresentation[y].length; x++) {
boardRepresentation[y][x] = false;
}
}
boardRepresentation[posY][posX] = true;
return boardRepresentation;
}
}
Ok, but what is gained ?
- The
view
can still access themodel
for reading:
ModelReadOnly model = new Model();
model.getBoard(); // <- This method is available
model.board[0][0]=false; // <-- This line is rejected by the compiler.
// ... because the ModelReadOnly interface does not grant
// access to class internals.
Cherry topping: custom toString
With all the changes so far, your code is fairly robust. But the view has still too much knowledge on the model internals. Ideally the view does not know more about the Model internals than it absolutely needs to know.
Our view is in so far primitive, as that it only prints a string representation of the model object. This can be handled by the model !
- Implement a
toString()
method for your model and modify your readOnly interface, so the view never accesses internals of the model object.toString()
methods are implicitly called when you print an object. SoSystem.out.println(model)
, will call theModel
classestoString()
method.
- Have the view print the model by implicitly invoking
toString()
.
Halma online
- Congratulations, you're done with the coding exercise.
- Now it's time to play Halma. You are going to implement Halma as part of your group project, so the best preparation
for your group project is playing some sounds of Halma online.
- The better you know what you're about to implement, the easier ;)
Some breadcrumbs
As you are playing the game online: Can you think of some MVC patterns to apply to your future Halma code ? Any parallels to the code you just wrote ? What changes to the model might be necessary ? What changes to the controller ?
Solution
The solution for this lab session is available on GitLab: