2.应用策略模式和单例模式-设计模式之坦克大战

前面对策略模式和单例模式都进行了理论化的总结,下面是这两种设计模式在坦克大战中的使用。

在应用这两种设计模式之前,代码在这里:

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

你可能感兴趣的:(Java设计模式理解应用,我的练手项目们,Java)