设计模式之装饰者decorator模式
概念
字面意思,用来做装饰的。
原理:通过类的聚合
例子(github游戏代码地址)
(游戏设计bug非常多,完全是为了学设计模式而写,勿喷。除了用的装饰模式,还混杂着其他设计模式,有些实在不适合混杂在一起,我又重新归零,另建了分支。
操作:上下左右控制移动,ctrl键发射一颗子弹,A键发射4颗子弹)
现有一个坦克的小游戏,我想要对坦克发射的子弹进行装饰。
原先的游戏界面:
我想要给子弹加个方框,然后再加个尾巴。效果图:
游戏中类的结构图:
稍作分析:
所有游戏物体(Bullet,Tank,Wall)都抽象出共同父类GameObject。抽象的GODecorator装饰器又是从GameObject继承,并且聚合了GameObject。
这样做的好处就是,我可以用修饰器对任意GameObject子类进行修饰,并且修饰之间可以扩展。
GameObject抽象类
package com.zl.pojo; import com.zl.enums.Group; import java.awt.*; /** * @Description * @Author zl * @Date 2020/9/14 16:04 * @Version 1.0 */ public abstract class GameObject { public int x, y; public abstract void paint(Graphics g); public abstract int getWidth(); public abstract int getHeight(); public abstract Rectangle getRect(); public abstract Group getGroup(); }
Bullet类
package com.zl.pojo; import com.zl.Audio; import com.zl.GameModel; import com.zl.ResourceImage; import com.zl.TankFrame; import com.zl.enums.Dir; import com.zl.enums.Group; import java.awt.*; import java.awt.image.BufferedImage; /** * @Description 子弹实体 * @Author zl * @Date 2020/8/15 11:13 * @Version 1.0 */ public class Bullet extends GameObject{ //方向 private Dir dir; //子弹宽高 public static int width = ResourceImage.bulletD.getWidth(); public static int heigth = ResourceImage.bulletD.getHeight(); public Rectangle rect = new Rectangle(); private final static int speed = 6; //子弹是否活着 private boolean living = true; public Group group = null; TankFrame tf = null; public Bullet(int x, int y, Dir dir, Group group) { this.x = x; this.y = y; this.dir = dir; this.group = group; rect.x = this.x; rect.y = this.y; rect.width = width; rect.height = heigth; //GameModel.getInstance().add(this); if (group == Group.GOOD) new Thread(()->new Audio("com/zl/audio/tank_fire.wav").play()).start(); } public Dir getDir() { return dir; } public void setDir(Dir dir) { this.dir = dir; } public void paint(Graphics g) { if (!living) { GameModel.getInstance().remove(this); } // Color color = g.getColor(); // g.setColor(Color.RED); // g.fillOval(x,y,10,10); // g.setColor(color); move(); BufferedImage image = null; switch (dir) { case UP: image = ResourceImage.bulletU; break; case DOWN: image = ResourceImage.bulletD; break; case LEFT: image = ResourceImage.bulletL; break; case RIGHT: image = ResourceImage.bulletR; break; } g.drawImage(image,x,y,null); } @Override public int getWidth() { return width; } @Override public int getHeight() { return heigth; } @Override public Rectangle getRect() { return rect; } @Override public Group getGroup() { return group; } private void move() { switch (dir){ case UP: y-=speed; break; case DOWN: y+=speed; break; case LEFT: x-=speed; break; case RIGHT: x+=speed; break; } rect.x = this.x; rect.y = this.y; if (x<0 || y<0 || x> TankFrame.GAME_WIDTH || y>TankFrame.GAME_HEIGHT) living = false; GameModel.getInstance().collideWith(); } /*public void collideWith(Tank tank) { if (this.group == tank.getGroup()) return; if (rect.intersects(tank.rect)) { tank.die(); this.die(); gm.gameObjects.add(new Explode(tank.getX()+ Tank.width/2- Explode.width/2, tank.getY()+ Tank.heigth/2- Explode.heigth/2, gm)); } }*/ public void die() { this.living = false; } }
GODecorator抽象类
package com.zl.decorator; import com.zl.pojo.GameObject; import java.awt.*; public abstract class GODecorator extends GameObject { GameObject go; public GODecorator(GameObject go) { this.go = go; } @Override public void paint(Graphics g) { go.paint(g); } }
RectDecorator类(为物体添加边框)
package com.zl.decorator; import com.zl.enums.Group; import com.zl.pojo.GameObject; import java.awt.*; public class RectDecorator extends GODecorator { public RectDecorator(GameObject go) { super(go); } @Override public void paint(Graphics g) { super.paint(g); Color color = g.getColor(); g.setColor(Color.BLACK); g.drawRect(super.go.x, super.go.y, getWidth()+2, getHeight()+2); g.setColor(color); } @Override public int getWidth() { return super.go.getWidth(); } @Override public int getHeight() { return super.go.getHeight(); } @Override public Rectangle getRect() { return super.go.getRect(); } @Override public Group getGroup() { return super.go.getGroup(); } }
TailDecorator类(为物体加上尾巴)
package com.zl.decorator; import com.zl.enums.Group; import com.zl.pojo.GameObject; import java.awt.*; public class TailDecorator extends GODecorator { public TailDecorator(GameObject go) { super(go); } @Override public void paint(Graphics g) { this.x = super.go.x; this.y = super.go.y; super.paint(g); Color color = g.getColor(); g.setColor(Color.BLACK); g.drawLine(super.go.x, super.go.y, super.go.x-10, super.go.y-20); g.setColor(color); } @Override public int getWidth() { return super.go.getWidth(); } @Override public int getHeight() { return super.go.getHeight(); } @Override public Rectangle getRect() { return super.go.getRect(); } @Override public Group getGroup() { return super.go.getGroup(); } }
在没有修饰的情况下,当坦克开火打出一颗子弹时,直接new
GameModel.getInstance().add(new Bullet(bx, by, t.getDir(), t.getGroup()));
在需要为子弹加边框时,new一个bullet,把它传到rectDecorator里面即可
GameModel.getInstance().add( new RectDecorator( new Bullet(bx, by, t.getDir(), t.getGroup()) ) );
如果需要在加边框的基础上加尾巴,接着把rectDecorator传到TailDecorator里面即可
GameModel.getInstance().add( new RectDecorator( new TailDecorator( new Bullet(bx, by, t.getDir(), t.getGroup()) ) ) );
如果需要修饰其他物体,以此类推
心有所想,必有回响