这一次的场景,我们用游戏来举例 ;
你是一名游戏开发者,现在呢,需要你来为某个游戏中的角色实现行走的功能,行走的指令从用户的键盘获取 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();
}
}
最终输出结果:
*******************************************************************
粘贴后文本内容为:你的气质,
粘贴后文本内容为:你的气质,源于你走过的路,
粘贴后文本内容为:你的气质,源于你走过的路,见过的人,,
粘贴后文本内容为:你的气质,源于你走过的路,见过的人,,读过的书!
撤销后文本内容为:你的气质,源于你走过的路,见过的人,,
撤销后文本内容为:你的气质,源于你走过的路,
撤销后文本内容为:你的气质,
撤销后文本内容为:
*******************************************************************
好了,我们总结一下,其实命令模式就是为了将发出请求的对象(命令调用者)和接收请求的对象进行解耦!这样解耦后,我们多了一层拦截,可以更方便的扩展,其实想一想,所有的所谓的模式不都是这样的么?再千变万化都无非是加一次或加多层代码,这样变化扩展的维度就多了一层或多层;设计模式不可滥用,如果一个简单的程序也去一昧的追求使用各种模式,那无非是增加设计成本,增加类与类之间的复杂度。
听点轻音乐睡觉了,各位码友,晚安!