设计模式之禅--9.命令模式

命令模式的定义

命令模式是一个高内聚的模式,其定义为:Encapsulate a request as an object,therebyletting you parameterize clients with different requests,queue or log requests,and support undoableoperations.(将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。)

命令模式的通用类图

 在该类图中,我们看到三个角色:
● Receive接收者角色
该角色就是干活的角色,命令传递到这里是应该被执行的,具体到我们上面的例子中就是Group的三个实现类。
● Command命令角色
需要执行的所有命令都在这里声明。
● Invoker调用者角色
接收到命令,并执行命令。在例子中,我(项目经理)就是这个角色。命令模式比较简单,但是在项目中非常频繁地使用,因为它的封装性非常好,把请求方(Invoker)和执行方(Receiver)分开了,扩展性也有很好的保障,通用代码比较简单

演示代码

这次是项目经理的一个工作模拟。模拟一个项目的开发过程。

项目的成员分工采用了常规的分工方式,分为需求组(Requirement Group,RG)、美工组(Page Group,PG)、代码组(我们内部还有一个比较优雅的名字:逻辑实现组,这里使用大家经常称呼的名称,即Code Group,简称CG),加上我这个项目经理正好十个人。刚开始,客户(也就是旅行社,甲方)很乐意和我们每个组探讨,比如和需求组讨论需求、和美工讨论页面、和代码组讨论实现,告诉他们修改、删除、增加各种内容等。这是一种比较常见的甲乙方合作模式,甲方深入到乙方的项目开发中,我们可以使用类图来表示这个过程,

 

项目的初期都是一步一步摸索的,双方的合作也一样。啥也不说了可是工作!!。

1.抽象类Group

public abstract class Group {

	//甲乙双方分开办公,你要和那个组讨论,你首先要找到这个组
	public abstract void find();
	
	//被要求增加功能
	public abstract void add();
	
	//被要求删除功能
	public abstract void delete();
	
	//被要求修改功能
	public abstract void change();
	
	//被要求给出所有的变更计划
	public abstract void plan();
	
	//每个接收者都要对直接执行的任务可以回滚
	public void rollBack(){
		//根据日志进行回滚
	}
}

大家看抽象类中的每个方法,其中的每个都是一个命令语气——“找到它,增加,删除,给我计划!”这些都是命令,给出命令然后由相关的人员去执行。

2.接着实现3个小组

需求组

public class RequirementGroup extends Group {

	// 客户要求需求组过去和他们谈
	public void find() {
		System.out.println("找到需求组...");
	}

	// 客户要求增加一项需求
	public void add() {
		System.out.println("客户要求增加一项需求...");
	}

	// 客户要求修改一项需求
	public void change() {
		System.out.println("客户要求修改一项需求...");
	}

	// 客户要求删除一项需求
	public void delete() {
		System.out.println("客户要求删除一项需求...");
	}

	// 客户要求出变更计划
	public void plan() {
		System.out.println("客户要求需求变更计划...");
	}

}

需求组有了,我们再看美工组。美工组也很重要,是项目的脸面,客户最终接触到的还是界面。 

美工组

public class PageGroup extends Group {
	
	//首先这个美工组应该被找到吧,要不你跟谁谈?
	public void find() {
		System.out.println("找到美工组...");
	}
	
	//美工被要求增加一个页面
	public void add() {
		System.out.println("客户要求增加一个页面...");
	}

	//客户要求对现有界面做修改
	public void change() {
		System.out.println("客户要求修改一个页面...");
	}

	//甲方是老大,要求删除一些页面
	public void delete() {
		System.out.println("客户要求删除一个页面...");
	}
	
	//所有的增删改那要给出计划呀
	public void plan() {
		System.out.println("客户要求页面变更计划...");
	}
}

最后看代码组。这个组的成员一般比较沉闷,不多说话,但多做事儿,这是这个组的典型特点。 

代码组

public class CodeGroup extends Group {

	// 客户要求代码组过去和他们谈
	public void find() {
		System.out.println("找到代码组...");
	}

	// 客户要求增加一项功能
	public void add() {
		System.out.println("客户要求增加一项功能...");
	}

	// 客户要求修改一项功能
	public void change() {
		System.out.println("客户要求修改一项功能...");
	}

	// 客户要求删除一项功能
	public void delete() {
		System.out.println("客户要求删除一项功能...");
	}

	// 客户要求出变更计划
	public void plan() {
		System.out.println("客户要求代码变更计划...");
	}

}

整个项目的3个支柱都已经产生了,那看客户怎么和我们谈。客户刚开始提交了他们自己写的一份比较完整的需求,需求组根据这份需求写了一份分析说明书,客户看后,要求增加需求,

3.甲方爸爸要发话了

public class Client {
	
	public static void main(String[] args) {		
		//首先客户找到需求组说,过来谈需求,并修改
		System.out.println("-------------客户要求增加一个需求-----------------");
		Group rg = new RequirementGroup();
		//找到需求组
		rg.find();
		//增加一个需求
		rg.add();
		//要求变更计划
		rg.plan();
	}
}

-------------客户要求增加一个需求-----------------
找到需求组...
客户要求增加一项需求...
客户要求需求变更计划...

好了,界面也谈过了,应该没什么大问题了吧。过了一天后,客户又让代码组过去,说是数据库设计问题,然后又叫美工组过去,布置了一堆命令……这个就不一一写了,大家应该能够体会得到!问题来了,我们修改可以,但是每次都是叫一个组去,布置个任务,然后出计划,每次都这样,如果让你当甲方,你烦不烦?而且这种方式很容易出错误,并且还真发生过。客户把美工叫过去了,要删除,可美工说需求是这么写的,然后客户又命令需求组过去,一次次地折腾之后,客户也烦躁了,于是直接抓住我这个项目经理说:“我不管你们内部怎么安排,你就给我找个接头负责人,我告诉他怎么做,删除页面,增加功能,你们内部怎么处理我不管,我就告诉他我要干什么就成了……”
我一听,好啊,这也正是我想要的,我们项目组的兄弟们也已经受不了了,于是我改变了一下我的处理方式, 

我们的小组重组了

在原有的类图上增加了一个Invoker类,其作用是根据客户的命令安排不同的组员进行工作,例如,客户说“界面上删除一条记录”,Invoker类接收到该String类型命令后,通知美工组PageGroup开始delete,然后再找到代码组CodeGroup后台不要存到数据库中,最后反馈给客户一个执行计划。这是一个挺好的方案,但是客户的命令是一个String类型的,这有非常
多的变化,仅仅通过一个字符串来传递命令并不是一个非常好的方案,因为在系统设计中,字符串没有约束力,根据字符串判断相关的业务逻辑不是一个优秀的解决方案。那怎么才是一个优秀的方案呢?解决方案是:对客户发出的命令进行封装,每个命令是一个对象,避免客户、负责人、组员之间的交流误差,封装后的结果就是客户只要说一个命令,我的项目组就立刻开始启动。不用思考、解析命令字符串, 

 

Command抽象类只有一个方法execute,其作用就是执行命令,子类非常坚决地实现该命令,与军队中类似,上级军官给士兵发布命令:爬上这个旗杆!然后士兵回答:Yes,Sir!完美的项目也与此类似,客户发送一个删除页面的命令,接头负责人Invoker接收到命令后,立刻执行DeletePageCommand的execute方法。对类图中增加的几个类说明如下。
● Command抽象类:客户发给我们的命令,定义三个工作组的成员变量,供子类使用;定义一个抽象方法execute,由子类来实现。
● CInvoker实现类:项目接头负责人,setComand接收客户发给我们的命令,action方法是执行客户的命令(方法名写成是action,与command的execute区分开,避免混淆)。 

继续我们的加班之旅吧!!

1.抽象命令类

public abstract class Command {
	
	//把三个组都定义好,子类可以直接使用
	protected RequirementGroup rg = new RequirementGroup();  //需求组
	protected PageGroup pg = new PageGroup();  //美工组
	protected CodeGroup cg = new CodeGroup();  //代码组;
	

	//只要一个方法,你要我做什么事情
	public abstract void execute();
	
	
}

抽象类很简单,具体的实现类只要实现execute方法就可以了。

2.增加需求的命令

public class AddRequirementCommand extends Command {
	// 执行增加一项需求的命令
	public void execute() {
		// 找到需求组
		super.rg.find();

		// 增加一份需求
		super.rg.add();

		// 页面也要增加
		super.pg.add();

		// 功能也要增加
		super.cg.add();

		// 给出计划
		super.rg.plan();
	}

}

3.删除页面的命令

public class DeletePageCommand extends Command {
	
	//执行删除一个页面的命令
	public void execute() {
		//找到页面组
		super.pg.find();
		
		//删除一个页面
		super.rg.delete();
		
		//给出计划
		super.rg.plan();
	}

}

Command抽象类可以有N个子类,如增加一个功能命令(AddFunCommand),删除一份需求命令(DeleteRequirementCommand)等,这里就不再描述了,只要是由客户产生、时常性的行为都可以定义为一个命令,其实现类都比较简单。

4.负责人

public class Invoker {
	// 什么命令
	private Command command;

	// 客户发出命令
	public void setCommand(Command command) {
		this.command = command;
	}

	// 执行客户的命令
	public void action() {
		this.command.execute();
	}
}

这更简单了,负责人只要接到客户的命令,就立刻执行。

5.甲方爸爸又开始提需求了

public class Client {

	public static void main(String[] args) {
		// 定义我们的接头人
		Invoker xiaoSan = new Invoker(); // 接头人就是我小三

		// 客户要求增加一项需求
		System.out.println("-------------客户要求删除一个页面-----------------");
		// 客户给我们下命令来
		// Command command = new AddRequirementCommand();
		Command command = new DeletePageCommand();

		// 接头人接收到命令
		xiaoSan.setCommand(command);

		// 接头人执行命令
		xiaoSan.action();

	}
}

 -------------客户要求删除一个页面-----------------
找到美工组...
客户要求删除一项需求...
客户要求需求变更计划...

嗯!这次都不累了。和甲方的合作还算愉快。

 

命令模式的优点


● 类间解耦
调用者角色与接收者角色之间没有任何依赖关系,调用者实现功能时只需调用Command抽象类的execute方法就可以,不需要了解到底是哪个接收者执行。
● 可扩展性
Command的子类可以非常容易地扩展,而调用者Invoker和高层次的模块Client不产生严重的代码耦合。
● 命令模式结合其他模式会更优秀
命令模式可以结合责任链模式,实现命令族解析任务;结合模板方法模式,则可以减少Command子类的膨胀问题。


命令模式的缺点


命令模式也是有缺点的,请看Command的子类:如果有N个命令,问题就出来了,Command的子类就可不是几个,而是N个,这个类膨胀得非常大,这个就需要读者在项目中慎重考虑使用。


命令模式的使用场景


只要你认为是命令的地方就可以采用命令模式,例如,在GUI开发中,一个按钮的点击是一个命令,可以采用命令模式;模拟DOS命令的时候,当然也要采用命令模式;触发-反馈机制的处理等。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值