JAVA/面向对象学习笔记-系列
JAVA/面向对象学习笔记(1)
JAVA/面向对象学习笔记(2)
JAVA/面向对象学习笔记(3)
QX-AI
GPT-4
QX-AI初始化中...
暂无预设简介,请点击下方生成AI简介按钮。
介绍自己
生成预设简介
推荐相关文章
生成AI简介

所看教程(视频):《浙江大学-翁恺-Java-面向对象程序设计》
作为我自己的复习笔记,也可以当做该视频的同步笔记
上接JAVA/面向对象学习笔记(1)

城堡游戏(可扩展性)

整体思路:用Room先初始化5个房间,Room类中有房间名称、四个方向所连接的房间,currentRoom = outside;设置出生点
进入goRoom方法,匹配用户输入的方向,让nextroom指向下一个房间,然后让currentRoom = outside;并输出房间信息。
大致以此循环

Room.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package castle;

public class Room {
//房间名称
public String description;
//房间四个方向连接的房间
public Room northExit;
public Room southExit;
public Room eastExit;
public Room westExit;


public Room(String description)//初始化房间名
{
this.description = description;
}

public void setExits(Room north, Room east, Room south, Room west)//设置房间的四个方向的连接
{
if (north != null)
northExit = north;
if (east != null)
eastExit = east;
if (south != null)
southExit = south;
if (west != null)
westExit = west;
}

@Override
public String toString() {
return description;//输出房间名
}

}
Game.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package castle;

import java.util.*;

public class Game {
private Room currentRoom;//创建一个Room对象,用于保存当前房间

public Game()//构造函数
{
creatRooms();//创建房间
}

private void creatRooms()//创建一个房间
{
Room outside, lobby,pub,study,bedroom;//创建5种房间

// 制造5种房间
outside = new Room("城堡外");
lobby = new Room("大堂");
pub = new Room("小酒吧");
study = new Room("书房");
bedroom = new Room("卧室");

// 初始化房间的出口
outside.setExits(null,lobby,study,pub);
lobby.setExits(null,null,null,outside);
pub.setExits(null,outside,null,null);
study.setExits(outside,bedroom,null,null);
bedroom.setExits(null,null,null,study);


currentRoom = outside; //从城堡门外开始
}


private void printWelcome()//输出欢迎信息
{
System.out.println();
System.out.println("欢迎来到城堡!");
System.out.println("这是一个超级无聊的游戏。");
System.out.println("如果需要帮助,请输入'help'");
System.out.println();
System.out.println("现在你在:" + currentRoom);
System.out.println("出口有:");
//输出当前房间的出口
if(currentRoom.northExit !=null)
System.out.print("north ");
if(currentRoom.eastExit !=null)
System.out.print("east ");
if(currentRoom.southExit !=null)
System.out.print("south ");
if(currentRoom.westExit !=null)
System.out.print("west ");
System.out.println();
}

// 以下为用户命令

private void printHelp()//帮助菜单
{
System.out.println("迷路了吗?你可以做的命令有:go bye help");
System.out.println("如:\tgo east");
}

private void goRoom(String direction)
{
Room nextRoom = null;//创建一个Room对象,用于保存下一个房间
// 在当前房间的出口中查找与用户输入的方向相同的房间
if(direction.equals("north")){
nextRoom = currentRoom.northExit;
}
if(direction.equals("east")){
nextRoom = currentRoom.eastExit;
}
if(direction.equals("south")){
nextRoom = currentRoom.southExit;
}
if(direction.equals("west")){
nextRoom = currentRoom.westExit;
}
// 如果找到了下一个房间,则进入下一个房间
if(nextRoom == null){
System.out.println("那里没有门!");
}
else{
currentRoom = nextRoom;//让当前房间等于下一个房间
// 输出当前房间的描述
System.out.println("你在"+ currentRoom);
System.out.println("出口有:");
if(currentRoom.northExit != null)
System.out.println("north");
if(currentRoom.eastExit != null)
System.out.println("east");
if(currentRoom.southExit != null)
System.out.println("south");
if(currentRoom.westExit != null)
System.out.println("west");
System.out.println();
}
}

public static void main(String[] args) {
Scanner in = new Scanner(System.in);
//通过new Scanner(System.in)创建一个Scanner,控制台会一直等待输入,直到敲回车键结束,把所输入的内容传给Scanner,作为扫描对象。
Game game = new Game();
game.printWelcome();//输出欢迎信息

while (true)//进入游戏主体,游戏结束时退出循环
{
String line = in.nextLine();//控制台输入一行
String[] words = line.split(" ");//以空格为分隔符,把用户输入的字符串分割成字符串数组
//判断是go还是help
if ( words[0].equals("help")){
game.printHelp();//输出帮助信息
}else if ( words[0].equals("go")){
game.goRoom(words[1]);//将用户输入的方向传给goRoom方法
}else if ( words[0].equals("bye")){
break;//如果用户数据为bye,则退出游戏
}
}
System.out.println("感谢您的光临。再见!");
in.close();//关闭Scanner
}
}
输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
欢迎来到城堡!
这是一个超级无聊的游戏。
如果需要帮助,请输入'help'

你在城堡外
出口有:
east
south
west

go south
你在书房
出口有:
north
east

go east
你在卧室
出口有:
west

help
迷路了吗?你可以做的命令有:go bye help
如: go east
bye
感谢您的光临。再见!

消除代码复制

在printWelcome和goRoom方法中都有一段相同的输出出口方向的代码
将这段代码提取出来,做成一个方法,在需要输出房间信息的地方调用即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void showPrompt()
{
// 输出当前房间的描述
System.out.println("你在"+ currentRoom);
System.out.println("出口有:");
if(currentRoom.northExit != null)
System.out.println("north");
if(currentRoom.eastExit != null)
System.out.println("east");
if(currentRoom.southExit != null)
System.out.println("south");
if(currentRoom.westExit != null)
System.out.println("west");
System.out.println();
}

封装

这个程序没有bug,能正常运行,但不见得是一个好的代码
评价一个代码质量的好坏是多元的,尤其是这个代码是否能适应将来的需要

可扩展性:代码是否易于将来增加新的东西

我们想在这个游戏里给每个房间增加两个方向down和up
我们需要改的地方很多
Room.java里需要增加两个房间对象public Room downExit;和public Room upExit; setExits方法需要增加两个参数并进行判断
Game.java里很多方法也要增加if判断,还要改每个房间的初始化

总之,想增加一个方向,代码几乎每个地方都要改变

要想增加可扩展性,首先要降低类和类之间的耦合

用封装来降低耦合

Room类和Game类都有大量的代码和出口相关
尤其是Game类中大量使用了Room类的成员变量
类和类之间的关系称作耦合
耦合越低越好,保持距离是形成良好代码的关键

我们可以让Room自己告诉Game有哪些出口,出口连接的房间

Room.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package castle;

public class Room {
//房间名称
private String description;
//public String description;
//房间四个方向连接的房间
private Room northExit;
private Room southExit;
private Room eastExit;
private Room westExit;
/*public Room northExit;
public Room southExit;
public Room eastExit;
public Room westExit;*/


public Room(String description)//初始化房间名
{
this.description = description;
}

public void setExits(Room north, Room east, Room south, Room west)//设置房间的四个方向的连接
{
if (north != null)
northExit = north;
if (east != null)
eastExit = east;
if (south != null)
southExit = south;
if (west != null)
westExit = west;
}

@Override
public String toString() {
return description;//输出房间名
}

public String getExitDesc() {
//返回一个字符串,来表达房间的出口
/*String returnString = "Exits:";
if (northExit != null)
returnString += "north ";
if (eastExit != null)
returnString += "east ";
if (southExit != null)
returnString += "south ";
if (westExit != null)
returnString += "west ";
return returnString;*/
//一般我们不使用String去做拼接,因为每次加都会产生一个新的String类型的对象,系统开销会很大,而是使用StringBuilder
StringBuilder builder = new StringBuilder("出口有:");
if (northExit != null)
builder.append("north ");
if (eastExit != null)
builder.append("east ");
if (southExit != null)
builder.append("south ");
if (westExit != null)
builder.append("west ");
return builder.toString();
}

public Room getExit(String direction) {
//返回指定方向的连接房间
if (direction.equals("north"))
return northExit;
if (direction.equals("east"))
return eastExit;
if (direction.equals("south"))
return southExit;
if (direction.equals("west"))
return westExit;
return null;
}
}

Game.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package castle;

import java.util.*;

public class Game {
private Room currentRoom;//创建一个Room对象,用于保存当前房间

public Game()//构造函数
{
creatRooms();//创建房间
}

private void creatRooms()//创建一个房间
{
Room outside, lobby,pub,study,bedroom;//创建5种房间

// 制造5种房间
outside = new Room("城堡外");
lobby = new Room("大堂");
pub = new Room("小酒吧");
study = new Room("书房");
bedroom = new Room("卧室");

// 初始化房间的出口
outside.setExits(null,lobby,study,pub);
lobby.setExits(null,null,null,outside);
pub.setExits(null,outside,null,null);
study.setExits(outside,bedroom,null,null);
bedroom.setExits(null,null,null,study);


currentRoom = outside; //从城堡门外开始
}


private void printWelcome()//输出欢迎信息
{
System.out.println();
System.out.println("欢迎来到城堡!");
System.out.println("这是一个超级无聊的游戏。");
System.out.println("如果需要帮助,请输入'help'");
System.out.println();
showPrompt();
/*System.out.println("现在你在:" + currentRoom);
System.out.println("出口有:");
//输出当前房间的出口
if(currentRoom.northExit !=null)
System.out.print("north ");
if(currentRoom.eastExit !=null)
System.out.print("east ");
if(currentRoom.southExit !=null)
System.out.print("south ");
if(currentRoom.westExit !=null)
System.out.print("west ");
System.out.println();*/
}

// 以下为用户命令

private void printHelp()//帮助菜单
{
System.out.println("迷路了吗?你可以做的命令有:go bye help");
System.out.println("如:\tgo east");
}

private void goRoom(String direction)
{
Room nextRoom = currentRoom.getExit(direction);//创建一个Room对象,用于保存下一个房间
// 在当前房间的出口中查找与用户输入的方向相同的房间
/*if(direction.equals("north")){
nextRoom = currentRoom.northExit;
}
if(direction.equals("east")){
nextRoom = currentRoom.eastExit;
}
if(direction.equals("south")){
nextRoom = currentRoom.southExit;
}
if(direction.equals("west")){
nextRoom = currentRoom.westExit;
}*/
// 如果找到了下一个房间,则进入下一个房间
if(nextRoom == null){
System.out.println("那里没有门!");
}
else{
currentRoom = nextRoom;//让当前房间等于下一个房间
// 输出当前房间的描述
showPrompt();
/*System.out.println("你在"+ currentRoom);
System.out.println("出口有:");
if(currentRoom.northExit != null)
System.out.println("north");
if(currentRoom.eastExit != null)
System.out.println("east");
if(currentRoom.southExit != null)
System.out.println("south");
if(currentRoom.westExit != null)
System.out.println("west");
System.out.println();*/
}
}

public void showPrompt()
{
// 输出当前房间的描述
System.out.println("你在"+ currentRoom);
//调用房间的getExitString()方法,输出当前房间的出口
System.out.println(currentRoom.getExitDesc());
/*System.out.println("出口有:");
if(currentRoom.northExit != null)
System.out.println("north");
if(currentRoom.eastExit != null)
System.out.println("east");
if(currentRoom.southExit != null)
System.out.println("south");
if(currentRoom.westExit != null)
System.out.println("west");
System.out.println();*/
}

public static void main(String[] args) {
Scanner in = new Scanner(System.in);
//通过new Scanner(System.in)创建一个Scanner,控制台会一直等待输入,直到敲回车键结束,把所输入的内容传给Scanner,作为扫描对象。
Game game = new Game();
game.printWelcome();//输出欢迎信息

while (true)//进入游戏主体,游戏结束时退出循环
{
String line = in.nextLine();//控制台输入一行
String[] words = line.split(" ");//以空格为分隔符,把用户输入的字符串分割成字符串数组
//判断是go还是help
if ( words[0].equals("help")){
game.printHelp();//输出帮助信息
}else if ( words[0].equals("go")){
game.goRoom(words[1]);//将用户输入的方向传给goRoom方法
}else if ( words[0].equals("bye")){
break;//如果用户数据为bye,则退出游戏
}
}
System.out.println("感谢您的光临。再见!");
in.close();//关闭Scanner
}
}

用接口来实现聚合

原本Room有什么出口,连接着什么房间,Game是能直接获取使用的
现在我们在Room中实现了两个接口getExitDesc和getExit,把方向的细节彻底隐藏在Room类内部
今后方向如何实现就和外部无关了

用容器来实现灵活性

在Room当中,有四个Room对象,用于保存这个房间连接着的四个房间(null,或者房间名)
这种一一对应的关系,可以使用HashMap去实现

Room.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package castle;

import java.util.HashMap;

public class Room {
//描述
private String description;
//用HashMap来保存这个房间连接着的四个房间
private HashMap<String, Room> exits = new HashMap<String, Room>();
/*private Room northExit;
private Room southExit;
private Room eastExit;
private Room westExit;*/

public Room(String description)//初始化房间名
{
this.description = description;
}

public void setExits(String direction, Room room) {
exits.put(direction, room);//将出口方向和连接的房间放进容器中
}

/*public void setExits(Room north, Room east, Room south, Room west)//设置房间的四个方向的连接
{
if (north != null)
northExit = north;
if (east != null)
eastExit = east;
if (south != null)
southExit = south;
if (west != null)
westExit = west;
}*/

@Override
public String toString() {
return description;//输出房间名
}

public String getExitDesc() {
//返回一个字符串,来表达房间的出口
//一般我们不使用String去做拼接,因为每次加都会产生一个新的String类型的对象,系统开销会很大,而是使用StringBuilder
StringBuilder builder = new StringBuilder("出口有:");
for (String direction : exits.keySet()) {
builder.append(direction);
builder.append(" ");
}
/*if (northExit != null)
builder.append("north ");
if (eastExit != null)
builder.append("east ");
if (southExit != null)
builder.append("south ");
if (westExit != null)
builder.append("west ");*/
return builder.toString();
}

public Room getExit(String direction) {
//返回指定方向的连接房间
return exits.get(direction);//直接从HashMap中获取房间,如果没有容器会自动返回null
/*if (direction.equals("north"))
return northExit;
if (direction.equals("east"))
return eastExit;
if (direction.equals("south"))
return southExit;
if (direction.equals("west"))
return westExit;
return null;*/
}
}

Game.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
package castle;

import java.util.*;

public class Game {
private Room currentRoom;//创建一个Room对象,用于保存当前房间

public Game()//构造函数
{
creatRooms();//创建房间
}

private void creatRooms()//创建一个房间
{
Room outside, lobby,pub,study,bedroom;//创建5种房间

// 制造5种房间
outside = new Room("城堡外");
lobby = new Room("大堂");
pub = new Room("小酒吧");
study = new Room("书房");
bedroom = new Room("卧室");

// 初始化房间的出口
outside.setExits("east", lobby);
outside.setExits("south", study);
outside.setExits("west", pub);
lobby.setExits("west", outside);
pub.setExits("east", outside);
study.setExits("north", outside);
study.setExits("east", bedroom);
bedroom.setExits("west", study);
//现在当我们想增加出口方向,很简单
lobby.setExits("up", pub);
pub.setExits("down", lobby);

/*outside.setExits(null,lobby,study,pub);
lobby.setExits(null,null,null,outside);
pub.setExits(null,outside,null,null);
study.setExits(outside,bedroom,null,null);
bedroom.setExits(null,null,null,study);*/

currentRoom = outside; //从城堡门外开始
}


private void printWelcome()//输出欢迎信息
{
System.out.println();
System.out.println("欢迎来到城堡!");
System.out.println("这是一个超级无聊的游戏。");
System.out.println("如果需要帮助,请输入'help'");
System.out.println();
showPrompt();
}

// 以下为用户命令

private void printHelp()//帮助菜单
{
System.out.println("迷路了吗?你可以做的命令有:go bye help");
System.out.println("如:\tgo east");
}

private void goRoom(String direction)
{
Room nextRoom = currentRoom.getExit(direction);//创建一个Room对象,用于保存下一个房间
// 如果找到了下一个房间,则进入下一个房间
if(nextRoom == null){
System.out.println("那里没有门!");
}
else{
currentRoom = nextRoom;//让当前房间等于下一个房间
// 输出当前房间的描述
showPrompt();
}
}

public void showPrompt()
{
// 输出当前房间的描述
System.out.println("你在"+ currentRoom);
//调用房间的getExitString()方法,输出当前房间的出口
System.out.println(currentRoom.getExitDesc());
}

public static void main(String[] args) {
Scanner in = new Scanner(System.in);
//通过new Scanner(System.in)创建一个Scanner,控制台会一直等待输入,直到敲回车键结束,把所输入的内容传给Scanner,作为扫描对象。
Game game = new Game();
game.printWelcome();//输出欢迎信息

while (true)//进入游戏主体,游戏结束时退出循环
{
String line = in.nextLine();//控制台输入一行
String[] words = line.split(" ");//以空格为分隔符,把用户输入的字符串分割成字符串数组
//判断是go还是help
if ( words[0].equals("help")){
game.printHelp();//输出帮助信息
}else if ( words[0].equals("go")){
game.goRoom(words[1]);//将用户输入的方向传给goRoom方法
}else if ( words[0].equals("bye")){
break;//如果用户数据为bye,则退出游戏
}
}
System.out.println("感谢您的光临。再见!");
in.close();//关闭Scanner
}
}

//现在当我们想增加出口方向,很简单
lobby.setExits(“up”, pub);
pub.setExits(“down”, lobby);

这里发生了什么?
将lobby的出口方向和对应房间传给它的setExits的方法后
lobby里面的容器就会多一对出口和房间的对应关系
lobby里面的getExitDesc、getExit方法以及未来可能加入的新方法,都遍历这个容器
所以,增加出口和连接的房间,只需要调用setExits方法,去往lobby里的容器写入东西即可

现在,对于增加出口来说,已经具有了可扩展性

以框架+数据来提高可扩展性

我们原来用硬编码去保存room的出口
现在我们用容器,HashMap和对应的方法(接口方法)组成了一个框架,数据就是放在HashMap里的东西
在这个框架中要增加出口很容易

启发:
命令的解析是否可以脱离if-else
定义一个Handler来处理命令
用Hash表来保存命令和Handler之间的关系

现在我们可以用相同的思路去解决用户命令(help,go,bye)的硬编码问题
一个字符串对应调用一个方法,这显然也是一一对应的关系
但容器只能放对象,一个方法不能放进容器中

如何把方法放进容器中

创建一个Handler类,然后把每个命令创建为Handler的子类
将命令字符串和Handler子类的对象一一对应放入HashMap中
将功能在Handler的子类的doCmd方法中实现
在game中通过父类对象管理者去管理子类对象(从HashMap中获取子类对象),管理者调用doCmd方法即可

Room.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package castle;

import java.util.HashMap;

public class Room {
//描述
private String description;
//用HashMap来保存这个房间连接着的四个房间
private HashMap<String, Room> exits = new HashMap<String, Room>();

public Room(String description)//初始化房间名
{
this.description = description;
}

public void setExits(String direction, Room room) {
exits.put(direction, room);//将出口方向和连接的房间放进容器中
}

@Override
public String toString() {
return description;//输出房间名
}

public String getExitDesc() {
//返回一个字符串,来表达房间的出口
//一般我们不使用String去做拼接,因为每次加都会产生一个新的String类型的对象,系统开销会很大,而是使用StringBuilder
StringBuilder builder = new StringBuilder("出口有:");
for (String direction : exits.keySet()) {
builder.append(direction);
builder.append(" ");
}
return builder.toString();
}

public Room getExit(String direction) {
//返回指定方向的连接房间
return exits.get(direction);//直接从HashMap中获取房间,如果没有容器会自动返回null
}
}
Game.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package castle;

import java.util.*;

public class Game {
private Room currentRoom;//创建一个Room对象,用于保存当前房间
//创建一个HashMap对象,用于保存所有的命令处理器
private HashMap<String, Handler> handlers = new HashMap<String, Handler>();

public Game()//构造函数
{
//将字符串和命令处理器对应
handlers.put("go",new HandlerGo(this));//把game对象作为参数传入HandlerGo类,让HandlerGo能管理game
handlers.put("help",new HandlerHelp(this));
handlers.put("bye",new HandlerBye(this));
creatRooms();//创建房间
}

private void creatRooms()//创建一个房间
{
Room outside, lobby,pub,study,bedroom;//创建5种房间

// 制造5种房间
outside = new Room("城堡外");
lobby = new Room("大堂");
pub = new Room("小酒吧");
study = new Room("书房");
bedroom = new Room("卧室");

// 初始化房间的出口
outside.setExits("east", lobby);
outside.setExits("south", study);
outside.setExits("west", pub);
lobby.setExits("west", outside);
pub.setExits("east", outside);
study.setExits("north", outside);
study.setExits("east", bedroom);
bedroom.setExits("west", study);
//现在当我们想增加出口方向,很简单
lobby.setExits("up", pub);
pub.setExits("down", lobby);

currentRoom = outside; //从城堡门外开始
}


private void printWelcome()//输出欢迎信息
{
System.out.println();
System.out.println("欢迎来到城堡!");
System.out.println("这是一个超级无聊的游戏。");
System.out.println("如果需要帮助,请输入'help'");
System.out.println();
showPrompt();
}

// 以下为用户命令

/*private void printHelp()//帮助菜单
{
System.out.println("迷路了吗?你可以做的命令有:go bye help");
System.out.println("如:\tgo east");
}*/

public void goRoom(String direction)
{
Room nextRoom = currentRoom.getExit(direction);//创建一个Room对象,用于保存下一个房间
// 如果找到了下一个房间,则进入下一个房间
if(nextRoom == null){
System.out.println("那里没有门!");
}
else{
currentRoom = nextRoom;//让当前房间等于下一个房间
// 输出当前房间的描述
showPrompt();
}
}

public void showPrompt()
{
// 输出当前房间的描述
System.out.println("你在"+ currentRoom);
//调用房间的getExitString()方法,输出当前房间的出口
System.out.println(currentRoom.getExitDesc());
}

public void play(){//游戏开始
Scanner in = new Scanner(System.in);
while (true)//进入游戏主体,游戏结束时退出循环
{
String line = in.nextLine();//控制台输入一行
String[] words = line.split(" ");//以空格为分隔符,把用户输入的字符串分割成字符串数组
Handler handler = handlers.get(words[0]);//获取用户输入的命令,从HashMap中获取对应的子类对象,让handler管理这个对象
String value = "";//保存命令的字符串
if(words.length > 1)//判断是不是bye命令
{
value = words[1];//获取参数
}
if(handler != null){//如果命令存在
handler.doCmd(value);//调用命令处理器
if ( handler.isBye() ) {//如果命令处理器返回true,则退出游戏
System.out.println("感谢您的光临。再见!");
break;
}
}
}
in.close();//关闭Scanner
}

public static void main(String[] args) {
/*Scanner in = new Scanner(System.in);*/
//通过new Scanner(System.in)创建一个Scanner,控制台会一直等待输入,直到敲回车键结束,把所输入的内容传给Scanner,作为扫描对象。
Game game = new Game();
game.printWelcome();//输出欢迎信息
game.play();
/*while (true)//进入游戏主体,游戏结束时退出循环
{
String line = in.nextLine();//控制台输入一行
String[] words = line.split(" ");//以空格为分隔符,把用户输入的字符串分割成字符串数组
//判断是go还是help
if ( words[0].equals("help")){
game.printHelp();//输出帮助信息
}else if ( words[0].equals("go")){
game.goRoom(words[1]);//将用户输入的方向传给goRoom方法
}else if ( words[0].equals("bye")){
break;//如果用户数据为bye,则退出游戏
}
}*/
//System.out.println("感谢您的光临。再见!");
//in.close();//关闭Scanner
}
}
Handler.java
1
2
3
4
5
6
7
8
9
10
11
package castle;

public class Handler {
protected Game game;//创建一个Game对象的管理者,为了方便设为protected
public Handler(Game game) {
this.game = game;//两个game管理者管理同一个Game对象
}

public void doCmd(String word) {}//一个接口,用于运行命令
public boolean isBye() { return false; }
}
HandlerBye.java
1
2
3
4
5
6
7
8
9
10
11
12
package castle;

public class HandlerBye extends Handler {
//必要的构造器
public HandlerBye(Game game) {
super(game);
}
@Override
public boolean isBye() {
return true;
}
}
HandlerHelp.java
1
2
3
4
5
6
7
8
9
10
11
12
13
package castle;

public class HandlerHelp extends Handler {
//必要的构造器
public HandlerHelp(Game game) {
super(game);
}
@Override
public void doCmd(String word) {
System.out.println("迷路了吗?你可以做的命令有:go bye help");
System.out.println("如:\tgo east");
}
}
HandlerGo.java
1
2
3
4
5
6
7
8
9
10
11
12
package castle;

public class HandlerGo extends Handler {
public HandlerGo(Game game) {
super(game);//现在HandlerGo能管理game对象了
}
//help和bye都与game无关,但go需要调用goRoom方法,所以需要拿到game对象的管理者
public void doCmd(String word) {
game.goRoom(word);//调用game的goRoom方法
}

}

HandlerGo在未来有更好的方法去实现,现在还是用管理者吧

如果要加入新的命令,也非常简单
创建一个新的Handler子类,在里面实现命令的功能
在Game的构造器中将命令字符串和Handler的子类对象放进去

现在这个城堡游戏已经有极高的可扩展性了

这个城堡游戏例子非常全面,偶尔回来看看这个例子,是个不错的选择


抽象abstract

关键字:abstract

1
2
3
public abstract class Shape {//抽象类
public abstract void draw(Graphics g);//抽象方法不带{},即不带方法体
}

抽象方法:表达概念,但无法实现具体功能(代码)的方法
抽象类:表达概念而无法构造出实体(对象)的类

有抽象方法的类一定是抽象类
抽象类不能制造对象,但是可以定义类变量(任何继承了抽象类的非抽象类的对象可以使用赋给这个类变量,即任何抽象类的子类的对象都可以由这个类变量来管理)
抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。

继承自抽象类的子类必须覆盖父类中的抽象方法,否则自己成为抽象类(即子类必须实现抽象父类的抽象方法)

两种抽象

与具体相对
·表示一种概念而非实体
与细节相对
·表示在一定程度上忽略细节而着眼大局


细胞自动机

死亡:如果活着的邻居的数量小于2或大于3,则死亡
新生:如果正好有3个邻居活着,则新生其他情况则保持原状

Cell.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package cell;

import java.awt.Graphics;

public class Cell {
private boolean alive = false;//默认为死亡状态

public void die() { alive = false; }//让细胞死亡
public void reborn() { alive = true; }//让细胞重生
public boolean isAlive() { return alive; }//返回细胞状态,判断细胞是否存活
//绘制细胞
public void draw(Graphics g, int x, int y, int size) {
g.drawRect(x, y, size, size);//画个空心矩形
if ( alive ) {//如果细胞存活
g.fillRect(x, y, size, size);//画个实心矩形
}
}
}

CellMachine.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package cellmachine;

import javax.swing.JFrame;

import cell.Cell;
import field.Field;
import field.View;

public class CellMachine {

public static void main(String[] args) {
//数据准备阶段,画出一个细胞网格
Field field = new Field(30,30);//一个30*30的网格
//遍历网格,每个网格都有一个细胞
for ( int row = 0; row<field.getHeight(); row++ ) {//遍历每一行
for ( int col = 0; col<field.getWidth(); col++ ) {//遍历每一列
field.place(row, col, new Cell());//每个细胞都有一个状态,初始状态为死亡
}
}

//遍历每个细胞,设置初始细胞的生存状态
for ( int row = 0; row<field.getHeight(); row++ ) {//遍历每一行
for ( int col = 0; col<field.getWidth(); col++ ) {//遍历每一列
Cell cell = field.get(row, col);//获取(row,col)位置上的细胞
//利用随机数让整个网格的五分之一的细胞生存
if ( Math.random() < 0.2 ) { //Math.random()返回0~1之间的随机数
cell.reborn();//reborn()方法设置细胞的生存状态为true
}
}
}

//在窗口中显示细胞网格
View view = new View(field);//将网格传入View类中
JFrame frame = new JFrame();//创建一个JFrame对象,是java当中的窗口,用来显示细胞网格
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设置窗口关闭时的操作
frame.setResizable(false);//设置窗口不可改变大小
frame.setTitle("Cells");//设置窗口的标题
frame.add(view);//将网格添加到窗口中
frame.pack();//设置窗口的大小
frame.setVisible(true);//设置窗口可见,即显示出来

//模拟细胞网格的运行
//遍历整个网格,取出每个细胞
for ( int i=0; i<1000; i++ ) {//进行1000次模拟
for ( int row = 0; row<field.getHeight(); row++ ) {//遍历每一行
for ( int col = 0; col<field.getWidth(); col++ ) {//遍历每一列
Cell cell = field.get(row, col);//获取(row,col)位置上的细胞
Cell[] neighbour = field.getNeighbour(row, col);//获取(row,col)位置上的细胞的邻居
int numOfLive = 0;//计数器,记录邻居中活细胞的个数
//遍历邻居,计算邻居中有多少个细胞是活的
for ( Cell c : neighbour ) {
if ( c.isAlive() ) {//如果邻居中有一个细胞是活的
numOfLive++;//让计数器加一
}
}
//控制台输出当前细胞的状态
System.out.print("["+row+"]["+col+"]:");//打印细胞的位置
System.out.print(cell.isAlive()?"live":"dead");//打印细胞的状态
System.out.print(":"+numOfLive+"-->");//打印细胞的邻居中有多少个细胞是活的
//写入下一次的细胞状态
if ( cell.isAlive() ) {//如果当前细胞是活的
if ( numOfLive <2 || numOfLive >3 ) {//判断是否满足让细胞死的条件条件
cell.die();//让细胞死
System.out.print("die");
}
} else if ( numOfLive == 3 ) {//如果当前细胞是死的,判断是否满足让细胞活的条件条件
cell.reborn();//让细胞活
System.out.print("reborn");
}
System.out.println();
}
}
System.out.println("UPDATE");
frame.repaint();//整个Field都更新好后,画出棋盘
//延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}


Field.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package field;

import java.util.ArrayList;

import cell.Cell;

public class Field {
private int width;
private int height;
private Cell[][] field;//一个二维数组field,存放每个格子(细胞)
//构造器,初始化棋盘
public Field(int width, int height) {
this.width = width;
this.height = height;
field = new Cell[height][width];
}
public int getWidth() { return width; }
public int getHeight() { return height; }
//添加细胞
public Cell place(int row, int col, Cell o) {
Cell ret = field[row][col];
field[row][col] = o;
return ret;
}
//获取某个格子
public Cell get(int row, int col) {
return field[row][col];
}
//获取周围细胞
public Cell[] getNeighbour(int row, int col) {
ArrayList<Cell> list = new ArrayList<Cell>();
for ( int i=-1; i<2; i++ ) {
for ( int j=-1; j<2; j++ ) {
int r = row+i;
int c = col+j;
if ( r >-1 && r<height && c>-1 && c<width && !(r== row && c == col) ) {
list.add(field[r][c]);
}
}
}
//toArray会自动把一个Cell[]数组装好,让返回这个数组
return list.toArray(new Cell[list.size()]);
}
//清空所有格子
public void clear() {
for ( int i=0; i<height; i++ ) {
for ( int j=0; j<width; j++ ) {
field[i][j] = null;
}
}
}
}


View.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package field;

import java.awt.Dimension;
import java.awt.Graphics;

import javax.swing.JFrame;
import javax.swing.JPanel;

import cell.Cell;

public class View extends JPanel {
private static final long serialVersionUID = -5258995676212660595L;
private static final int GRID_SIZE = 16;
private Field theField;

//构造器
public View(Field field) {
theField = field;
}

//每次当窗口被重绘时调用,被调用时会得到一个Graphics对象,即当前要画的对象
@Override
public void paint(Graphics g) {
super.paint(g);//调用父类的paint方法
//绘制网格
for ( int row = 0; row<theField.getHeight(); row++ ) {//行
for ( int col = 0; col<theField.getWidth(); col++ ) {//列
Cell cell = theField.get(row, col);//得到每个格子的对象
if ( cell != null ) {//如果该格子有对象
cell.draw(g, col*GRID_SIZE, row*GRID_SIZE, GRID_SIZE);//绘制该格子
}
}
}
}

@Override
public Dimension getPreferredSize() {
return new Dimension(theField.getWidth()*GRID_SIZE+1, theField.getHeight()*GRID_SIZE+1);
}

public static void main(String[] args) {
Field field = new Field(10,10);
for ( int row = 0; row<field.getHeight(); row++ ) {
for ( int col = 0; col<field.getWidth(); col++ ) {
field.place(row, col, new Cell());
}
}
View view = new View(field);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setTitle("Cells");
frame.add(view);
frame.pack();
frame.setVisible(true);
}

}

Cell、Field、View的关系

Field只需要管好数据以及提供数据
View只管拿到数据之后按数据把整个网格都重新画一遍
而Cell只管自己应该画空心还是实心,Field要就提供给它

不去精心设计哪个局部需要更新,需要更新就整个重画
这样简化了程序逻辑,是在计算机运算速度提高的基础上实现的

数据与表现分离

程序的业务逻辑与表现无关
-表现可以是图形的也可以是文本的
-表现可以是当地的也可以是远程的

需要不同表现,那就用不同表现的代码去取数据,然后表现出来

责任驱动的设计

将程序要实现的功能分配到合适的类/对象中去是设计中非常重要的一环

将功能拆分成很多个部分,每个部分只做自己擅长做的、简单的事

网格化

图形界面本身有更高的解析度,但是将画面网格化以后,数据就更容易处理了

无需关心x、y轴坐标,只需知道要操作的对象在第几行第几列


狐狸和兔子

细胞自动机是细胞在一个网格上,每个细胞都有两种状态

现在要模拟一个有狐狸和兔子的农场:
·狐狸和兔子都有年龄,且有规律增加
·当年龄到了一定的上限就会自然死亡
·狐狸可以随机决定在周围的兔子中吃一个,吃了后年龄上限会提高
·狐狸和兔子可以随机决定生一个小的,放在旁边的空的格子里
·如果不吃也不生,狐狸和兔子可以随机决定向旁边空的格子移一步

这比只有细胞,细胞只有两种状态要复杂得多

源码

这个没注释,原理和细胞自动机差不多

Cell.java
1
2
3
4
5
6
7
package cell;

import java.awt.Graphics;

public interface Cell {
void draw(Graphics g, int x, int y, int size);
}

Animal.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package animal;

import java.util.ArrayList;
import field.Location;

public abstract class Animal {

private int ageLimit;
private int breedableAge;
private int age;
private boolean isAlive = true;

public Animal(int ageLimit, int breedableAge) {
this.ageLimit = ageLimit;
this.breedableAge = breedableAge;
}

protected int getAge() {
return age;
}

protected double getAgePercent() {
return (double) age / ageLimit;
}

public abstract Animal breed();

public void grow() {
age++;
if (age >= ageLimit) {
die();
}
}

public void die() {
isAlive = false;
}

public boolean isAlive() {
return isAlive;
}

public boolean isBreedable() {
return age >= breedableAge;
}

public Location move(Location[] freeAdj) {
Location ret = null;
if (freeAdj.length > 0 && Math.random() < 0.02) {
ret = freeAdj[(int) (Math.random() * freeAdj.length)];
}
return ret;
}

@Override
public String toString() {
return "" + age + ":" + (isAlive ? "live" : "dead");
}

public Animal feed(ArrayList<Animal> neighbour) {
return null;
}

protected void longerLife(int inc) {
ageLimit += inc;
}
}

Fox.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package animal;

import java.awt.Color;
import java.awt.Graphics;
import java.util.ArrayList;

import cell.Cell;

public class Fox extends Animal implements Cell {

public Fox() {
super(20, 4);
}

@Override
public void draw(Graphics g, int x, int y, int size) {
int alpha = (int) ((1 - getAgePercent()) * 255);
g.setColor(new Color(0, 0, 0, alpha));// (int)((20-getAge())/20.0*255)));
g.fillRect(x, y, size, size);
}

@Override
public Animal breed() {
Animal ret = null;
if (isBreedable() && Math.random() < 0.05) {
ret = new Fox();
}
return ret;
}

@Override
public String toString() {
return "Fox:" + super.toString();
}

@Override
public Animal feed(ArrayList<Animal> neighbour) {
Animal ret = null;
if (Math.random() < 0.2) {
ret = neighbour.get((int) (Math.random() * neighbour.size()));
longerLife(2);
}
return ret;
}
}

Rabbit.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package animal;

import java.awt.Color;
import java.awt.Graphics;
import cell.Cell;

public class Rabbit extends Animal implements Cell {

public Rabbit() {
super(10, 2);
}

@Override
public void draw(Graphics g, int x, int y, int size) {
int alpha = (int) ((1 - getAgePercent()) * 255);
g.setColor(new Color(255, 0, 0, alpha));
g.fillRect(x, y, size, size);
}

@Override
public Animal breed() {
Animal ret = null;
if (isBreedable() && Math.random() < 0.12) {
ret = new Rabbit();
}
return ret;
}

@Override
public String toString() {
return "Rabbit:" + super.toString();
}
}
Field.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package field;

import java.util.ArrayList;
import cell.Cell;

public class Field {

private static final Location[] adjacent = {
new Location(-1, -1), new Location(-1, 0), new Location(-1, 1),
new Location(0, -1), new Location(0, 0), new Location(0, 1),
new Location(1, -1), new Location(1, 0), new Location(1, 1)
};

private int width;
private int height;
private Cell[][] field;

public Field(int width, int height) {
this.width = width;
this.height = height;
field = new Cell[height][width];
}

public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public Cell place(int row, int col, Cell o) {
Cell ret = field[row][col];
field[row][col] = o;
return ret;
}

public Cell get(int row, int col) {
return field[row][col];
}

public Cell[] getNeighbour(int row, int col) {
ArrayList<Cell> list = new ArrayList<Cell>();
for (int i = -1; i < 2; i++) {
for (int j = -1; j < 2; j++) {
int r = row + i;
int c = col + j;
if (r > -1 && r < height && c > -1 && c < width && !(r == row && c == col)) {
list.add(field[r][c]);
}
}
}
return list.toArray(new Cell[list.size()]);
}

public Location[] getFreeNeighbour(int row, int col) {
ArrayList<Location> list = new ArrayList<Location>();
for (Location loc : adjacent) {
int r = row + loc.getRow();
int c = col + loc.getCol();
if (r > -1 && r < height && c > -1 && c < width && field[r][c] == null) {
list.add(new Location(r, c));
}
}
return list.toArray(new Location[list.size()]);
}

public boolean placeRandomAdj(int row, int col, Cell cell) {
boolean ret = false;
Location[] freeAdj = getFreeNeighbour(row, col);
if (freeAdj.length > 0) {
int idx = (int) (Math.random() * freeAdj.length);
field[freeAdj[idx].getRow()][freeAdj[idx].getCol()] = cell;
ret = true;
}
return ret;
}

public Cell remove(int row, int col) {
Cell ret = field[row][col];
field[row][col] = null;
return ret;
}

public void remove(Cell cell) {
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
if (field[row][col] == cell) {
field[row][col] = null;
break;
}
}
}
}

public void clear() {
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
field[i][j] = null;
}
}
}

public void move(int row, int col, Location loc) {
field[loc.getRow()][loc.getCol()] = field[row][col];
remove(row, col);
}
}
Location.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package field;

public class Location {

private int row;
private int col;

public Location(int row, int col) {
this.row = row;
this.col = col;
}

public int getRow() {
return row;
}

public int getCol() {
return col;
}
}
View.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package field;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JPanel;
import cell.Cell;

public class View extends JPanel {
private static final long serialVersionUID = -2417015700213488315L;
private static final int GRID_SIZE = 16;
private Field theField;
public View(Field field) {
theField = field;
}

@Override
public void paint(Graphics g) {
super.paint(g);
g.setColor(Color.GRAY);
for (int row = 0; row < theField.getHeight(); row++) {
g.drawLine(0, row * GRID_SIZE, theField.getWidth() * GRID_SIZE, row * GRID_SIZE);
}
for (int col = 0; col < theField.getWidth(); col++) {
g.drawLine(col * GRID_SIZE, 0, col * GRID_SIZE, theField.getHeight() * GRID_SIZE);
}
for (int row = 0; row < theField.getHeight(); row++) {
for (int col = 0; col < theField.getWidth(); col++) {
Cell cell = theField.get(row, col);
if (cell != null) {
cell.draw(g, col * GRID_SIZE, row * GRID_SIZE, GRID_SIZE);
}
}
}
}

@Override
public Dimension getPreferredSize() {
return new Dimension(theField.getWidth() * GRID_SIZE + 1, theField.getHeight() * GRID_SIZE + 1);
}
}
FoxAndRabbit.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package foxnrabbit;

import java.util.ArrayList;
import javax.swing.JFrame;
import animal.Animal;
import animal.Fox;
import animal.Rabbit;
import cell.Cell;
import field.Field;
import field.Location;
import field.View;

public class FoxAndRabbit {

private Field theField;
private View theView;

public FoxAndRabbit(int size) {
theField = new Field(size, size);
for (int row = 0; row < theField.getHeight(); row++) {
for (int col = 0; col < theField.getWidth(); col++) {
double probability = Math.random();
if (probability < 0.05) {
theField.place(row, col, new Fox());
} else if (probability < 0.15) {
theField.place(row, col, new Rabbit());
}
}
}

theView = new View(theField);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setTitle("Cells");
frame.add(theView);
frame.pack();
frame.setVisible(true);
}

public void start(int steps) {
for (int i = 0; i < steps; i++) {
step();
theView.repaint();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public void step() {
for (int row = 0; row < theField.getHeight(); row++) {
for (int col = 0; col < theField.getWidth(); col++) {
Cell cell = theField.get(row, col);
if (cell != null) {
Animal animal = (Animal) cell;
animal.grow();
if (animal.isAlive()) {
Location loc = animal.move(theField.getFreeNeighbour(row, col));
if (loc != null) {
theField.move(row, col, loc);
}
// eat
// animal.eat(theField);
Cell[] neighbour = theField.getNeighbour(row, col);
ArrayList<Animal> listRabbit = new ArrayList<Animal>();
for (Cell an : neighbour) {
if (an instanceof Rabbit) {
listRabbit.add((Rabbit) an);
}
}
if (!listRabbit.isEmpty()) {
Animal fed = animal.feed(listRabbit);
if (fed != null) {
theField.remove((Cell) fed);
}
}
// breed
Animal baby = animal.breed();
if (baby != null) {
theField.placeRandomAdj(row, col, (Cell) baby);
}
} else {
theField.remove(row, col);
}
}
}
}
}

public static void main(String[] args) {
FoxAndRabbit fab = new FoxAndRabbit(50);
fab.start(500);
}
}

项目结构及分析

在英文中Cell有两种意思,格子、细胞
在刚刚到细胞自动机中,Cell类表达细胞,或者没有细胞的空格子,这很合理

狐狸和兔子类有很多相似的属性和动作,所以它们应该有一个父类
但这个父类不应该是Cell,Cell在这个程序中应该表达有东西或没东西的格子才合理
所以这个父类应该是Animal

现在类之间关系是这样的:

在细胞自动机中我们通过place(r,c,cell)将Cell放进网格中
但现在Fox、Rabbit和Cell没有联系,无法将它们放进网格中
如果按照之前的思路,Fox、Rabbit应该也是Cell的子类,但多继承是不被允许的(除了C++)

如果让Animal从Cell继承,这在语意上是模糊的,动物不应该是一种格子(虽然这样做能实现)

接口

接口是纯抽象类
·所有的成员函数都是抽象函数
·所有的成员变量都是public static final

类表达一个具体的东西,而接口表达一种概念、一种规范
接口规定了长什么样,但是不管里面有什么

我们可以将Cell类改造成接口:

Cell
1
2
3
4
import java.awt.Graphics;
public interface Cell {
void draw(Graphics g, int x, int y, int size);
}

Cell现在的作用:所有实现了Cell这个接口的类,都应该有draw这个方法
在这个程序中,只要实现了这个方法的类的对象,都可以直接放到Field中

为什么Cell接口只要求实现draw方法?
因为将来View只需要拿Cell去draw,所以只需要要求放到Field里的类的对象实现draw方法

现在类之间关系是这样的:

现在Fox、Rabbit的对象都可以放进Field里(Field需要一个Cell,而Fox、Rabbit都实现了Cell)

implements

用关键字implements让类实现接口

1
2
3
public class Fox extends Animal implements Cell{
//Fox是一种Animal,它实现了Cell
}

在Field的place方法中,place需要一个Cell类的对象
Cell本身是一个接口,它本身是抽象的,不可能有对象
所有实现了Cell接口的对象都可以交给Cell对象的管理者
1
2
3
4
5
public Cell place(int row, int col, Cell o) {
Cell ret = field[row][col];
field[row][col] = o;
return ret;
}

当Fox实现Cell后,就必须重写Cell中的方法
把draw方法实际地做出来
1
2
3
4
5
6
@Override
public void draw(Graphics g, int x, int y, int size) {
int alpha = (int) ((1 - getAgePercent()) * 255);
g.setColor(new Color(0, 0, 0, alpha));
g.fillRect(x, y, size, size);
}

类用extends,接口用implements
类可以实现很多接口
接口可以继承接口,但不能继承类
接口不能实现接口

interface

声明一个接口

1
2
3
4
public interface Cell{//接口
}
public class Cell{//类
}

interface是一种特殊的class,它替代掉了class

面向接口的编程方式

在上面的程序中,Field需要一个能draw的类的对象,然后Field提供了一个Cell接口,所有实现了这个接口的东西都可以交给Field,而它不关心这个东西是什么,只需要符合接口即可

设计程序时先定义接口,再实现类
任何需要在函数间传入传出的一定是接口而不是具体的类
是Java成功的关键之一,因为极适合多人同时写一个大程序:每个人只需要用接口去提出要求,其他人根据借口实现具体的类
也是Java被批评的要点之一,因为代码量膨胀起来很快,显得程序十分臃肿

增加一个按钮

在狐狸与兔子程序的图形界面中增加一个按钮,按一下执行一步

在FoxAndRabbit.java中,这么一段代码生成了一个窗口

1
2
3
4
5
6
7
8
theView = new View(theField);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setTitle("Cells");
frame.add(theView);
frame.pack();
frame.setVisible(true);

首先要在窗口中增加一个按钮
1
2
3
4
5
6
7
8
9
10
11
12
theView = new View(theField);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setTitle("Cells");
frame.add(theView);
//有一个类叫JButton,做一个叫btnstep的对象
JButton btnstep =new JButton("单步");
//把这个按钮加到窗口中
frame.add(btnstep);
frame.pack();
frame.setVisible(true);

运行一下

整个窗口只剩下了我们加进去的这个按钮,解决这个问题我们需要了解Swing


下接JAVA/面向对象学习笔记(3)