命令模式

1、概述

将一个请求封装为一个对象。命令模式允许系统使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。

2、模式分析

在一次请求中,存在请求的调用者和执行者,一般情况下调用者会直接调用执行者的API,这样两者紧密的耦合在一起。如下图

现在在调用者和执行者之间添加一个对象,用来封装请求的命令。由该对象调用执行者的API,进而使调用者和执行者独立开来。这样,调用者只需要指定具体的请求命令即可,不需要知道请求时如何被接受、操作是否被执行,以及是如何执行的。

调用者只针对命令接口编程,不针对执行者的API编程

3、模式角色

命令接口:定义命令的接口,声明执行方法。

具体命令:具体命令,实现命令接口,实现命令要执行的方法。成员中含有执行者,以及命令操作的相关参数,在命令执行方法中调用执行者的API

执行者:真正实行命令的对象,含有命令执行的详细API

调用者:持有命令对象,调用者通过调用命令对象的方法,进而实现执行者API的调用。

客户端:创建具体的命令对象,并且设置命令对象的接收者

 

4、优点与缺点

优点:

  • 降低了系统的耦合度;
  • 新的命令可以很容易的加入到系统中;
  • 很容易地设计成一个命令队列;
  • 很容易地实现对请求的撤销和恢复;
  • 在需要的情况下,可以容易地将命令记入日志。

缺点:

导致系统有过多的具体命令类,增加了系统的复杂度。

5、应用示例

以ATM上的操作为例,对银行卡账户上的存款进行操作,包括查询、存款、取款等,其类图如下:

 

Account(执行者,银行卡账户)

 1 /**
 2  * 银行账户实体类,可以执行查询、支取、存款等操作命令
 3  * @author hjy
 4  */
 5 public class Account {
 6     private BigDecimal decimal;
 7     
 8     //无参构造函数,账户金额初始为0
 9     public Account(){
10         this.decimal = new BigDecimal(0);
11     }
12     //有参构造方法,账户金额为给定值
13     public Account(int initNum){
14         this.decimal = new BigDecimal(initNum);
15     }
16     
17     //查询操作
18     public void query(){
19         //return decimal;
20         Double out = decimal.setScale(2).doubleValue();
21         System.out.println("当前账户余额为: " + out);
22     }
23     
24     //存款操作
25     public void inMonery(double in){
26         decimal = decimal.add(new BigDecimal(in)); 
27     }
28     //取款操作
29     public void outMonery(double out){
30         BigDecimal outMonery = new BigDecimal(out);
31         if(outMonery.compareTo(decimal) == 1){
32             System.out.println("余额不足!!!");
33             return;
34         }
35         decimal = decimal.subtract(outMonery);
36     }
37 }

AccountCommand命令接口

1 /**
2  * 银行账户操作命令接口
3  * @author hjy
4  */
5 public interface AccountCommand {
6     //命令执行操作
7     public void execute();
8 }

AccountQueryCommand查询命令

 1 /**
 2  * 查询操作命令的实现类
 3  * @author hjy
 4  */
 5 public class AccountQueryCommand implements AccountCommand {
 6     private Account account;
 7     
 8     //构造查询命令对象时,只需要指定操作的账号即可
 9     public AccountQueryCommand(Account account){
10         this.account = account;
11     }
12     //执行查询命令
13     @Override
14     public void execute() {
15         account.query();
16     }
17 }

AccountInCommand存款命令

 1 /**
 2  * 存款操作命令实现类
 3  * @author hjy
 4  */
 5 public class AccountInCommand implements AccountCommand {
 6     private Account account;
 7     private double in;
 8     
 9     //构造存款命令对象时,需要执行具体的账号及存款金额
10     public AccountInCommand(Account account, double in){
11         this.account = account;
12         this.in = in;
13     }
14     //执行存款命令
15     @Override
16     public void execute() {
17         account.inMonery(in);
18     }
19 }

AccountOutCommand取款命令

 1 /**
 2  * 取款操作实现类
 3  * @author hjy
 4  */
 5 public class AccountOutCommand implements AccountCommand {
 6     private Account account;
 7     private double out;
 8     
 9     //构造取款命令对象时,需要执行具体的账号及存款金额
10     public AccountOutCommand(Account account, double out){
11         this.account = account;
12         this.out = out;
13     }
14     //执行取款命令
15     @Override
16     public void execute() {
17         account.outMonery(out);
18     }
19 }

AccountRequest(调用者)

 1 /**
 2  * 账户操作请求的实体类,每一个对象实际是最一条请求命令的封装
 3  * @author hjy
 4  */
 5 public class AccountRequest {
 6     //封装操作的命令
 7     private AccountCommand command;
 8     
 9     public AccountRequest(){}
10     public void setCommand(AccountCommand command) {
11         this.command = command;
12         this.undoCommand = command;
13     }
14     
15     //执行命令
16     public void executeCommand(){
17         if(command == null){
18             System.out.println("请指定具体的操作命令!!!");
19             return;
20         }
21         command.execute();
22     }
23 }

Client(客户端)

/**
 * 账户操作的客户端
 * @author hjy
 */
public class AccountClient {
    public static void main(String[] args) {
        //定义一个账户
        Account account = new Account(2000);
        //定义一个账户操请求的对象
        AccountRequest accountRequest = new AccountRequest();

        //定义一个查询命令
        AccountCommand queryCommand = new AccountQueryCommand(account);
        //先来一次查询(必须先执行要请求的操作命令)
        accountRequest.setCommand(queryCommand);
        accountRequest.executeCommand();
        
        //执行存款100元操作
        AccountCommand moneryInCommand = new AccountInCommand(account, 100);
        accountRequest.setCommand(moneryInCommand);
        accountRequest.executeCommand();
        
        //先来一次查询(必须先执行要请求的操作命令)
        accountRequest.setCommand(queryCommand);
        accountRequest.executeCommand();
        
        //执行取款1000元操作
        AccountCommand moneyOutCommand = new AccountOutCommand(account, 1000);
        accountRequest.setCommand(moneyOutCommand);
        accountRequest.executeCommand();
        
        //先来一次查询(必须先执行要请求的操作命令)
        accountRequest.setCommand(queryCommand);
        accountRequest.executeCommand();
        
        //再执行一次取款操作,取款金额为1500元
        moneyOutCommand = new AccountOutCommand(account, 1500);
        accountRequest.setCommand(moneyOutCommand);
        accountRequest.executeCommand();
        
        //先来一次查询(必须先执行要请求的操作命令)
        accountRequest.setCommand(queryCommand);
        accountRequest.executeCommand();
    }
}

执行结果

当前账户余额为: 2000.0
当前账户余额为: 2100.0
当前账户余额为: 1100.0
余额不足!!!
当前账户余额为: 1100.0

改进

可以在命令接口中添加撤销操作,每个具体命令类实现该方法,同时在调用者中添加一个成员变量来记录最近执行的命令即可。

posted @ 2017-12-01 16:55  simple-clean-opt  Views(214)  Comments(0Edit  收藏  举报