设计模式--行为模式--状态模式--Java

State

Intent

•Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.

applicability

An object's behavior depends on its state, and it must change its behavior at run-time depending on that state.

Operations have large, multipart conditional statements that depend on the object's state. This state is usually represented by one or more enumerated constants.Often, several operations will contain this same conditional structure. The State pattern puts each branch of the conditional in a separate class. This lets you treat the object's state as an object in its own right that can vary independently from other objects.

UML


下面摘自《Java与模式》:


Game Engine example

作为一个通用的游戏,站在抽象的角度来讲,实际上可以简单分为,菜单屏,游戏屏,设置屏,帮助屏。









状态图



通过状态图可以发现,实际上这几种状态有明显的状态的变迁。 每一种状态对应其自身的渲染过程,以及事件处理过程。

经过抽象可以得到下列对象模型


Screen.update主要作用为处理事件响应,譬如鼠标键盘触屏等。
Screen.present主要作用渲染当前状态的帧。

下面这个类是较为简单的HelpScreen代码:
package org.changsheng.game.TankGame.screen;

import java.awt.Color;
import java.awt.event.KeyEvent;
import java.awt.geom.Rectangle2D;
import java.util.List;

import org.changsheng.game.TankGame.util.BoundsUtil;
import org.changsheng.game.TankGame.util.TankGameGraphics;
import org.changsheng.game.engine.interfaze.Game;
import org.changsheng.game.engine.interfaze.GameInput.GameKeyEvent;
import org.changsheng.game.engine.interfaze.GameInput.GameMouseEvent;
import org.changsheng.game.engine.interfaze.Screen;

/**
 * Help screen .
 * 
 * @author changsheng
 * 
 */
public class HelpScreen extends Screen {

	public HelpScreen(Game game) {
		super(game);
	}

	@Override
	public void update(float deltaTime) {

		List events = game.getGameInput().getMouseEvents();
		List keyevents = game.getGameInput().getKeyEvents();

		for (int i = 0; i < events.size(); i++) {
			GameMouseEvent event = events.get(i);
			/* mouse left click */
			if (event.WITCH == GameMouseEvent.MOUSE_LEFT) {
				if (event.TYPE == GameMouseEvent.MOUSE_UP) {
					if (BoundsUtil.inBounds(event, rectMenu)) {
						game.setScreen(new MainMenuScreen(game));
					}

				}
			}
		}

		for (int i = 0; i < keyevents.size(); i++) {
			GameKeyEvent event = keyevents.get(i);
			if (event.type == GameKeyEvent.KEY_UP) {
				if (event.keyCode == KeyEvent.VK_M) {
					game.setScreen(new MainMenuScreen(game));
				}
			}
		}

	}

	Rectangle2D.Double rectMenu = new Rectangle2D.Double(300, 510, 200, 50);

	@Override
	public void present(float deltaTime) {
		TankGameGraphics g2d = (TankGameGraphics) game.getGameGraphics2D();
		g2d.drawBackground();
		g2d.drawString("Control", 100, 150, Color.RED);
		g2d.drawString("Start --- Enter", 200, 200, Color.WHITE);
		g2d.drawString("Move --- UP/DOWN/LEFT/RIGHT", 200, 250, Color.WHITE);
		g2d.drawString("Fire --- Space", 200, 300, Color.WHITE);
		g2d.drawString("Pause --- P", 200, 350, Color.WHITE);
		g2d.drawString("Resume --- P", 200, 400, Color.WHITE);
		g2d.drawString("Menu --- M", 200, 450, Color.WHITE);
		g2d.drawRoundRect(rectMenu, 20, Color.YELLOW);
		g2d.drawString("Menu", rectMenu, 30, Color.YELLOW);

	}

	@Override
	public void pause() {

	}

	@Override
	public void resume() {

	}

	@Override
	public void dispose() {

	}

}

其中Game类为游戏资源中央控制器,存放键盘事件,鼠标事件,以及重要的SetScreen(Screen screen);方法等信息。
HelpScreen.update方法主要是当点击Menu区域时,返回到菜单屏幕,
HelpScreen.present方法主要是渲染HelpScreen,如上面的截屏。

一旦通过Screen.update方法中的逻辑使状态发生变迁之后,在渲染线程中就可以实时显示变迁后的屏幕,
比如点击开始游戏,状态从MenuScreen变迁到GameScreen。
渲染的代码如下:
package org.changsheng.game.engine.interfaze.impl;

import java.awt.Graphics;
import java.util.concurrent.TimeUnit;

import javax.swing.JPanel;

import org.changsheng.game.engine.interfaze.Game;

/**
 * Render View and Render Thread.
 * 
 * @author changsheng
 * 
 */
@SuppressWarnings("serial")
public class GameRenderView extends JPanel implements Runnable {

	Game game;

	/* whether the render thread is running. */
	private volatile boolean running = false;

	/* render thread */
	private Thread renderThread;

	/* start time */
	long startTime = System.currentTimeMillis();

	public GameRenderView(Game game) {
		this.game = game;
	}

	@Override
	protected void paintComponent(Graphics g) {
		super.paintComponent(g);
		/* use the japnel's graphics draw the buffered image. */
		/**
		 * UI Thread 中只是获取了BufferedImage并没有进行绘制处理, 所以在此共享对象BufferedIamge是线程安全的。
		 */
		g.drawImage(game.getGameGraphics2D().getBufferedImage(), 0, 0, null);
	}

	public void run() {
		while (running) {
			/* Current frames is 100 image/second */
			try {
				TimeUnit.MILLISECONDS.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			long endTime = System.currentTimeMillis();
			long detaltime = (endTime - startTime);
			game.getCurrentScreen().update(detaltime);// update current screen
														// events...
			game.getCurrentScreen().present(detaltime);// present current screen
														// // effect...
			repaint();// call the paintComponent method.
			startTime = System.currentTimeMillis();
		}
	}

	/**
	 * build the render thread and start it.
	 */
	public void build() {
		running = true;
		renderThread = new Thread(this);
		renderThread.start();
	}

	/**
	 * rebuild the render thread and start it.
	 */
	public void rebuild() {
		running = true;
		renderThread = new Thread(this);
		renderThread.start();
	}

	/**
	 * pause the render thread.
	 */
	public void pause() {
		running = false;
		while (true) {
			try {
				/* wait the render thread complete. */
				renderThread.join();
				break;
			} catch (InterruptedException e) {
				// retry
			}
		}
	}

}


其中渲染线程中最重要的是
game.getCurrentScreen().update(detaltime);// update current screen
game.getCurrentScreen().present(detaltime);// present current screen
第一个更新当前State事件
第二个更新当前State帧

假如说这里根本不使用State Patterns,可以想象一下,首先会出现switch过多这种情况(代码坏味道),对于渲染需要switch对于事件更新也需要switch,而且这些代码极大的可能和渲染高度偶合在一起,假如需求变更,需要添加商铺功能,高分功能,这个时候不可避免阅读代码困难,修改不易。

综上所述,通过使用State Patterns可以解耦状态调用端和状态变迁端,即游戏渲染线程和Screen变迁。另一方面,对于状态的添加开放,对现有状态修改关闭,符合OCP原则,另一方面也符合SRP原则。

你可能感兴趣的:(设计模式-行为模式)