前面对策略模式和单例模式都进行了理论化的总结,下面是这两种设计模式在坦克大战中的使用。
在应用这两种设计模式之前,代码在这里:
https://github.com/phs999/DesignPatterns/tree/e6d14348afa089398fac14dddc5f77315b1e4bb2
其中Tank类中fire()方法,控制了坦克发射炮弹的位置和方式。但这样每个Tank对象的fire()方法都是一样的,见下面的Tank类。但具体程序中可能有不同的需求。比如,敌方坦克没有人控制的话,需要自己随机发射炮弹;我方坦克有人控制,需要在按下特定键时发射炮弹,甚至可以增强向四个方向发射炮弹。以上这些需求都可通过策略模式,以面向接口的方式实现。
package phs999.tank;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.util.Random;
public class Tank {
private int x, y;
private Dir dir = Dir.UP;
private boolean moving = false;//默认坦克不自动移动
private static final int speed = 5;
private static int WIDTH=ResourceMgr.goodTankD.getWidth();
private static int HEIGHT=ResourceMgr.goodTankD.getHeight();
private TankFrame tf=null;
private boolean live=true;
private Group group=Group.BAD;
private Random random=new Random();
Rectangle rect=new Rectangle();
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public static int getWIDTH() {
return WIDTH;
}
public static int getHEIGHT() {
return HEIGHT;
}
public Group getGroup() {
return group;
}
public void setGroup(Group group) {
this.group = group;
}
public boolean isMoving() {
return moving;
}
public void setMoving(boolean moving) {
this.moving = moving;
}
public Dir getDir() {
return dir;
}
public void setDir(Dir dir) {
this.dir = dir;
}
public Tank(int x, int y, Dir dir,Group group,TankFrame tf) {
super();
this.x = x;
this.y = y;
this.dir = dir;
this.group=group;
this.tf=tf;
if (group.equals(Group.BAD)) {
moving=true;
}
rect.x=x;
rect.y=y;
rect.width=WIDTH;
rect.height=HEIGHT;
}
public void paint(Graphics g) {
if (!live&&this.group.equals(Group.BAD)) {
tf.enemyTanks.remove(this);
return;
}
if (group.equals(Group.BAD)) {
switch (dir) {
case LEFT:
g.drawImage(ResourceMgr.badTankL, x, y, null);
break;
case RIGHT:
g.drawImage(ResourceMgr.badTankR, x, y, null);
break;
case UP:
g.drawImage(ResourceMgr.badTankU, x, y, null);
break;
case DOWN:
g.drawImage(ResourceMgr.badTankD, x, y, null);
break;
default:
break;
}
}else {
switch (dir) {
case LEFT:
g.drawImage(ResourceMgr.goodTankL, x, y, null);
break;
case RIGHT:
g.drawImage(ResourceMgr.goodTankR, x, y, null);
break;
case UP:
g.drawImage(ResourceMgr.goodTankU, x, y, null);
break;
case DOWN:
g.drawImage(ResourceMgr.goodTankD, x, y, null);
break;
default:
break;
}
}
move();
}
private void move() {
if (!moving) {
return;
}
randomDir();
switch (dir) {
case LEFT:
x -= speed;
break;
case RIGHT:
x += speed;
break;
case UP:
y -= speed;
break;
case DOWN:
y += speed;
break;
default:
break;
}
if (random.nextInt(100)>95&&this.group.equals(Group.BAD)) {
this.fire();
}
boundsCheck();
rect.x=x;
rect.y=y;
}
private void boundsCheck() {
if (x<0) {
x=0;
}
if (x>TankFrame.GAME_WIDTH-this.WIDTH) {
x=TankFrame.GAME_WIDTH-this.WIDTH;
}
if (y<30) {
y=30;
}
if (y>TankFrame.GAME_HEIGHT-this.HEIGHT) {
y=TankFrame.GAME_HEIGHT-this.HEIGHT;
}
}
private void randomDir() {
if (this.group.equals(Group.BAD)&&random.nextInt(100)>97) {
this.dir=Dir.values()[random.nextInt(4)];
}
}
public void fire() {
int bX = this.x + Tank.WIDTH/2 - Bullet.getWIDTH()/2;
int bY = this.y + Tank.HEIGHT/2 - Bullet.getHEIGHT()/2;
tf.bullets.add(new Bullet(bX, bY, this.dir, this.group, this.tf));
if(this.group == Group.GOOD) {
new Thread(()->new Audio("audio/tank_fire.wav").play()).start();
}
}
public void die() {
this.live=false;
tf.explodes.add(new Explode(x, y, tf));
}
}
为了应用策略模式,我们首先定义了一个FireStrategy接口。
package phs999.tank;
/**
* 坦克开火策略接口,实现该接口的子类具体定义了坦克发射炮弹的策略
* @author phs
*
*/
public interface FireSrategy {
void fire(Tank tank);
}
其次,我们需要的不同场景下的开炮策略可以通过实现该接口达到目的。比如,我定义了默认的开火策略、向四个方向开火的策略以及随机开火策略。需要说明的是,在具体实现这些策略时,我都用了单例模式,保证他们只产生一个实例,这样方便后面将策略对象当做参数传递时,避免重复产生策略对象。具体代码如下:
package phs999.tank;
/**
* 坦克默认的发射炮弹策略
* @author phs
*
*/
public class DefaultFireStrategy implements FireSrategy{
private static FireSrategy fireSrategy= new DefaultFireStrategy();
private DefaultFireStrategy() {
}
public static FireSrategy getFireStrategy() {
return fireSrategy;
}
@Override
public void fire(Tank tank) {
int bX = tank.getX() + tank.getWIDTH()/2 - Bullet.getWIDTH()/2;
int bY = tank.getY() + tank.getHEIGHT()/2 - Bullet.getHEIGHT()/2;
new Bullet(bX, bY, tank.getDir(), tank.getGroup(), tank.getTf());
if(tank.getGroup() == Group.GOOD) {
new Thread(()->new Audio("audio/tank_fire.wav").play()).start();
}
}
}
package phs999.tank;
public class FourDirFireStrategy implements FireSrategy{
private static FireSrategy fireSrategy=new FourDirFireStrategy();
private FourDirFireStrategy() {
}
public static FireSrategy getFireStrategy() {
return fireSrategy;
}
@Override
public void fire(Tank tank) {
int bX = tank.getX() + tank.getWIDTH()/2 - Bullet.getWIDTH()/2;
int bY = tank.getY() + tank.getHEIGHT()/2 - Bullet.getHEIGHT()/2;
Dir[] dirs=Dir.values();
for (int i = 0; i < dirs.length; i++) {
new Bullet(bX, bY, dirs[i], tank.getGroup(), tank.getTf());
}
if(tank.getGroup() == Group.GOOD) {
new Thread(()->new Audio("audio/tank_fire.wav").play()).start();
}
}
}
package phs999.tank;
import java.util.Random;
/**
* 针对Group.BAD的坦克,实现随机发射炮弹
* @author phs
*
*/
public class RandomFireStrategy implements FireSrategy{
private static final FireSrategy randomFireStrategy=new RandomFireStrategy();
private Random random=new Random();
private RandomFireStrategy() {
}
public static FireSrategy getFireStrategy() {
return randomFireStrategy;
}
@Override
public void fire(Tank tank) {
if (random.nextInt(100)>95) {
int bX = tank.getX() + tank.getWIDTH()/2 - Bullet.getWIDTH()/2;
int bY = tank.getY() + tank.getHEIGHT()/2 - Bullet.getHEIGHT()/2;
new Bullet(bX, bY, tank.getDir(), tank.getGroup(), tank.getTf());
if(tank.getGroup() == Group.GOOD) {
new Thread(()->new Audio("audio/tank_fire.wav").play()).start();
}
}
}
}
这个时候再看Tank对象的fire()方法,那就是想怎么开火就怎么开火了。
package phs999.tank;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.util.Random;
public class Tank {
private int x, y;
private Dir dir = Dir.UP;
private boolean moving = false;//默认坦克不自动移动
private static final int speed = 5;
private static int WIDTH=ResourceMgr.goodTankD.getWidth();
private static int HEIGHT=ResourceMgr.goodTankD.getHeight();
private TankFrame tf=null;
private boolean live=true;
private Group group=Group.BAD;
private Random random=new Random();
Rectangle rect=new Rectangle();
FireSrategy fs;
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public static int getWIDTH() {
return WIDTH;
}
public static int getHEIGHT() {
return HEIGHT;
}
public Group getGroup() {
return group;
}
public void setGroup(Group group) {
this.group = group;
}
public boolean isMoving() {
return moving;
}
public void setMoving(boolean moving) {
this.moving = moving;
}
public Dir getDir() {
return dir;
}
public void setDir(Dir dir) {
this.dir = dir;
}
public Tank(int x, int y, Dir dir,Group group,TankFrame tf) {
super();
this.x = x;
this.y = y;
this.dir = dir;
this.group=group;
this.tf=tf;
if (group.equals(Group.BAD)) {
moving=true;
}
rect.x=x;
rect.y=y;
rect.width=WIDTH;
rect.height=HEIGHT;
}
public void paint(Graphics g) {
if (!live&&this.group.equals(Group.BAD)) {
tf.enemyTanks.remove(this);
return;
}
if (group.equals(Group.BAD)) {
switch (dir) {
case LEFT:
g.drawImage(ResourceMgr.badTankL, x, y, null);
break;
case RIGHT:
g.drawImage(ResourceMgr.badTankR, x, y, null);
break;
case UP:
g.drawImage(ResourceMgr.badTankU, x, y, null);
break;
case DOWN:
g.drawImage(ResourceMgr.badTankD, x, y, null);
break;
default:
break;
}
fire(RandomFireStrategy.getFireStrategy());
}else {
switch (dir) {
case LEFT:
g.drawImage(ResourceMgr.goodTankL, x, y, null);
break;
case RIGHT:
g.drawImage(ResourceMgr.goodTankR, x, y, null);
break;
case UP:
g.drawImage(ResourceMgr.goodTankU, x, y, null);
break;
case DOWN:
g.drawImage(ResourceMgr.goodTankD, x, y, null);
break;
default:
break;
}
}
move();
}
private void move() {
if (!moving) {
return;
}
randomDir();
switch (dir) {
case LEFT:
x -= speed;
break;
case RIGHT:
x += speed;
break;
case UP:
y -= speed;
break;
case DOWN:
y += speed;
break;
default:
break;
}
boundsCheck();
rect.x=x;
rect.y=y;
}
void fire(FireSrategy fs) {
fs.fire(this);
}
private void boundsCheck() {
if (x<0) {
x=0;
}
if (x>TankFrame.GAME_WIDTH-this.WIDTH) {
x=TankFrame.GAME_WIDTH-this.WIDTH;
}
if (y<30) {
y=30;
}
if (y>TankFrame.GAME_HEIGHT-this.HEIGHT) {
y=TankFrame.GAME_HEIGHT-this.HEIGHT;
}
}
private void randomDir() {
if (this.group.equals(Group.BAD)&&random.nextInt(100)>97) {
this.dir=Dir.values()[random.nextInt(4)];
}
}
public void die() {
this.live=false;
tf.explodes.add(new Explode(x, y, tf));
}
public TankFrame getTf() {
return this.tf;
}
}
本质就是一个面向接口编程。
详细代码见这里:
https://github.com/phs999/DesignPatterns/tree/cc322c37b6f24e914fa2f9b170d8dceec415a180