使用装饰者模式扩展坦克代码-设计模式之坦克大战

2022-07-31,,,,

目录

一、复习总结装饰者模式

1、实现原理:

2、与继承的区别:

二、坦克大战中应用装饰者模式

1、结构图

2、 实现存在的问题

(1)碰撞检测逻辑出问题

(2)解决方案


一、复习总结装饰者模式

先来复习一下装饰者模式,

https://blog.csdn.net/phs999/article/details/107726853

装饰者模式的核心就是将待修饰类对象传参到装饰类,然后得到一个新的、更加强大、可以支持更多功能、修饰后的类对象

待修饰类对象修饰后的对象 类型相同。

1、实现原理:

(1)装饰类与被装饰类都实现同一个接口或继承同一个抽象类。

(2)装饰类的构造方法必须是有参构造方法,而且参数是要被修饰的对象。

(3)修饰类中实现了对被修饰对象的方法重写和扩展

2、与继承的区别:

(1)修饰类的构造方法是带参的,而且可以实现多个修饰类同时使用。

二、坦克大战中应用装饰者模式

在坦克大战中应用该设计模式,目的是练习,但应用之后会出现各种问题,下面主要讨论一下这些问题,以便实际应用中提前避免。

1、结构图

就像上面总结的装饰者模式的实现原理,坦克大战中一样。此处将游戏对象GameObject作为被修饰的抽象类,所有的游戏对象实体比如Bullet、Wall、Tank等都是他的子类。相应的,装饰类也要继承GameObject,并将GameObject作为构造方法参数。

2、 实现存在的问题

(1)碰撞检测逻辑出问题

碰撞检测的逻辑可以参考之前的文章,使用责任链模式实现:

https://blog.csdn.net/phs999/article/details/107453246

比如子弹与坦克之间的碰撞检测:

package phs999.tank.cor;
 
import phs999.tank.Bullet;
import phs999.tank.GameObject;
import phs999.tank.Tank;
 
public class BulletTankCollider implements Collider {
 
	@Override
	public boolean collide(GameObject o1, GameObject o2) {
		if (o1 instanceof Bullet && o2 instanceof Tank) {
			Bullet b = (Bullet)o1;
			Tank t = (Tank)o2;
			if (b.getGroup().equals(t.getGroup())) {
				return false;
			}
			if (b.getRect().intersects(t.getRect())) {
				t.die();
				b.die();
				return true;
			}
			
		}else if (o1 instanceof Tank && o2 instanceof Bullet) {
			collide(o2,o1);
		}else {
			return false;
		}
		return false;
	}
 
}

也就是必须明确是什么类型的类,是子弹还是坦克还是墙,然后才能进行不同逻辑分支的碰撞检测。

而我们如果对GameObject进行装饰,装饰后也只能得到GameObject,但是无法进行具体类的判断,进而不能满足碰撞检测的逻辑。

首先,是装饰者抽象类:

package phs999.tank.decorator;

import java.awt.Graphics;

import phs999.tank.GameObject;

public abstract class GameObjectDecorator extends GameObject{

	GameObject go;
	
	public GameObjectDecorator(GameObject go) {
		this.go=go;
	}
		
	@Override
	public void paint(Graphics g) {
		go.paint(g);
	}
	@Override
	public int getX() {
		return go.getX();
	}
	@Override
	public int getY() {
		return go.getY();
	}
	
} 

然后是具体的抽象类:

package phs999.tank.decorator;

import java.awt.Color;
import java.awt.Graphics;

import phs999.tank.GameObject;

public class RectDecorator extends GameObjectDecorator{

	public RectDecorator(GameObject go) {
		super(go);
	}

	@Override
	public void paint(Graphics g) {
		super.paint(g);
		Color color=g.getColor();
		g.setColor(Color.YELLOW);
		g.drawRect(getX(), getY(), getWidth()+1, getHeight()+1);
	}
	

	@Override
	public int getWidth() {
		return go.getWidth();
	}

	@Override
	public int getHeight() {
		return go.getHeight();
	}
	
} 

然后,我们在统一管理添加游戏对象的类GameModelFacade中,在初始化墙并添加到对象列表的时候,调用了装饰者类。具体代码在第30行。

package phs999.tank;

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

import phs999.tank.cor.ColliderChain;
import phs999.tank.decorator.RectDecorator;

public class GameModelFacade {
	private static GameModelFacade gm=new GameModelFacade();

	Tank myTank = new Tank(200, 400, Dir.UP,Group.GOOD);
	/*
	 * List<Bullet> bullets =new ArrayList<>(); List<Tank> enemyTanks=new
	 * ArrayList<>(); List<Explode> explodes=new ArrayList<>();
	 */
	private List<GameObject> objects=new ArrayList<GameObject>();
//	Collider collider1=new BulletTankCollider();
//	Collider collider2=new TankTankCollider();
	ColliderChain chain=new ColliderChain();
	
	private GameModelFacade() {
		 int initTankCount=Integer.parseInt((String)PropertyMgr.get("initTankCount"));
		 //初始化敌方坦克
		   for (int i = 0; i < initTankCount; i++) {
			   add(new Tank(40+80*i, 200, Dir.DOWN,Group.BAD));
			}
		   add(myTank);
		   add(new RectDecorator(new Wall(150, 150, 200, 50)));
		   add(new Wall(550, 150, 200, 50));
		   add(new Wall(300, 300, 50, 200));
		   add(new Wall(550, 300, 50, 200));
	}
	
	public static GameModelFacade getInstance() {
		return gm;
	}
	
	public void paint(Graphics g) {
		myTank.paint(g);
		for (int i = 0; i < objects.size(); i++) {
			objects.get(i).paint(g);
		}
		
		//碰撞检测
		  for (int i = 0; i <objects.size(); i++) { 
			  //collider2.collide(objects.get(i), myTank);
			  for (int j = i+1; j < objects.size();j++) { 
				  chain.collide(objects.get(i), objects.get(j));
				  //collider1.collide(objects.get(i), objects.get(j));//子弹坦克
				  //collider2.collide(objects.get(i), objects.get(j));//坦克坦克
			  } 
		  }
		 
		
	}
	
	
	public void add(GameObject go) {
		objects.add(go);
	}
	
	public void remove(GameObject go) {
		objects.remove(go);
	}

	public Tank getMainTank() {
		return myTank;
	}
} 

详细代码在这里:

https://github.com/phs999/DesignPatterns/tree/b8e29f38855ff4bcc75d2e3da41c4d9d0259b5d8/tank/src/phs999/tank/decorator

(2)解决方案

可以考虑对具体的游戏对象比如Bullet、Tank、Wall进行分别抽象,相应的为AbstractBullet、AbstractTank、AbstractWall,然后针对这些抽象类(AbstractBullet、AbstractTank、AbstractWall)进行装饰者模式的实现和应用,而不是GameObject。

所以说,装饰者模式的在使用前,有必要明确要被装饰的类是否合适,层次与等级。不是抽象等级越高越好,不是越抽象越好。

甚至,并不是应用设计模式就是高级的,就是好的。设计模式只是针对某些特定场景、解决特定问题的。如果使得代码逻辑更加复杂不可控,并造成新的问题,那就有必要衡量是不是此处场景不用更好。

本文地址:https://blog.csdn.net/phs999/article/details/107827532

《使用装饰者模式扩展坦克代码-设计模式之坦克大战.doc》

下载本文的Word格式文档,以方便收藏与打印。