(十二)命令模式详解(故事版)
作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可。
背景:小左是魔都某公司技术部的一名屌丝程序猿,每天的工作就是维护一个20世纪的古董级项目,由于公司不大,所以公司很多制度不太完善,导致小左每天都郁闷异常,只是靠偶尔的在刚毕业的小小美女程序媛旁边露一手,来丰富自己的精神生活。
某一天下午一点半,阴。
小左正趴在桌子上,迷迷糊糊想着某些个不切实际的美梦,突然QQ上“滴滴滴”的声音,将本就只是半梦半醒的小左给吵醒了。无奈之下,小左只好懒懒散散的抬起头,打开了QQ消息。
业务人员A:“小左啊,我们的XX列表需要加一个显示项和一个搜索项,你看你啥时候能做完啊,这个很急。”
小左眯缝着眼一看,心里小小一盘算,不算太复杂,于是就匆匆的回复了。
小左:“这个啊,很快,下午就搞定。”
于是小左摩拳擦掌的就开始准备下手,结果eclipse还没打开,QQ就又响起来了。
业务人员B:“小左啊,我们的XX页面需要改一下啊,得多添几个保存的项,你看你啥时候能做完,给我个时间?”
心里嘀嘀咕咕的盘算一下,小左就回复到:“大概需要大半天吧,明天搞定成不?”
业务人员B:“不行啊,这个很急啊,最好今天就做完,你看你加加班,搞定它,没问题吧?”
小左心里一边骂娘,一边回复到:“好吧,我尽快。”
关了QQ窗口,小左心里开始犯嘀咕:“这下活多了,得赶紧做,争取一下,一下午做两个功能差不多,幸亏自己在给完成时间时多加了一点。”
刚要下手,小左的QQ又响了起来。
业务人员C:“小左啊,我刚带一个客户来做业务,我不小心把东西填错了,你看你能不能给我改一下,客户在这等着呢,着急啊。”
小左心里已经开始抓狂了,但还是淡定的回复到:“好吧,我看看。”
不过刚连上生产环境的数据库,select语句还没写完,小左的QQ又响起来了。
业务人员D:“小左啊,我们的系统为什么XX列表点搜索不好用啊,你赶紧把这个问题解决啊,这么简单的系统怎么这么多问题啊。”
小左此时已经彻底被激怒,一个程序猿最忍受不了的就是被不懂程序的人指指点点,于是没好气的回复到:“尽量吧,这会太忙,没空。”
这一下,估计是刚毕业的小美女听到了不断的QQ消息声,竟然主动和小左搭话。
小甜甜:“噢爸(韩语),你好像很忙啊,要不要小妹帮帮你啊。”
小左:“没事,你刚来公司没多久,业务还不太熟悉,这点事我分分钟就搞定。”
说完,小左还扬起嘴角,摆了个超自信的表情,简直是猥琐到极点。
不过这一招对付刚毕业的单纯小姑娘还真好使,只见小甜甜一脸的崇拜相,笑着回道:“你好棒哦,谢谢噢爸的照顾啊。”
这不吹牛不要紧,苦逼的小左愣是独自加班到夜里十一点半,把今天接到的任务都给搞定了,为的就是在小甜甜面前证明自己的能力。这血淋淋的事实说明了,屌丝的生活中,随便一个有点姿色的美女都足以秒杀屌丝,并且让其卖命。
托着疲惫的身体回到家中,小左到了家里就往床上一躺,衣服袜子扔的满屋子都是,也懒得洗脸刷牙,就准备睡觉了。
忽然小左猛地坐起来,自言自语到:“对啊,今晚该写博客了,好久没写了,最近都忙的不行了。”
想到这些,小左觉得刚才的困意一瞬间就消失无踪,这也算是屌丝的一大气质了吧---夜里欢。
随着电脑缓慢的启动,小左的脑子中就开始飘荡着今天的事,“由于公司的制度不完善,人员缺乏,导致业务人员一有什么事情,全部都来找自己。本来自己就已经身兼数职,现在还要每天面临业务员们的各种炮轰,实在是有点受不了啊。”
于是,小左想着想着,就打算将这个问题写出来,看能不能从代码里找到现实中解决问题的答案。
在键盘前犹豫片刻后,小左先把自己用JAVA代码解释了一遍。
package com.command; public class Programmer { private String name; public Programmer(String name) { super(); this.name = name; } public String getName() { return name; } public void handleDemand(){ System.out.println( name + "处理新需求"); } public void handleBug(){ System.out.println( name + "处理bug"); } public void handleProblem(){ System.out.println( name + "处理线上问题"); } }
看着自己的功能,小左不禁感叹,“自己就是一个机器啊”。感叹之余,小左又继续摸着下巴思考了片刻,写出了业务人员的类。
package com.command; public class Salesman { private String name; public Salesman(String name) { super(); this.name = name; } public String getName() { return name; } public void putDemand(Programmer programmer){ System.out.println( "业务员" + name + "提出新需求"); programmer.handleDemand(); } public void putBug(Programmer programmer){ System.out.println( "业务员" + name + "提出bug"); programmer.handleBug(); } public void putProblem(Programmer programmer){ System.out.println( "业务员" + name + "提出线上问题"); programmer.handleProblem(); } }
最后,小左就干脆拿今天的工作为例,写了一下一天的工作。
package com.command; public class Work{ public static void main(String[] args) { Programmer xiaozuo = new Programmer("小左"); Salesman salesmanA = new Salesman("A"); salesmanA.putDemand(xiaozuo); Salesman salesmanB = new Salesman("B"); salesmanB.putDemand(xiaozuo); Salesman salesmanC = new Salesman("C"); salesmanC.putBug(xiaozuo); Salesman salesmanD = new Salesman("D"); salesmanD.putProblem(xiaozuo); } }
看着运行的结果,小左不禁发出一声冷笑,暗自为自己感到悲哀。仔细的看着代码,小左开始一一分析自己的悲哀。
1,业务员和自己的耦合度太高,导致每个业务人员都可以直接命令自己,这导致自己心里很不爽。
2,由于自己本身只是个程序猿,所以自己对公司的业务并不是特别擅长,很难给任务制定优先级,给任务排序,自己所擅长的还是码代码,结果造成的后果就是经常按时完成了一个不重要的任务,但不小心却将很重要的任务向后推迟了,如果业务人员告状,自己可能就要挨批。可是这么多业务人员提问题,到底谁轻谁重,我哪知道。
3,如果任务堆积过多的时候,自己有时会忘记其中的一个甚至几个,人毕竟精力有限,如果任务多了,难免会出现这种情况,结果有的业务人员就直接告状到老板那里,实在是憋屈。
4,由于任务是随即产生的,业务人员什么时候想到任务就随时给自己,所以没有一个整体的规划,很容易导致自己加班。
“看来,说来说去,还是业务人员跟我太紧密了啊。”小左叹气道。
由于小左最近在研究设计模式,所以就想到了是不是可以用设计模式来解决自己和业务人员之间的问题。于是小左开始在度娘上面找寻适合的设计模式。
“有了,命令模式应该是可以解决的,看它的定义好像挺符合的”。电脑前的小左突然一拍大腿,大叫道。
定义:在软件系统中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合,比如要对行为进行“记录、撤销/重做、事务”等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将“行为请求者”与“行为实现者”解耦?将一组行为抽象为对象,实现二者之间的松耦合。这就是命令模式(Command Pattern)。
看着这个定义,小左心中想道:“命令模式中所说的两个角色,不正是我和业务人员吗。业务人员是行为请求者,他们请求我,噢,不,应该说命令我产生编码,修改bug和处理线上问题的行为,而我就去实现或者说执行这些行为。况且,看命令模式的定义,还可以支持记录,我正需要这个记录啊,否则每次任务太多,忘了哪个任务,都要我挨批。”
于是小左迫不及待的开始研究命令模式的类图,试图从中找到解决的办法。
“不对啊,这个模式当中好像有一个invoker(调用者),这会是什么样的人呢?”小左看着命令模式的类图,不禁有些疑惑。
小左看到类图的时候,一下就看出自己便是Receiver,而业务人员便是Client,可是这个Invoker是什么呢?从类图上看,小左知道,这应该是一个特别的人,它是专门用来记录业务人员提出的需求,bug以及线上问题等等,并且还要负责通知我完成各个任务。
“我擦,这不就是产品经理(ProductManager)吗。”小左脑子中忽然闪出这个名词。
“这下有了,产品经理负责接收业务人员的各个任务,然后过滤和排好优先级以后再交代给我完成,并且他可以记住所有接受过的任务,这样我也不会再忘记了,因为有他提醒着我呢。”
说干就干,小左立马就开始尝试用命令模式去处理自己和业务人员的紧耦合问题,而解决这个问题最直接的手段就是添加了一个产品经理,并且将各个任务都抽象成一个对象,这样产品经理就可以管理这些对象了,或者也可以说是管理这些任务。
小左觉得自己的类是不需要变化的,主要变化的是要添加一组抽象的行为对象,还有改变客户端调用的方式,也就是业务人员的类。
那么首先是添加命令接口和具体的命令,不过这里似乎叫任务(Task)更合适,下面就写。
package com.command; public interface Task { void handle(); }
快速的写出这个接口,小左脑子里开始思考具体的命令应该是哪几个,而且具体的命令是要关联一个接受者的,而接受者正是自己,也就是Programmer。
“有了,应该有这几个具体的命令。”有了思路,小左就开始疯狂的敲打键盘码代码,写出了下面几个具体的任务。
package com.command; public class Demand implements Task{ private Programmer programmer; public Demand(Programmer programmer) { super(); this.programmer = programmer; } public void handle() { programmer.handleDemand(); } public String toString() { return "Demand [programmer=" + programmer.getName() + "]"; } }
package com.command; public class Bug implements Task{ private Programmer programmer; public Bug(Programmer programmer) { super(); this.programmer = programmer; } public void handle() { programmer.handleBug(); } public String toString() { return "Bug [programmer=" + programmer.getName() + "]"; } }
package com.command; public class Problem implements Task{ private Programmer programmer; public Problem(Programmer programmer) { super(); this.programmer = programmer; } public void handle() { programmer.handleProblem(); } public String toString() { return "Problem [programmer=" + programmer.getName() + "]"; } }
“这下妥了,这些任务包括了新的需求,bug和线上问题等等这些种类,而接受者正是我自己,或者说是程序猿,所以handle方法里,应该使用程序猿处理这个任务。”顿了一下,小左继续自言自语道:“这下就差一个最主要的角色,产品经理了,我来试着写一下。”
package com.command; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class ProductManager { private static final int TASK_NUMBER_IN_DAY = 4;//一天最多分派掉四个任务,多了推到第二天 private List<Task> taskList; public ProductManager() { super(); taskList = new ArrayList<Task>(); } //接受一个任务 public void receive(Task task){ taskList.add(task); } //分配给程序猿任务,督促程序猿完成 public void assign(){ Task[] copy = new Task[taskList.size() > TASK_NUMBER_IN_DAY ? taskList.size() - TASK_NUMBER_IN_DAY : 0]; for (int i = 0; i < TASK_NUMBER_IN_DAY && i < taskList.size(); i++) { taskList.get(i).handle(); } System.arraycopy(taskList.toArray(), TASK_NUMBER_IN_DAY > taskList.size() ? taskList.size() : TASK_NUMBER_IN_DAY, copy, 0, copy.length); taskList = Arrays.asList(copy); } //打印剩下的任务 public void printTaskList(){ if (taskList == null || taskList.size() == 0) { System.out.println("----------当前无任务--------"); return; } System.out.println("---------当前剩下的任务列表--------"); for (Task task : taskList) { System.out.println(task); } System.out.println("----------------------------------"); } }
“仔细想想,产品经理负责的任务还挺多呢,比如优先级排序,任务过滤,需求转化等等,怪不得产品经理要比程序猿工资高呢。不过貌似全部写出来有点复杂啊,就让他负责记录下任务,然后分配下任务吧。”小左皱着眉头自言自语道。
“不过这样的话,业务员也应该变一下了,他是要认识产品经理的。”
package com.command; public class Salesman { private String name; public Salesman(String name) { super(); this.name = name; } public String getName() { return name; } public void putDemand(ProductManager productManager,Programmer programmer){ System.out.println( "业务员" + name + "提出新需求"); productManager.receive(new Demand(programmer)); } public void putBug(ProductManager productManager,Programmer programmer){ System.out.println( "业务员" + name + "提出bug"); productManager.receive(new Bug(programmer)); } public void putProblem(ProductManager productManager,Programmer programmer){ System.out.println( "业务员" + name + "提出线上问题"); productManager.receive(new Problem(programmer)); } }
“这下好了,业务人员再也不能让我直接去写代码了,必须得发给产品经理,再让产品经理分配给我。这下我看下我的工作应该是什么样子了。”
package com.command; public class Work { public static void main(String[] args) { Programmer xiaozuo = new Programmer("小左"); ProductManager productManager = new ProductManager(); Salesman salesmanA = new Salesman("A"); Salesman salesmanB = new Salesman("B"); Salesman salesmanC = new Salesman("C"); Salesman salesmanD = new Salesman("D"); salesmanA.putDemand(productManager, xiaozuo); salesmanB.putDemand(productManager, xiaozuo); salesmanB.putBug(productManager, xiaozuo); salesmanC.putDemand(productManager, xiaozuo); salesmanC.putProblem(productManager, xiaozuo); salesmanD.putDemand(productManager, xiaozuo); System.out.println("第一天产品经理分配任务"); productManager.assign(); productManager.printTaskList(); System.out.println("第二天产品经理分配任务"); productManager.assign(); productManager.printTaskList(); } }
小左看着运行结果和代码开始思考,“这样和标准的命令模式几乎已经一样了,只是设计模式还是不能死搬硬套啊,有些地方还是有些别扭。比如业务人员不应该再依赖于程序猿,业务人员应该只依赖产品经理就可以了,他不应该负责把任务对应给我,而应该是产品经理决定某一个任务由谁去处理的。我还得把这个地方再改一下,要不按照现在的样子,业务人员还是能把任务分配到我身上。不过目前的好处是,我不需要当时就做了,而是等着产品经理分配。从结果就看出来了,刚才是业务人员发个任务我就做一个,现在是产品经理分配了一天的任务,然后一起做。”
“按照刚才想的思路,产品经理应该要认识所有的程序猿,而且还要提供一个可以选择程序猿的方法。”小左拖着下巴喃喃的说道,说完以后就开始修改刚才的代码。
package com.command; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class ProductManager { private static final int TASK_NUMBER_IN_DAY = 4;//一天最多分派掉四个任务,多了推到第二天 private List<Task> taskList; private List<Programmer> programmerList;//产品经理应该认识所有的程序猿 private int currentIndex; public ProductManager(Programmer... programmers) { super(); if (programmers == null || programmers.length == 0) { throw new RuntimeException("产品经理手下没有程序员,任务分配不出去,无法正常工作!"); } taskList = new ArrayList<Task>(); programmerList = Arrays.asList(programmers); } //接受一个任务 public void receive(Task task){ taskList.add(task); } public void assign(){ Task[] copy = new Task[taskList.size() > TASK_NUMBER_IN_DAY ? taskList.size() - TASK_NUMBER_IN_DAY : 0]; for (int i = 0; i < TASK_NUMBER_IN_DAY && i < taskList.size(); i++) { taskList.get(i).handle(); } System.arraycopy(taskList.toArray(), TASK_NUMBER_IN_DAY > taskList.size() ? taskList.size() : TASK_NUMBER_IN_DAY, copy, 0, copy.length); taskList = Arrays.asList(copy); } //产品经理可以选择程序猿,简单的循环选取。 public Programmer chooseProgrammer(){ return programmerList.get(currentIndex == programmerList.size() ? 0 : currentIndex++); } public void printTaskList(){ if (taskList == null || taskList.size() == 0) { System.out.println("----------当前无任务--------"); return; } System.out.println("---------当前剩下的任务列表--------"); for (Task task : taskList) { System.out.println(task); } System.out.println("----------------------------------"); } }
“这下业务人员就不需要认识我了,哈哈。”小左边YY着有了产品经理的美好生活,一边将业务人员的类改成了下面的样子。
package com.command; public class Salesman { private String name; private ProductManager productManager; public Salesman(String name) { super(); this.name = name; } public Salesman(String name, ProductManager productManager) { super(); this.name = name; this.productManager = productManager; } public void putDemand(){ System.out.println( "业务员" + name + "提出新需求"); productManager.receive(new Demand(productManager.chooseProgrammer())); } public void putBug(){ System.out.println( "业务员" + name + "提出bug"); productManager.receive(new Bug(productManager.chooseProgrammer())); } public void putProblem(){ System.out.println( "业务员" + name + "提出线上问题"); productManager.receive(new Problem(productManager.chooseProgrammer())); } public String getName() { return name; } public ProductManager getProductManager() { return productManager; } public void setProductManager(ProductManager productManager) { this.productManager = productManager; } }
这下一天的工作形式就变化了,小左将一天的工作写成了下面这个样子。
package com.command; public class Work { public static void main(String[] args) { Programmer xiaozuo = new Programmer("小左"); ProductManager productManager = new ProductManager(xiaozuo); Salesman salesmanA = new Salesman("A",productManager); Salesman salesmanB = new Salesman("B",productManager); Salesman salesmanC = new Salesman("C",productManager); Salesman salesmanD = new Salesman("D",productManager); salesmanA.putDemand(); salesmanB.putDemand(); salesmanB.putBug(); salesmanC.putDemand(); salesmanC.putProblem(); salesmanD.putDemand(); System.out.println("第一天产品经理分配任务"); productManager.assign(); productManager.printTaskList(); System.out.println("第二天产品经理分配任务"); productManager.assign(); productManager.printTaskList(); } }
“哈哈,这下业务人员彻底不认识我了,只认识产品经理,只不过由于公司就我一个程序员,所以产品经理那个平均的循环分配算法,分来分去还是分给我一个人了。不过这个也没办法,公司不招人,我也说了不算啊,而且小甜甜还没熟悉好公司业务呢,也帮不了我。”
虽然最终这些任务都还是小左去做,但是这下小左不用加班了,一天最多四个任务,就算完不成临时需要加班,那也是小左编码速度的问题,心里相对会舒服很多,不至于太憋屈。毕竟自己没完成,那加会儿班也会心甘情愿。
这会小左心情好了,心情一好,小左竟然开始动手画起刚才的类图来了。
小左看着自己的作品,自言自语道:“虽然这个实际的类图比命令模式的原版类图稍微多了一层关系,就是ProductManager和Programmer的关联关系,但是其它的可是和命令模式一模一样啊,而且很明显,业务人员(Salesman)和我们程序猿(Programmer)之间没有关联的关系了。”
“看来我们公司应该招一个产品经理了啊,还得多招几个程序猿,这样我的工作就简单的多了。公司和程序倒是挺相似的,程序是需要多个类之间相互协作,公司也是一样啊,需要每个员工都分工协作,这样就不需要一些人身兼数职了,简直是累死个人啊!”
“博客今天就先不写了,稍微总结一下,明天好去给小甜甜说一下我今晚的发现,顺便给她讲讲命令模式。这下又可以在她面前露一手了,估计这样下去,她早晚会爱上我啊。啊,哈哈。。”小左又开始YY了。
不过为了能给小甜甜留一个好印象,小左可是煞费心思啊,这不,他又开始总结起来了。
“为了减轻自己的负担,我添加了一个产品经理,并且还将任务抽象成类,这样确实解决了我的很多问题,我得稍微总结下。”
1,程序猿和业务员解耦,不直接打交道。
2,产品经理分担了程序猿的很多潜在任务,比如制定任务优先级,先做哪个后做哪个。
3,程序猿不至于忘掉其中一个任务,因为产品经理那有任务列表的。
4,任务有规划的完成,不至于加班或者说加班太频繁。
“不过要是给小甜甜解释,这样还不够啊,得专业一点,才好忽悠啊。”小左暗暗的点头道。
用编程的语言来解释命令模式的使用场景或者说可以解决的问题,就是下面几点。
1,希望将行为请求者和行为实现者解耦,不直接打交道。
2,希望分离掉行为请求者一部分的责任,行为请求者只需要将命令发给调用者,不再主动的去让行为实现者产生行为,符合单一职责原则。
3,希望可以控制执行的命令列表,方便记录,撤销/重做以及事务等功能。
4,期待可以将请求排队,有序执行。
5,希望可以将请求组合使用。
“不过上面我总结的第五点在例子当中没有体现,不过这个也好办,其实就相当于是任务可以组合,比如一个线上的问题(Problem)很可能伴随着一个bug(Bug),这样的话可以做一个组合的任务,既要处理线上的问题同时也要处理Bug。”
“装X就要装到底啊,命令模式的优点和缺点都有哪些,我也得给总结出来,别到时候在小甜甜面前掉链子。”小左一甩自己的中分头说道。
1,最大的优点,就是将行为请求者和行为实现者解耦。
2,命令的添加特别方便,并且可以方便的制定各种命令和利用现有命令组合出新的命令。
3,如果针对每一类具有共同接口的接受者制作一个调用者,可以控制命令的执行情况。
“命令模式的缺点应该和大部分设计模式一样,会增加系统的复杂性,这里的复杂性应该主要指的是类的数量,这个倒是好理解,看一下上面的例子就知道多了很多类。”
“这下可以睡个安稳觉了,希望明天一举拿下啊。啊哈。”说完,小左就躺床上进入了梦乡。