设计模式学习(一)——命令模式

命令模式定义

命令模式(Command Pattern)将一个 [ 请求 | 命令 | 调用方法 ] 封装为一个对象,从而可用不同的 [ 请求 | 命令 | 调用方法 ] 对客户进行参数化,对 [ 请求 | 命令 | 调用方法 ] 排队或记录其日志,以及支持可撤销的操作。

命令模式可以对发送者(Sender)和接收者(Receiver)完全解耦(Decoupling)发送者是请求操作(发出命令)的对象,接收者是接收请求(接收命令)并执行某相应操作的对象。


命令模式结构图

图中:

  • Command表示抽象命令类,它用于声明执行操作的一个接口;

  • ConcreteCommand表示具体命令类,它将一个接收者对象绑定于一个动作,实现在Command中声明的execute()方法,调用接收者的相关操作(Action);

  • Client表示客户应用程序,创建一个具体命令类的对象,并且设定它的接收者;

  • Invoker表示调用者,要求一个命令对象执行一个请求;

  • Receiver表示接收者,它实现如何执行关联请求的相关操作。


命令模式实例——公告板系统

实例说明

某软件公司欲开发一个基于Windows平台的公告板系统。系统提供一个主菜单(Menu),在主菜单中包含了一些菜单项(MenuItem),可以通过Menu类的addMenuItem()方法增加菜单项。菜单项的主要方法是click(),每一个菜单项包含一个抽象命令类,具体命令类包括OpenCommand(打开命令)、CreateCommand(新建命令)、EditCommand(编辑命令)等,命令类具有一个execute()方法,用于调用公告板系统界面类(BoardScreen)的open()、create()、edit()等方法。现使用命令模式设计该系统,使得MenuItem类与BoardScreen类的耦合度降低,绘制类图并编程实现。

实例类图

实例类图中与命令模式结构图对应的类主要有:

  • Command为抽象命令类(即一个接口);

  • OpenCommand、CreateCommand和EditCommand为具体命令类;

  • Client为客户端测试类(未在图中画出);

  • MenuItem为调用者(也是请求发送者);

  • BoardScreen为接收者。

实例代码

Command.java:

//抽象命令
interface Command
{
public void execute();
}

MenuItem.java:

//菜单项类:请求发送者(调用者)
class MenuItem
{
private String name;
private Command command;
public MenuItem(String name)
{

this.name = name;
}
public String getName()
{

return this.name;
}
public void setName(String name)
{

this.name = name;
}
public Command getCommand()
{

return this.command;
}
public void setCommand(Command command)
{

this.command = command;
}
public void click()
{

command.execute();
}
}

Menu.java:

import java.util.*;

//菜单类
class Menu
{

public ArrayList itemList = new ArrayList();
public void addMenuItem(MenuItem item)
{
itemList.add(item);
}
}

OpenCommand.java:

//打开命令:具体命令
class OpenCommand implements Command
{

private BoardScreen screen;
public OpenCommand(BoardScreen screen)
{

this.screen = screen;
}
public void execute()
{

screen.open();
}
}

CreateCommand.java:

//新建命令:具体命令
class CreateCommand implements Command
{

private BoardScreen screen;
public CreateCommand(BoardScreen screen)
{

this.screen = screen;
}
public void execute()
{

screen.create();
}
}

EditCommand.java:

//编辑命令:具体命令
class EditCommand implements Command
{

private BoardScreen screen;
public EditCommand(BoardScreen screen)
{

this.screen = screen;
}
public void execute()
{

screen.edit();
}
}

BoardScreen.java:

//公告板系统界面:接收者
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(Object obj:menu.itemList)
{
System.out.println(((MenuItem)obj).getName());
}
}
public void open()
{

System.out.println("显示打开窗口");
}
public void create()
{

System.out.println("显示新建窗口");
}
public void edit()
{

System.out.println("显示编辑窗口");
}
public Menu getMenu()
{

return menu;
}
}

Client.java:

//客户端测试类
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 = (MenuItem)menu.itemList.get(0);
createItem = (MenuItem)menu.itemList.get(1);
editItem = (MenuItem)menu.itemList.get(2);
openItem.setCommand(openCommand);
createItem.setCommand(createCommand);
editItem.setCommand(editCommand);
screen.display();
openItem.click();
createItem.click();
editItem.click();
}
}

运行结果

附加描述

在本实例中,只需要在调用者MenuItem中注入不同的具体命令类,可以使得相同的菜单项MenuItem对应接收者BoardScreen的不同方法。无需修改类库代码,只需修改客户端代码即可更换接收者。

在实际开发时,还可以将BoardScreen中的open()、create()和edit()等方法封装在不同的类中,如果需要更换某菜单项的功能,只需对应增加一个新的具体命令类和一个接收者类,再将新的具体命令对象注入对应的MenuItem对象,即可实现菜单项功能的改变,且符合开闭原则。

注:开闭原则(Open-Closed Principle, OCP)由Bertrand Meyer提出,其定义为“Software entities should be open for extension, but closed for modification.”也就是说软件实体应对扩展开放,而对修改关闭,即软件实体应尽量在不修改原有代码的情况下进行扩展。


后话

今天花了好长时间,才对命令模式有了基本的理解。感慨设计模式学路之漫漫,愚将日积跬步而求索。