问题
分析案例“俄罗斯方块项目中的T类和J类”中的T类和J类,会发现存在大量的重复代码,比如,cells属性,print方法、drop方法、moveLeft方法、moveRight方法,这四个方法在各个类中的实现都是相同的。因此,本案例要求使用继承的方式,构建T类和J类父类Tetromino类,重构T类和J类并测试重构后的代码。
另外,测试时,需要打印游戏所在的平面(宽10格,高20格),用"-"号表示平面上的每个单元;然后使用"*"号打印显示方块中的每个格子,如图-1中所示。
方案
要实现本案例的功能,解决方案如下:
1.抽取出,T类和J类中共有的属性和方法,然后,构建Tetromino类,将这些共有的属性和方法抽取到Tetromino类中。
2.重构T类为TetrominoT类。在TetrominoT类中,只保留该类特有的部分,比如,该类的构造中,初始化T型方块部分。
3.重构J类为TetrominoJ类。在TetrominoJ类中,也是只保留该类特有的部分,比如,该类的构造中,初始化J型方块部分。
4.测试重构后的代码,构建TetrominoGame类。首先,在TetrominoGame类中,创建方法打印出游戏所在的平面(宽10格,高20格),用"-"号表示平面上的每个单元;然后使用"*"号打印显示方块中的每个格子。
步骤
实现此案例需要按照如下步骤进行。
步骤一:构建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;//属性,用来存储一个方块的四个格子的坐标
/**
*构造方法,初始化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++;
}
}
}
步骤三:重构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类为TetrominoJ类,重构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{
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();
}
}
}
控制台输出结果如下所示:
--------打印T型----------
- - - - * * * - - -
- - - - - * - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
--------打印J型----------
- - - - * * * - - -
- - - - - - * - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
从上述代码中打印结果可以看出,父类的引用是可以指向子类的对象的。构造子类对象时,也构造了父类的对象。
博主点评:
当重复的代码比较多时,要考虑将公共的抽出来定义在父类里,子类去继承。
扫码关注我吧: