命令模式
定义
命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
主要解决
在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。
优缺点
优点
- 通过引入中间件(抽象接口)降低系统的耦合度。
- 扩展性良好,增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,且满足“开闭原则”。
- 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
- 方便实现 Undo 和 Redo 操作。
- 可以在现有命令的基础上,增加额外功能。比如日志记录,结合装饰器模式会更加灵活。
缺点
- 可能产生大量具体的命令类。因为每一个具体操作都需要设计一个具体命令类,这会增加系统的复杂性。
- 命令模式的结果其实就是接收方的执行结果,但是为了以命令的形式进行架构、解耦请求与实现,引入了额外类型结构(引入了请求方与抽象命令接口),增加了理解上的困难。不过这也是设计模式的通病,抽象必然会额外增加类的数量,代码抽离肯定比代码聚合更加难理解。
实现
主要角色
- 抽象命令类(abstract command):声明执行的接口,拥有执行命令的抽象方法execute()。
- 具体命令类(concrete command):是抽象类的具体实现类,它拥有接收者对象,并通过调用接受者的功能来完成命令要执行的操作。
- 接收者(receiver):执行命令功能的相关操作,是具体命令对象业务的真正实现者。
- 调用者(invoker):是请求的发送者,他通常拥有很多的命令对象,并通过访问命令对象来执行相关操作,它不直接访问接收者。
代码
这里以一个
- 抽象命令接口
1 2 3 4
| public interface Order { void execute(); }
|
- 具体命令类
1 2 3 4 5 6 7 8 9 10 11 12
| public class BuyStock implements Order { private Stock abcStock; public BuyStock(Stock abcStock){ this.abcStock = abcStock; } public void execute() { abcStock.buy(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12
| public class BuyStock implements Order { private Stock abcStock; public BuyStock(Stock abcStock){ this.abcStock = abcStock; } public void execute() { abcStock.buy(); } }
|
- 请求类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
public class Stock { private String name = "ABC"; private int quantity = 10; public void buy(){ System.out.println("Stock [ Name: "+name+", Quantity: " + quantity +" ] bought"); } public void sell(){ System.out.println("Stock [ Name: "+name+", Quantity: " + quantity +" ] sold"); } }
|
- 命令调用类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
import java.util.ArrayList; import java.util.List; public class Broker { private List<Order> orderList = new ArrayList<Order>(); public void takeOrder(Order order){ orderList.add(order); } public void placeOrders(){ for (Order order : orderList) { order.execute(); } orderList.clear(); } }
|
- 使用 Broker 类来接受并执行命令。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class CommandPatternDemo { public static void main(String[] args) { Stock abcStock = new Stock(); BuyStock buyStockOrder = new BuyStock(abcStock); SellStock sellStockOrder = new SellStock(abcStock); Broker broker = new Broker(); broker.takeOrder(buyStockOrder); broker.takeOrder(sellStockOrder); broker.placeOrders(); } }
|
图解
JavaScript中的命令模式
在JavaScript中,我们无须那么麻烦的创建类。由于函数在JS中作为一等公民,本身就可以作为参数进行传递。所以不一定要将其封装在execute方法中,而是直接将其作为字面量对象的成员进行传递。
比如按钮点击事件的添加:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| let bindClick = function(button, func){ button.onClick = func }
let MenuBar = { refresh: function(){ console.log('刷新子菜单') } }
let SubMenu = { add: function(){ console.log('添加子菜单') }, del: function(){ consle.log('删除子菜单') } }
bindClick(button1, MenuBar.refresh) bindClick(button2, SubMenu.add) bindClick(button3, SubMenu.del)
|
组合模式
定义
组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。
主要解决
它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
宏命令
宏命令包含了一组具体的子命令对象,不管是宏命令,还是子命令,都有一个execute方法负责执行命令。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| let CloseDoorCommand = { execute: function(){ console.log('关门') } }
let openPCCommand = { execute: function(){ consoloe.log('开电脑') } }
let openQQCommand = { execute: function(){ console.log('登录QQ') } }
let MacroCommand = function(){ return { commandList : [], add: function(command){ this.commandList.push(command) }, execute: function(){ for(let c of this,commandList){ c.execute() } } } }
let macroCommand = MacroCommand() macroCommand.add(CloseDoorCommand) macroCommand.add(openPCCommand) macroCommand.add(openQQCommand)
macroCommand.execute()
|
在组合模式中,请求在树中传递总是遵循一种逻辑。
请求从树最顶端的对象向下传递,如果当前请求的对象是叶对象(普通子命令),也对象自身会对请求做出相应的处理;如果当前请求的对象是组合对象(宏命令),组合对象则会遍历它下属的子节点,将请求传递给这些子节点。
图解