一、命令模式
命令模式(Command Pattern)是一种对象行为型模式,别名为动作(Action)、事务(Transactino)。
定义:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。
使用频率:4星
模式结构:
命令模式包含以下主要角色:
1、抽象命令类(Command)角色:声明执行命令的接口,拥有执行命令的抽象方法 execute()。
2、具体命令类(Concrete Command)角色:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
3、实现者/接收者(Receiver)角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者。
4、调用者/请求者(Invoker)角色:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。
命令模式结构图:
优点:
1、通过引入中间件(抽象接口)降低系统的耦合度。
2、扩展性良好,增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,且满足“开闭原则”。
3、可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
4、方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
5、可以在现有命令的基础上,增加额外功能。比如日志记录,结合装饰器模式会更加灵活。
缺点:
1、可能产生大量具体的命令类。因为每一个具体操作都需要设计一个具体命令类,这会增加系统的复杂性。
2、命令模式的结果其实就是接收方的执行结果,但是为了以命令的形式进行架构、解耦请求与实现,引入了额外类型结构(引入了请求方与抽象命令接口),增加了理解上的困难。不过这也是设计模式的通病,抽象必然会额外增加类的数量,代码抽离肯定比代码聚合更加难理解。
应用场景:
1、请求调用者需要与请求接收者解耦时,命令模式可以使调用者和接收者不直接交互。
2、系统随机请求命令或经常增加、删除命令时,命令模式可以方便地实现这些功能。
3、当系统需要执行一组操作时,命令模式可以定义宏命令来实现该功能。
4、当系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作时,可以将命令对象存储起来,采用备忘录模式来实现。
二、命令模式实例之公告板系统
1.实例说明
开发一个基于windows平台的公告板系统,系统提供一个主菜单(Menu),在主菜单中包含一些菜单项(MenuItem),可以通过Menu类的addMenuItem方法增加菜单项。菜单项的主要方法是click,每一个菜单项包含以抽象命令类,具体命令类包括OpenCommand、CreateCommand、EditCommand等,命令类具体有一个execute方法,用于调用公共板系统界面类(BoardScreen)的open、create、edit方法。现使用命令模式设计该系统,使得MenuItem类与BoardScreen类的耦合度降低。
2.实例类图
3.实例代码
本例中BoardScreen充当接收者角色,MenuItem充当调用者角色,Command充当抽象命令角色,OpenCommand、CreateCommand和EditCommand充当具体命令角色。
import java.util.*;
//抽象命令
interface Command{
public void execute();
}
//菜单项类:请求发送者(调用者)
class MenuItem{
private String name;
private Command command;
public MenuItem(String name){
this.name=name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Command getCommand() {
return command;
}
public void setCommand(Command command) {
this.command = command;
}
public void click(){
command.execute();
}
}
//菜单类
class Menu{
public ArrayList<MenuItem> itemList = new ArrayList<>();
public void addMenuItem(MenuItem item){
itemList.add(item);
}
}
//打开命令:具体命令
class OpenCommand implements Command{
private BoardScreen screen;
public OpenCommand(BoardScreen screen){
this.screen = screen;
}
@Override
public void execute() {
screen.open();
}
}
//新建命令:具体命令
class CreateCommand implements Command{
private BoardScreen screen;
public CreateCommand(BoardScreen screen){
this.screen = screen;
}
@Override
public void execute() {
screen.create();
}
}
//编辑命令:具体命令
class EditCommand implements Command{
private BoardScreen screen;
public EditCommand(BoardScreen screen){
this.screen = screen;
}
@Override
public void execute() {
screen.edit();
}
}
//公共板系统界面:接收者
class BoardScreen{
private Menu menu;
private MenuItem openItem,createItem,editItem;
public BoardScreen(){
menu=new Menu();
openItem=new MenuItem("打开");
createItem=new MenuItem("新建");
editItem=new MenuItem("编辑");
menu.addMenuItem(openItem);
menu.addMenuItem(createItem);
menu.addMenuItem(editItem);
}
public void display(){
System.out.println("主菜单选项:");
for (MenuItem menuItem : menu.itemList) {
System.out.println(menuItem.getName());
}
}
public void open(){
System.out.println("显示打开窗口!");
}
public void create(){
System.out.println("显示新建窗口!");
}
public void edit(){
System.out.println("显示编辑窗口!");
}
public Menu getMenu(){
return menu;
}
}
客户端测试:
public class Client {
public static void main(String[] args) {
BoardScreen screen = new BoardScreen();//接收者
Menu menu=screen.getMenu();
Command openCommand,createCommand,editCommand;//命令
openCommand=new OpenCommand(screen);
createCommand=new CreateCommand(screen);
editCommand=new EditCommand(screen);
MenuItem openItem,createItem,editItem;//调用者
openItem=menu.itemList.get(0);
createItem=menu.itemList.get(1);
editItem=menu.itemList.get(2);
openItem.setCommand(openCommand);
createItem.setCommand(createCommand);
editItem.setCommand(editCommand);
screen.display();
openItem.click();
createItem.click();
editItem.click();
}
}
运行结果:
主菜单选项:
打开
新建
编辑
显示打开窗口!
显示新建窗口!
显示编辑窗口!
只需要在调用者MenuItem中注入不同的具体命令类,可以使得相同的菜单项MenuItem对应接收者BoardScreen的不同方法。无须修改类库代码,只需修改客户端代码即可更换接收者。
参考文献
【1】命令模式(详解版)
【2】设计模式实训教程(第2版) 刘伟 编著 清华大学出版社