命令模式

命令模式<Command>
设计模式--->简单来说,就是将简单问题复杂化,这样就提高了可扩展性,灵活性,可维护性,可复用性。

大排档:
夜幕降临,灯红柳绿的都市,没钱的我很少夜间去消费,大部分时间是待在了房子里学习Java,看看书,找同学聊聊天,看看新闻的,偶尔抽风一次,约上同学一起走在繁华的街道,听着那首熟悉的《再见青春》,感知着这个城市的心跳,走着走着怀念起了曾经放肆的时光,几个人不顾街边的一切,在夜市大排档坐下来吃着烧烤,喝着冰镇啤酒,狂侃人间冷暖...

毕业季:
即将毕业了,再来一次大排档...

再现大排档:
灯光若隐若现,我们一行四人,只见西边街道旁云雾缭绕,我们走到跟前,只见传说中的犀利哥正龙飞凤舞般,凤姐也婀娜多姿地吆喝着,我们选择了靠接路边的一张桌子坐下来,凤姐空手走近:
我:“烤鱼,四只。” 
凤姐:“利哥,烤鱼四只。”
云姐:“田螺二盘”。
凤姐:“利哥,再来两盘田螺。”
云姐急忙忙:“点多了,不好意思,只要一盘田螺。”
凤姐:“利哥,错了,田螺一盘。”
...
只见凤姐招架不住了,乱七八糟的,人一多都不知道是谁点的了,也不知道点的东西有没有,有的消费者活了,这是只听见有一女音:命令模式。我转过头来,云姐话音刚落。
凤姐不知是被吓住了,还是怎么了,半会没有动,站那念叨:命令模式,莫非就是那个,将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数话,对请求排队或记录请求日志,以及支持撤销操作。哎,太抽象了,那么,现在生意这么火爆,我是不是可以拿一个本子记录下来他们的点的食物呢,然后根据库存记录来告诉他们哪些可以有,哪些不能有呢,当他们后悔所点的菜时,我也给记录上一笔,这样就有条不紊了,有木有?凤姐突然如释重负般的叹了一声,向我旁边的云姐跑了个媚眼,哎,凤姐啊凤姐啊,你咋就没有看到旁边的帅哥呢。
半分钟后,凤姐手拿一小本,笑眯眯地走过来:
凤姐:“对不起,刚才点的菜可以重新说一下吗?”
我:”四只烤鱼。“
云姐:”一盘炒田螺。“
凤姐:”咦,你不是点两盘的吗?哦,对了,你又减了一盘。“
我:”两串鸡翅。
凤姐:”还有吗?“
我俩:”先上吧!“
凤姐:”好嘞。“之间凤姐扭着小屁股走向犀利哥...

云姐给我讲命令模式:
刚才,那么多人点烧烤,凤姐一开始还能记住,可是这年头,这种店打着灯笼都不好找,所以生意火爆是必然的,那么这时,凤姐如果没有将面向对象中的命令模式灵活运用,只怕她要苦喽。那么,我们吃烧烤时,不用跟犀利哥交流,只需要告诉凤姐我们要什么,然后凤姐再告诉犀利哥,一开始,凤姐,用了最简单的方法,我们点一个,她就告诉犀利哥,问题来了,等所有都烤好时,都不知道是谁点的呢,特别让人气愤的是,当某某点了最爱吃的烧烤,满怀期待地等着,事后却被告知没有了,可谓吃力不讨好啊。还好凤姐学过编程,知道命令模式是个啥,那么就刚才凤姐去拿了个小本子记录这个小小的变化,我们来一次面向对象之旅吧!

面向对象:
1.首先看看有哪些对象呢:
Customer:消费者,我们每一个人都算一个消费者。
Barbecuer:就是烧烤师傅,犀利哥。
 Waiter:就是负责服务我们的,凤姐。
Command:这个就是我们所点的烧烤,每点一个算一个命令,可以抽取出来,作为一个抽象类。
FryPaludina,BakeChickenWingCommand,BakeFishCommand继承自这个类的各种命令。
OrderLog:小本子,负责记录我们所点的菜名。
FoodDepository,库存类,可以帮助凤姐事前告诉我们有没有。

2.差不多了,那么看看他们有什么职责呢,还有他们之间的交互,这里主要看看Command,Waiter和Barbecuer
Command :executeCommand(),这个有自身负责的执行方法,是一个抽象的方法。当然这个类要有烧烤师傅,消费者和食物名称属性,所以要提供一些获取这些信息的
公共接口。
Waiter:服务员类,凤姐,负责我们和犀利哥之间的交流,凤姐必须有日志记录小本,知道库存信息,还有我们提的一些列菜单,有这些属性。所以必须提供,添加订单
和删除订单的消息,在添加和删除的过程中记录日志,并根据库存判断是否还有。
Barbecuer:犀利哥,负责烧烤的,主要有几个方法,就是烤鱼,烤鸡翅,炒田螺。
3.类的交互,看看我们的UML类图就知道了:
云姐拿出包里的纸和笔,开始画着:


我正看得入迷,刚准备开口问云姐怎么用代码实现了,云姐突然卖了一关子,得瑟道:”自己想去。“,于是我脑袋快速运转...

最简单的是消费者了Customer:
/**
 * 消费者类
 * @author PingCX
 *
 */
public class Customer {
	private String name;
	private String sex;
	
	public Customer(String name,String sex){
		this.name = name;
		this.sex = sex;
	}

	public String getName() {
		return name;
	}

	public String getSex() {
		return sex;
	}
}

看到凤姐手上那个小本,想到了OrderLog可以这么设计:
/**
 * 日志类
 * @author PingCX
 *
 */
public final class OrderLog {

	private final static OrderLog INSTANCE = new OrderLog();
	private int index;
	private Map<String, String> logs;

	private OrderLog() {
		logs = new TreeMap<String, String>();
		index = 1;
	}

	public static OrderLog getInstance() {
		return INSTANCE;
	}

	/**
	 * 添加日志
	 * 
	 * @param content
	 */
	public void addLog(String content) {
		logs.put(
				index++
						+ ":"
						+ new SimpleDateFormat("yyyy-MM-dd E hh:mm:ss")
								.format(new Date()), content);
	}

	/**
	 * 展示日志信息
	 */
	public void displayLog() {
		for (Entry<String, String> entrys : logs.entrySet()) {
			String dateString = entrys.getKey();
			String content = entrys.getValue();
			System.out.println(dateString + "-->" + content);
		}
	}
}

@这里用到了单例设计模式,赚了,又让我复习了一次饿汉式单例。 
@提供了一个添加日志的公共接口,addLog(String content){...},还有一个展示日志信息的接口displayLog(){...}
----------------------------------------------------------------------------------------------------------
这次我点了四条鱼,凤姐,看了一眼小本子,直接告诉我鱼只剩下三条了,食物仓库也有主意了
FoodDepository:
/**
 * 食物仓库
 * @author PingCX
 *
 */
public final class FoodDepository {
	
	private static FoodDepository instance; 
	
	private  Map<String, Integer> food;
	
	private  FoodDepository(){
		food = new HashMap<String, Integer>();
		food.put("鱼", 0);
		food.put("鸡翅", 0);
		food.put("田螺", 0);
	}
	
	public static FoodDepository getInstance(){
		if (instance == null) {
			synchronized (FoodDepository.class) {
				if (instance == null) {
					instance = new FoodDepository();
				}
			}
		}
		return instance;
	}
	
	/**
	 * 设置食物的数量
	 * @param foodName
	 * @param num
	 */
	public void setFoodNum(String foodName, int num){
		food.put(foodName, num);
	}
	
	/**
	 * 获得食物的数量
	 * @param foodName 食物的名字
	 * @return 食物的数量
	 */
	public int getFoodNum(String foodName){
		return food.get(foodName);
	}
}

@在构造方法中,对仓库各种进行初始化数量为0。
@有更新食物库存信息的方法setFoodNum(String foodName, int num){...}和根据名称获得食物库存信息的接口getFoodNum(String foodName){...}

---------------------------------------------------------------------------------------------------------------
斜眼看了一眼凤姐,只见她和犀利哥在有说有笑的,于是Waiter和Barbecuer也浮现出来:
关键人物一凤姐Waiter:
/**
 * 服务员类,给烧烤师傅传达命令的
 * 
 * @author PingCX
 * 
 */
public class Waiter {

	// 服务员的名字
	private String name;
	// 内部的命令集合
	private List<Command> commands;
	// 服务员要知道仓库的情况
	private FoodDepository fDepository;
	// 服务员手上的一个小本子,记录订单日志
	private OrderLog orderLog;

	public Waiter(String name) {
		this.name = name;
		commands = new ArrayList<Command>();
		fDepository = FoodDepository.getInstance();
		orderLog = OrderLog.getInstance();
	}

	/**
	 * 添加订单
	 * 
	 * @param command
	 */
	public void addOrder(Command command) {
		String foodName = command.getFoodName();
		int num = fDepository.getFoodNum(foodName);
		String msg = command.getCustomer().getName() + "--点了一份\""
				+ command.getFoodName() + "\"(√)";
		String call = command.getCustomer().getSex().equals("男") ? "先生" : "女士";
		System.out.println(name + ":" + msg);
		if (num == 0) {
			System.out.println(name + ":抱歉," + command.getCustomer().getName()
					+ call + ",\"" + foodName + "\"已经卖完了...");
		} else {
			// 添加订单
			commands.add(command);
			// 添加日志信息
			orderLog.addLog(msg);
			// 库存减一
			fDepository.setFoodNum(foodName, num - 1);
		}
	}

	/**
	 * 取消已经点过的订单
	 * 
	 * @param command
	 */
	public void cancelOrder(Command command) {
		String foodName = command.getFoodName();
		int num = fDepository.getFoodNum(foodName);
		commands.remove(command);
		String msg = command.getCustomer().getName() + "--取消了已经点的\""
				+ command.getFoodName() + "\"一份(×)";
		// 添加日志信息
		orderLog.addLog(msg);
		// 库存加一
		fDepository.setFoodNum(command.getFoodName(), num + 1);
		System.out.println(name + ":" + msg);
	}

	/**
	 * 订单确定,全部执行
	 */
	public void confirmOrder() {
		for (Command command : commands) {
			command.executeCommand();
		}
	}

	/**
	 * 获取日志信息
	 * 
	 * @return
	 */
	public OrderLog getOrderLog() {
		return orderLog;
	}
}

@首先,有个List<Command> commands = new ArrayList<Command>():,保存消费者的点菜命令。
@addOrder(Command command){...};cancelOrder(Command command){...}添加和撤销订单,由凤姐来负责写单。
@confirmOrder(){...}是订单确定之后,凤姐再统一通知犀利哥开始。
@getOrderLog();凤姐记录完毕订单后,负责提供这个订单的接口。
-----------------------------------------------------------------------------------------------------------
关键人物二烤肉者犀利哥:
/**
 * 烤肉者
 * @author PingCX
 *
 */
public class Barbecuer {

	private String name;
	
	public Barbecuer(String name) {
		this.name = name;
	}

	/**
	 * 烤鱼
	 */
	public void bakeFish() {
		System.out.println(name+":看我大显身手,香喷喷的鱼即将出炉(1只)...");
	}
	
	/**
	 * 烤鸡翅
	 */
	public void bakeChickenWing(){
		System.out.println(name+":待我挥一挥手,美味鸡翅重现(1串)...");
	}
	
	/**
	 * 炒田螺
	 */
	public void fryPaludina(){
		System.out.println(name+":南方特色,经典田螺,让你回味无穷(1盘)...");
	}
}

@当然犀利哥远远不止这三个技能,这里简化了。
----------------------------------------------------------------------------------------------------------------
将这些搞定后,就是万事俱备只欠东风了,然后就是我点菜了,Command我抽象了出来:
抽象Command类:
/**
 * 抽象命令类
 * @author PingCX
 *
 */
public abstract class Command {
	
	//要接受命令的烧烤师傅
	protected Barbecuer barbecuer;
	//食物的名字
	protected String foodName;
	//发出此命令的消费者
	protected Customer customer;
	
	public Command(Customer customer, Barbecuer barbecuer){
		this.customer = customer;
		this.barbecuer = barbecuer;
	}
	
	/**
	 * 执行命令,抽象方法
	 */
	public abstract void executeCommand();
	
	/**
	 * 一个获得食物名字的接口
	 * @return
	 */
	public String getFoodName(){
		return foodName;
	}
	
	/**
	 * 返回消费者的名字
	 * @return
	 */
	public Customer getCustomer(){
		return customer;
	}
}

@这里便于测试,简单地提供了两个方法,获得实物名称和消费者,getFoodName(){...};getCustomer(){...},这里放在父类用到了模板方法的思想,模板方法前面有介绍。
@核心方法时抽象的executeCommand();
------------------------------------------------------------------------------------------------------
接下来就是三个具体的命令类:
我最喜欢的鱼,BakeFishCommand:
/**
 * 烤鱼命令类
 * 
 * @author PingCX
 * 
 */
public class BakeFishCommand extends Command {

	public BakeFishCommand(Customer customer, Barbecuer barbecuer) {
		super(customer,barbecuer);
		foodName = "鱼";
	}

	@Override
	public void executeCommand() {
		barbecuer.bakeFish();
	}
}

那天晚上鸡翅很不错,回味无穷:
BakeChickenWingCommand:
/**
 * 烤鸡翅命令类
 * 
 * @author PingCX
 * 
 */
public class BakeChickenWingCommand extends Command {

	public BakeChickenWingCommand(Customer customer, Barbecuer barbecuer) {
		super(customer, barbecuer);
		foodName = "鸡翅";
	}

	@Override
	public void executeCommand() {
		barbecuer.bakeChickenWing();
	}
}

还有我小时候最喜欢的之一田螺,不过这次味道没有做好:
FryPaludina:
/**
 * 炒田螺命令类
 * @author PingCX
 *
 */
public class FryPaludina extends Command {

	public FryPaludina(Customer customer,Barbecuer barbecuer){
		super(customer,barbecuer);
		foodName = "田螺";
	}
	@Override
	public void executeCommand() {
		barbecuer.fryPaludina();
	}
}

差不多了,命令模式的架构已经完成,就来我们的测试了,这个测试很好玩的,拭目以待吧:
Client:
/**
 * 测试类
 * @author PingCX
 *
 */
public class Client {

	public static void main(String[] args) {
		//进货准备
		FoodDepository fDepository = FoodDepository.getInstance();
		fDepository.setFoodNum("鱼", 3);
		fDepository.setFoodNum("鸡翅", 55);
		fDepository.setFoodNum("田螺", 10);
		
		//犀利哥街头卖烧烤
		Barbecuer xiLiGe = new Barbecuer("犀利哥");
		//凤姐摇身一变助犀利哥
		Waiter fengJie = new Waiter("凤姐");
		//袁慎建俗不可耐吃排档
		Customer ysjian = new Customer("袁慎建", "男");
		//云姐也被我拖下水
		Customer mqy = new Customer("马青云", "女");
		//开始点了,我点了四只烤鱼,两串烤鸡翅,
		Command fishCommand1 = new BakeFishCommand(ysjian, xiLiGe);
		Command fishCommand2 = new BakeFishCommand(ysjian, xiLiGe);
		Command fishCommand3 = new BakeFishCommand(ysjian, xiLiGe);
		Command fishCommand4 = new BakeFishCommand(ysjian, xiLiGe);
		Command chickenWingCommand1 = new BakeChickenWingCommand(ysjian, xiLiGe);
		Command chickenWingCommand2 = new BakeChickenWingCommand(ysjian, xiLiGe);
		//云姐点了两盘炒田螺
		Command paludinaCommand1 = new FryPaludina(mqy, xiLiGe);
		Command paludinaCommand2 = new FryPaludina(mqy, xiLiGe);
		//凤姐开始写菜单了
		System.out.println("----------凤姐记订单----------");
		fengJie.addOrder(fishCommand1);
		fengJie.addOrder(fishCommand2);
		fengJie.addOrder(fishCommand3);
		//这盘没有成功,凤姐告诉我已经没有了
		fengJie.addOrder(fishCommand4);
		fengJie.addOrder(paludinaCommand1);
		fengJie.addOrder(paludinaCommand2);
		//云姐告诉凤姐田螺点多了,取消一盘
		fengJie.cancelOrder(paludinaCommand2);
		
		fengJie.addOrder(chickenWingCommand1);
		fengJie.addOrder(chickenWingCommand2);
		System.out.println("\n----------犀利哥登场-----------");
		//菜单确定,犀利哥开始表演
		fengJie.confirmOrder();
		System.out.println("----------犀利哥谢幕-----------\n");
		//展示订单日志信息
		System.out.println("------------订单日志------------");
		fengJie.getOrderLog().displayLog();
	}
}
@先来看看结果:
----------凤姐记订单----------
凤姐:袁慎建--点了一份"鱼"(√)
凤姐:袁慎建--点了一份"鱼"(√)
凤姐:袁慎建--点了一份"鱼"(√)
凤姐:袁慎建--点了一份"鱼"(√)
凤姐:抱歉,袁慎建先生,"鱼"已经卖完了...
凤姐:马青云--点了一份"田螺"(√)
凤姐:马青云--点了一份"田螺"(√)
凤姐:马青云--取消了已经点的"田螺"一份(×)
凤姐:袁慎建--点了一份"鸡翅"(√)
凤姐:袁慎建--点了一份"鸡翅"(√)

----------犀利哥登场-----------
犀利哥:看我大显身手,香喷喷的鱼即将出炉(1只)...
犀利哥:看我大显身手,香喷喷的鱼即将出炉(1只)...
犀利哥:看我大显身手,香喷喷的鱼即将出炉(1只)...
犀利哥:南方特色,经典田螺,让你回味无穷(1盘)...
犀利哥:待我挥一挥手,美味鸡翅重现(1串)...
犀利哥:待我挥一挥手,美味鸡翅重现(1串)...
----------犀利哥谢幕-----------

------------订单日志------------
1:2013-04-04 星期四 07:50:19-->袁慎建--点了一份"鱼"(√)
2:2013-04-04 星期四 07:50:19-->袁慎建--点了一份"鱼"(√)
3:2013-04-04 星期四 07:50:19-->袁慎建--点了一份"鱼"(√)
4:2013-04-04 星期四 07:50:19-->马青云--点了一份"田螺"(√)
5:2013-04-04 星期四 07:50:19-->马青云--点了一份"田螺"(√)
6:2013-04-04 星期四 07:50:19-->马青云--取消了已经点的"田螺"一份(×)
7:2013-04-04 星期四 07:50:19-->袁慎建--点了一份"鸡翅"(√)
8:2013-04-04 星期四 07:50:19-->袁慎建--点了一份"鸡翅"(√)
@开始点的时候,我点了四只鱼,凤姐告诉我没有了,所以没有记录日志。

一顿美味排档吃完了,学会了命令模式...

我们稍作歇息,准备打道回府,我有点晕了,云姐意犹未尽地给我讲命令模式的优点:
命令模式的优点: 可以方便的将一系列命令排成一个队列,并且记录日志,还可以进行撤销动作,在请求得不到满足的时候可以互相协商,提高效率。具有很高的扩展性,如果有人还要吃别的,可以增减命令而不影响其他的类,同时很好地降低了耦合性,因为它将请求一个操作的对象与知道怎么执行一个操作的对象分离开来
我还沉浸在命令模式的优美之中,云姐还补充到: 只有当系统真正需要如撤销/恢复操作等功能时,再把原来的代码进行重构才有意义

上面的设计是为了符合云姐的描述,设计出来的命令模式代码多了几个类,回来了,我自己画了一个核心类图:


尾声:不论你职位有多高,跟人一起讨论时,不要用命令的口吻,严于律己,宽以待人...毕业季,我的大排档,大俗中有大雅,大雅中点缀大俗...














 

posted @ 2013-04-05 00:10  坚固66  阅读(229)  评论(0编辑  收藏  举报