分析案例“定义Tetris项目中的T类和J类并测试”中的T类和J类,会发现存在大量的重复代码,比如,cells属性,print方法、drop方法、moveLeft方法、moveRight方法,这四个方法在各个类中的实现都是相同的。因此,本案例要求使用继承的方式,构建T类和J类父类Tetromino类,重构T类和J类并测试重构后的代码,
另外,测试时, 需要打印出游戏所在的平面(宽10格,高20格),用“-”号表示平面上的每个单元;然后使用“*”号打印显示方块中的每个格子,如图-1中所示。
图- 1
要实现本案例的功能,解决方案如下:
实现此案例需要按照如下步骤进行。
步骤一:构建Tetromino类
首先,抽取出T类和J类中共有的属性和方法,然后,构建Tetromino类,将这些共有的属性和方法抽取到Tetromino类中。抽取T类和J类中的cells属性,print方法、drop方法、moveLeft方法以及moveRight方法到Tetromino类中,代码如下所示:
public class Tetromino { Cell[] cells;// 属性,用来存储一个方块的四个格子的坐标 /** * 按顺时针方向,打印方块中四个格子所在的坐标 */ public void print() { String str = ""; for (int i = 0; i < cells.length - 1; i++) { str += "(" + cells[i].getCellInfo() + "), "; } str += "(" + cells[cells.length - 1].getCellInfo() + ")"; System.out.println(str); } /** * 使方块下落一个格子 */ public void drop() { for (int i = 0; i < cells.length; i++) { cells[i].row++; } } /** * 使方块左移一个格子 */ public void moveLeft() { for (int i = 0; i < cells.length; i++) { cells[i].col--; } } /** * 使用方块右移一个格子 */ public void moveRight() { for (int i = 0; i < cells.length; i++) { cells[i].col++; } } }
步骤二:定义Tetromino类的构造方法
查看T类和J类带参构造方法中,都对cells数组进行了初始化,因此,对cells数组的初始化也属于两个类的共有部分。所以,定义Tetromino类的无参数构造方法,在构造方法中,初始化cells数组的长度为4,代码如下所示:
public class Tetromino { Cell[] cells;// 属性,用来存储一个方块的四个格子的坐标 #cold_bold /** #cold_bold * 构造方法,初始化cells数组 #cold_bold */ #cold_bold public Tetromino() { #cold_bold cells = new Cell[4]; #cold_bold } /** * 按顺时针方向,打印方块中四个格子所在的坐标 */ public void print() { String str = ""; for (int i = 0; i < cells.length - 1; i++) { str += "(" + cells[i].getCellInfo() + "), "; } str += "(" + cells[cells.length - 1].getCellInfo() + ")"; System.out.println(str); } /** * 使方块下落一个格子 */ public void drop() { for (int i = 0; i < cells.length; i++) { cells[i].row++; } } /** * 使方块左移一个格子 */ public void moveLeft() { for (int i = 0; i < cells.length; i++) { cells[i].col--; } } /** * 使用方块右移一个格子 */ public void moveRight() { for (int i = 0; i < cells.length; i++) { cells[i].col++; } } }
步骤三:重构T类
重构T类为TetrominoT类,在步骤一和步骤二中,我们已经将T类和J类的共有部分抽取出去。再此,重构T类时,只有构造方法的实现不同。构造方法的实现为根据不同的形状给cells数组的每个元素进行赋值。TetrominoT类的代码如下所示:
public class TetrominoT extends Tetromino { public TetrominoT(int row, int col) { super(); // 按顺时针方向初始化Cell cells[0] = new Cell(row, col); cells[1] = new Cell(row, col + 1); cells[2] = new Cell(row, col + 2); cells[3] = new Cell(row + 1, col + 1); } }
上述代码中,使用super关键字调用父类的无参数构造方法。代码“super();”是可以省略不写的。默认情况下,系统在子类构造方法的第一句代码就是“super(); ”即,调用父类无参数的构造方法。
步骤四:重构J类
重构J类为TetrominJ类,重构J类时,和重构T是一样的实现,只有构造方法的实现不同。TetrominoJ类的代码如下所示:
public class TetrominoJ extends Tetromino { public TetrominoJ(int row, int col) { cells[0] = new Cell(row, col); cells[1] = new Cell(row, col + 1); cells[2] = new Cell(row, col + 2); cells[3] = new Cell(row + 1, col + 2); } }
上述代码中,在TetrominoJ类的构造方法的第一句,虽然没有使用super关键字调用父类无参数的构造方法,但是,系统会默认调用父类无参数的构造方法。
步骤五:测试重构后的代码
测试重构后的代码,构建TetrominoGame类。首先,在TetrominoGame类中,创建方法打印出游戏所在的平面(宽10格,高20格),用“-”号表示平面上的每个单元格;然后使用“*”号打印显示方块中的每个格子。TetrominoGame类的代码如下所示:
import java.util.Scanner; public class TetrominoGame { /** * 打印出游戏所在的平面(宽10格,高20格)。用“-”号表示平面上的每个单元,用“*”号打印显示方块中的每个格子 * * @param tetromino 需要显示在游戏平面中的方块 */ public static void printTetromino(Tetromino tetromino) { int totalRow = 20; int totalCol = 10; //获取方块中存储的四个格子的数组 Cell[] cells = tetromino.cells; for (int row = 0; row < totalRow; row++) { for (int col = 0; col < totalCol; col++) { // 用于判断该位置是否包含在cells数组中 boolean isInCells = false; for (int i = 0; i < cells.length; i++) { if (cells[i].row == row && cells[i].col == col) { System.out.print("* "); isInCells = true; break; } } if (!isInCells) { System.out.print("- "); } } System.out.println(); } } }
在TetrominoGame类中,添加main方法,在控制台,打印T型和J型。代码如下所示:
public class TetrominoGame { #cold_bold public static void main(String[] args) { #cold_bold //测试TetrominoT #cold_bold System.out.println("--------打印T型---------"); #cold_bold Tetromino t = new TetrominoT(0, 4); #cold_bold printTetromino(t); #cold_bold #cold_bold //测试TetrominoJ #cold_bold System.out.println("--------打印J型---------"); #cold_bold Tetromino j = new TetrominoJ(0, 4); #cold_bold printTetromino(j); #cold_bold } /** * 打印出游戏所在的平面(宽10格,高20格)。用“-”号表示平面上的每个单元,用“*”号打印显示方块中的每个格子 * * @param tetromino 需要显示在游戏平面中的方块 */ public static void printTetromino(Tetromino tetromino) { int totalRow = 20; int totalCol = 10; //获取方块中存储的四个格子的数组 Cell[] cells = tetromino.cells; for (int row = 0; row < totalRow; row++) { for (int col = 0; col < totalCol; col++) { // 用于判断该位置是否包含在cells数组中 boolean isInCells = false; for (int i = 0; i < cells.length; i++) { if (cells[i].row == row && cells[i].col == col) { System.out.print("* "); isInCells = true; break; } } if (!isInCells) { System.out.print("- "); } } System.out.println(); } } }
控制台输出结果如下所示:
--------打印T型--------- - - - - * * * - - - - - - - - * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --------打印J型--------- - - - - * * * - - - - - - - - - * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
从上述代码和打印结果可以看出,父类的引用是可以指向子类的对象的。构造子类对象时,也构造了父类的对象。
本案例中,Tetromino类的完整代码如下所示:
public class Tetromino { Cell[] cells;// 属性,用来存储一个方块的四个格子的坐标 /** * 构造方法,初始化cells数组 */ public Tetromino() { cells = new Cell[4]; } /** * 按顺时针方向,打印方块中四个格子所在的坐标 */ public void print() { String str = ""; for (int i = 0; i < cells.length - 1; i++) { str += "(" + cells[i].getCellInfo() + "), "; } str += "(" + cells[cells.length - 1].getCellInfo() + ")"; System.out.println(str); } /** * 使方块下落一个格子 */ public void drop() { for (int i = 0; i < cells.length; i++) { cells[i].row++; } } /** * 使方块左移一个格子 */ public void moveLeft() { for (int i = 0; i < cells.length; i++) { cells[i].col--; } } /** * 使用方块右移一个格子 */ public void moveRight() { for (int i = 0; i < cells.length; i++) { cells[i].col++; } } }
TetrominoT类的完整代码如下所示:
public class TetrominoT extends Tetromino { public TetrominoT(int row, int col) { super(); // 按顺时针方向初始化Cell cells[0] = new Cell(row, col); cells[1] = new Cell(row, col + 1); cells[2] = new Cell(row, col + 2); cells[3] = new Cell(row + 1, col + 1); } }
TetrominoJ类的完整代码如下所示:
public class TetrominoJ extends Tetromino { public TetrominoJ(int row, int col) { cells[0] = new Cell(row, col); cells[1] = new Cell(row, col + 1); cells[2] = new Cell(row, col + 2); cells[3] = new Cell(row + 1, col + 2); } }
TetrominoGame类的完整代码如下所示:
public class TetrominoGame { public static void main(String[] args) { //测试TetrominoT System.out.println("--------打印T型---------"); Tetromino t = new TetrominoT(0, 4); printTetromino(t); //测试TetrominoJ System.out.println("--------打印J型---------"); Tetromino j = new TetrominoJ(0, 4); printTetromino(j); } /** * 打印出游戏所在的平面(宽10格,高20格)。用“-”号表示平面上的每个单元,用“*”号打印显示方块中的每个格子 * * @param tetromino 需要显示在游戏平面中的方块 */ public static void printTetromino(Tetromino tetromino) { int totalRow = 20; int totalCol = 10; //获取方块中存储的四个各自的数组 Cell[] cells = tetromino.cells; for (int row = 0; row < totalRow; row++) { for (int col = 0; col < totalCol; col++) { // 用于判断该位置是否包含在cells数组中 boolean isInCells = false; for (int i = 0; i < cells.length; i++) { if (cells[i].row == row && cells[i].col == col) { System.out.print("* "); isInCells = true; break; } } if (!isInCells) { System.out.print("- "); } } System.out.println(); } } }