This week we'll go over more examples of class design, with a focus on expanding our view of what we can represent with a class.
Up to now, we've seen examples of modeling real life (more or less) objects in
code, like Robot
or Human
or Timer
. The real power of object-oriented
programming (and some of the biggest conceptual differences from the more
straightforward procedural paradigm) comes in modeling more abstract
concepts like rules, strategies, and procedures.
Helpful organizational tools: packages and enums
A couple of new details we'll start to use in class:
Packages are the way to organize groups of classes in Java. Package
structure reflects the file system hierarchy: package x.y.z
lies in folder
x/y/z
on disk. You can use a package declaration statement at the top of each
source file, like package x.y.z;
.
Enums provide a way to organize a group of constant values in a convenient
way. In the tic-tac-toe example we'll use, the possible entries on the game
board are either X
, O
, or EMPTY
. We could just pick numbers or char
data to represent each type, but Java provides a shortcut for defining
groups of such related constants. In the simplest case, we can just say:
public enum Piece {
EMPTY, X, O
}
Then we can use Piece.X
, Piece.O
, and Piece.EMPTY
to represent
the various entries. In addition to convenience, we are given an extra
mesasure of type safety: if we had represented the pieces using
a char
, we would have to worry about handling errors due to the wrong
letter being used. But with an enum
, the three listed values are the
only ones possible anywhere in our program.
Modeling procedures as objects
Here's a simple example that filters standard input for numbers
in a desired range. How would you use this class to build a more
complicated filter that looks for numbers in any one of several ranges?
For example, checking for numbers that lie in [0, 1]
, [2, 3]
, or
[4, 5]
.
import java.util.Scanner;
public class RangeFilter {
private double lower;
private double upper;
public RangeFilter(double min, double max) {
lower = min;
upper = max;
}
public boolean contains(double x) {
return x >= lower && x <= upper;
}
// Filter standard input for numbers in the range [0, 10].
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
RangeFilter range = new RangeFilter(0, 10);
while (input.hasNextDouble()) {
double x = input.nextDouble();
if (range.contains(x))
System.out.println(x);
}
}
}
Modeling rules as objects
Look in the examples
directory for a tic-tac-toe-like project. We'll
fill in definitions for methods used to model the rules of the game, starting
from the template here:
package org.ilzd.tictacx;
/** Represent rules for a tic-tac-toe style game,
* and track status of game instance with respect
* to the rules.
*
*/
public class Rules {
private Board board;
/** Track current player. */
private Piece player = Piece.X;
/** Track winner (when game is over) */
private Piece winner = null;
/** Number in-a-row required for win */
private int goal = 3;
/** Construct rules object to govern given board.
*
* @param b board to track
*/
public Rules(Board b) {
board = b;
}
// Override default goal of 3.
public Rules(Board b, int goal) {
this.board = b;
this.goal = goal;
}
/** Determine if game is over.
*
* @return true if game is over
*/
public boolean gameOver() {
// Check all possible starting points
for (int r = 0; r < board.getRows(); r++) {
for (int c = 0; c < board.getCols(); c++) {
// Check all possible directions
if (check(r, c, 1, 0) // Columns
|| check(r, c, 0, 1) // Rows
|| check(r, c, 1, 1) // Diag down-right
|| check(r, c, 1, -1) // Diag down-left
) {
// Track winner
winner = board.get(r, c);
return true;
}
}
}
return false;
}
/** Check starting at position (r, c) and moving in direction
* (deltaR, deltaC), check to see if all squares are occupied
* by same player.
*/
private boolean check(int r, int c, int deltaR, int deltaC) {
// Determine piece to check for
Piece player = board.get(r, c);
if (player == Piece.EMPTY)
return false;
// Count of pieces in-a-row
int count = 1;
// Current location to check
int r1 = r + deltaR;
int c1 = c + deltaC;
while (count < goal) {
// Check for going off board
if (r1 < 0 || r1 >= board.getRows())
return false;
if (c1 < 0 || c1 >= board.getCols())
return false;
// Check for not hitting desired piece type
if (board.get(r1, c1) != player)
return false;
// If we make it past checks, we have another hit
count++;
// And go on to next square
r1 += deltaR;
c1 += deltaC;
}
// If we exit the loop, we've hit the goal
return true;
}
/** Determine if given move is legal.
*
* @param r row location
* @param c column location
* @return true if move is legal
*/
public boolean legal(int r, int c) {
// Check bounds.
if (r < 0 || r >= board.getRows())
return false;
if (c < 0 || c >= board.getCols())
return false;
// Check earlier plays.
if (board.get(r, c) != Piece.EMPTY)
return false;
// Check if square below exists and is empty
if (r + 1 < board.getRows() && board.get(r + 1, c) == Piece.EMPTY)
return false;
return true;
}
/** Determine active player.
*
* @return player who can take the next turn
*/
public Piece getPlayer() {
// Fill in logic to track current player.
return player;
}
/** Return winning player.
*
* @return winning player, or null if game is not over
*/
public Piece getWinner() {
return winner;
}
/** Play a piece at given location.
*
* @param row row location
* @param col column location
* @return true if play was allowed.
*/
public boolean play(int row, int col) {
if (!legal(row, col))
return false;
board.play(getPlayer(), row, col);
nextPlayer();
return true;
}
private void nextPlayer() {
if (player == Piece.X)
player = Piece.O;
else
player = Piece.X;
}
}