第三篇:命令模式

这一次的场景,我们用游戏来举例 ;

你是一名游戏开发者,现在呢,需要你来为某个游戏中的角色实现行走的功能,行走的指令从用户的键盘获取 W,S,A,D分别对应角色的上下左右移动,好吧;为了模拟上面的需求,我们基本可以确定要有些什么对象和方法了;

首先,我们创建需要的游戏角色;

/** 游戏角色*/
public class Role {
	
	public void turnOn(){
		System.out.println("角色前进...");
	}
	public void turnBack(){
		System.out.println("角色后退...");
	}
	public void turnLeft(){
		System.out.println("角色向左...");
	}
	public void turnRight(){
		System.out.println("角色向右...");
	}
}


然后,再创建一个键盘,它可能是这个样子的:

/**键盘对象*/
public class KeyBoard {
	
	private Role role;
	
	public KeyBoard( Role role) {
		this.role = role;
	}
	
	/**按下W键*/
	public void downW(){
		role.turnOn();
	}
	/**按下S键*/
	public void downS(){
		role.turnBack();
	}
	/**按下A键*/
	public void downA(){
		role.turnLeft();
	}
	/**按下D键*/
	public void downD(){
		role.turnRight();
	}
	
}

让我们来测试一下吧!

	public static void main(String[] args){
		Role role = new Role();
		KeyBoard keyBoard = new KeyBoard(role);
		keyBoard.downA();
		keyBoard.downW();
	}

输出结果:

角色向左...
角色前进...


好吧,现在我们来看一下这种设计有什么问题呢,我先说几点,剩下的你们自己补充喔!

1:键盘与Role高强度耦合,键盘只应该触发按键,至于事件方法触发后,该做什么的可能性有很多,不应该与Role类绑死;

2:如果我现在说行走的同时,要加入碰撞检测,一旦碰到了障碍物将不允许再行走,此时该怎么办?我们可能得改动Role类的行走方法代码,加入if...else... 或者得改动KeyBoard类,在调用Role方法前,加入判断...可想而知,后期还会有太多的需求加入,都得改动原有代码,这极大增加了系统出错风险,记住,我们的设计原则有一点是:对扩展开放,对修改关闭!

OK,让我们以命令模式的思维再来思考一下;

在命令模式中,有3个角色, 命令调用者(命令下达者),命令接收者(命令执行者),命令;


 

想想,我们的键盘Keyboard,角色Role,Test测试类应该要分别对应上图的哪个位置?  KeyBoard是命令调用者,Role是命令接受者(因为最底层是它去执行行走的方法),Test是客户端;我们现在还少命令这个对象!

终于到了要来改造我们的设计的时候了!

先定义一个命令接口;

/**命令对象接口*/
public interface Command {
	
	/**统一执行命令的方法*/
	void execute();
}

/**一个空的命令,什么都不做*/
class EmptyCommand implements Command{

	@Override
	public void execute() {
		System.out.println("我什么都做不了...");
	}
	
}

/**前进命令*/
public class TurnOnCommand implements Command{
	
	private Role role;
	
	public TurnOnCommand( Role role) {
		this.role = role;
	}
	@Override
	public void execute() {
		System.out.println("前行前的碰撞检测...");
		role.turnOn();
		System.out.println("前进完毕了...");
	}
}

/**后退命令*/
class TurnBackCommand implements Command{
	
	private Role role;
	
	public TurnBackCommand( Role role) {
		this.role = role;
	}
	@Override
	public void execute() {
		System.out.println("后退的碰撞检测...");
		role.turnBack();
		System.out.println("后退完毕了...");
	}
}

/**往左移动命令*/
class TurnLeftCommand implements Command{
	
	private Role role;
	
	public TurnLeftCommand( Role role) {
		this.role = role;
	}
	@Override
	public void execute() {
		System.out.println("往左的碰撞检测...");
		role.turnLeft();
		System.out.println("往左移动完毕...");
	}
}

/**往右移动命令*/
class TurnRightCommand implements Command{
	
	private Role role;
	
	public TurnRightCommand( Role role) {
		this.role = role;
	}
	@Override
	public void execute() {
		System.out.println("往右的碰撞检测...");
		role.turnRight();
		System.out.println("往右移动完毕了...");
	}
}

改造键盘对象成为命令调用者;

/**键盘对象,命令调用者*/
public class KeyBoard {
	
	/**用来存放一堆命令*/
	Map<String,Command> commands ;
	
	public KeyBoard() {
		commands = new HashMap<String, Command>();
		//初始化一堆命令,默认所有的操作都是一个空的命令
		Command emptyCommand = new EmptyCommand();
		commands.put("W", emptyCommand);
		commands.put("S", emptyCommand);
		commands.put("A", emptyCommand);
		commands.put("D", emptyCommand);
	}
	
	/**将命令对象绑定到命令调用者*/
	public void setCommand( String key ,Command command ){
		commands.put(key, command);
	}
	
	/**按下W键,直接给命令对象下达执行指令,至于整个执行过程,
	 * 我不去关心!我只需要知道,我的命令有人会去执行就可以了*/
	public void downW(){
		commands.get("W").execute();
	}
	
	/**按下S键*/
	public void downS(){
		commands.get("S").execute();
	}
	/**按下A键*/
	public void downA(){
		commands.get("A").execute();
	}
	/**按下D键*/
	public void downD(){
		commands.get("D").execute();
	}
	
}

创建客户端:

/**我们的客户端*/
public class Test {
	
	public static void main(String[] args){
		Role role = new Role();
		//创建命令调用者
		KeyBoard keyBoard = new KeyBoard();
		keyBoard.downA();
		//给命令调用者绑定命令对象
		keyBoard.setCommand("W", new TurnOnCommand(role));
		keyBoard.setCommand("S", new TurnBackCommand(role));
		keyBoard.setCommand("A", new TurnLeftCommand(role));
		keyBoard.setCommand("D", new TurnRightCommand(role));
		//走一下试试
		keyBoard.downA();
		keyBoard.downW();
		
	}
}

看看输出结果:

************************************************************************

我什么都做不了...
往左的碰撞检测...
角色向左...
往左移动完毕...
前行前的碰撞检测...
角色前进...
前进完毕了...

************************************************************************

至此,我们的Keyboard完全与Role解耦,在这样的设计中,键盘只负责下达一个指令,具体怎么执行它完全不知道,也不会去关心,它与一系列Command对象的耦合是没有任何问题的,届时需求变动,需要更换行走逻辑,只需要在对应的命令对象中修改代码就可以,不会影响到其它的行走逻辑,又或者,直接绑定一个新的命令对象!

那么,命令模式到底有些什么作用呢,可以用在哪里呢?举个例子,我们可以用命令模式来实现电脑上的 复制--撤销...这种功能 ,好吧,文字不好举例,还是让我们用代码来说话吧!

先创建一个计算机和文本对象:

/**一台电脑*/
public class Computer {
	
	/**一个堆栈,记录所有执行过的命令*/
	private Stack<ExpertCommand> commandStack ;
	
	public Computer() {
		commandStack = new Stack<ExpertCommand>();
	}
	
	/**粘贴文本,交给命令对象去做!*/
	public void paste(ExpertCommand command){
		command.execute();
		//将执行过的粘贴命令压入栈中
		commandStack.push(command);
	}
	
	/**撤销粘贴,放心,命令对象知道该怎么回滚!*/
	public void rollBack(){
		//弹出最后一次粘贴命令,并执行它的回滚方法
		if(!commandStack.empty()){
			commandStack.pop().rollBack();
		}
	}
}

/**一个txt文本*/
class Text{
	
	//文本内容
	private String content;
	
	public String getContent() {
		return content;
	}

	public void setContent(String content) {
		this.content = content;
	}
}

再创建一个命令接口:

/**具备撤销功能的命令接口*/
public interface ExpertCommand {
	
	/**命令执行*/
	void execute();
	
	/**命令回滚*/
	void rollBack();
}


/**复制命令*/
public class PasteCommand implements ExpertCommand{
	
	/**它必须知道要往哪个文本上面复制...*/
	private Text text;
	/**它必须知道要复制什么内容...*/
	private String copyText;
	
	public PasteCommand(Text text,String copyText) {
		this.text = text;
		this.copyText = copyText;
	}
	@Override
	public void execute() {
		//让命令接受者text去执行更新文本内容的操作
		String originText = text.getContent();
		originText+=copyText;
		text.setContent(originText);
		System.out.println("粘贴后文本内容为:"+text.getContent());
	}
	@Override
	public void rollBack() {
		//撤销上次的粘贴内容,这里直接将上次复制的文本替换为空就可以了!
		String originText = text.getContent();
		text.setContent(originText.substring(0, 
				originText.lastIndexOf(copyText)));
		System.out.println("撤销后文本内容为:"+text.getContent());
	}
}

测试一下吧!

public class Test {
	
	public static void main(String[] args){
		Computer computer = new Computer();
		Text text = new Text();
		text.setContent("");//刚打开的文本文件是个空文本...
		
		//执行几个粘贴命令
		computer.paste(new PasteCommand(text, "你的气质,"));
		computer.paste(new PasteCommand(text, "源于你走过的路,"));
		computer.paste(new PasteCommand(text, "见过的人,"));
		computer.paste(new PasteCommand(text, "读过的书!"));
		
		//回滚复制过的内容...
		computer.rollBack();
		computer.rollBack();
		computer.rollBack();
		computer.rollBack();
		computer.rollBack();
		computer.rollBack();
	
	}
}

最终输出结果:

*******************************************************************

粘贴后文本内容为:你的气质,
粘贴后文本内容为:你的气质,源于你走过的路,
粘贴后文本内容为:你的气质,源于你走过的路,见过的人,,
粘贴后文本内容为:你的气质,源于你走过的路,见过的人,,读过的书!
撤销后文本内容为:你的气质,源于你走过的路,见过的人,,
撤销后文本内容为:你的气质,源于你走过的路,
撤销后文本内容为:你的气质,
撤销后文本内容为:

*******************************************************************


好了,我们总结一下,其实命令模式就是为了将发出请求的对象(命令调用者)和接收请求的对象进行解耦!这样解耦后,我们多了一层拦截,可以更方便的扩展,其实想一想,所有的所谓的模式不都是这样的么?再千变万化都无非是加一次或加多层代码,这样变化扩展的维度就多了一层或多层;设计模式不可滥用,如果一个简单的程序也去一昧的追求使用各种模式,那无非是增加设计成本,增加类与类之间的复杂度。

听点轻音乐睡觉了,各位码友,晚安!





  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值