设计模式笔记--命令模式

常用设计模式有23中,分为:

创建型模式(主要用于创建对象)

1、单例模式    2、工厂方法模式    3、抽象工厂模式    4、建造者模式     5、原型模式 
行为型模式 (主要用于描述对象或类是怎样交互和怎样分配职责)

1、模板方法模式  2、中介者模式  3、命令模式    4、责任链模式   5、策略模式   6、迭代器模式  

7、观察者模式      8、备忘录模式   9、访问者模式   10、状态模式   11、解释器模式

结构型模式(主要用于处理类或对象的组合)

1、代理模式  2、装饰模式   3、适配器模式   4、组合模式   5、外观模式(门面模式)   6、享元模式    7、桥梁模式


命令模式

命令模式是一个高内聚的模式,其定义为:将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。




在该类图中,我们看到三个角色:

● Receive接收者角色
该角色就是干活的角色,命令传递到这里是应该被执行的,具体到我们上面的例子中就Group的三个实现类。

● Command命令角色
需要执行的所有命令都在这里声明。

● Invoker调用者角色
接收到命令,并执行命令。在例子中,项目经理就是这个角色。  


命令模式比较简单,但是在项目中非常频繁地使用,因为它的封装性非常好,把请求方(Invoker)和执行方(Receiver)分开了,扩展性也有很好的保障  

通用代码

Receiver类,如代码清单15-13所示。
<span style="font-size:18px;">代码清单15-13 通用Receiver类
public abstract class Receiver {
//抽象接收者,定义每个接收者都必须完成的业务
public abstract void doSomething();
}</span>


接收者可以有多个,有多个就需要定义一个所有特性的抽象集合——抽象的接收者,其具体的接收者如代码清单15-14所示。
<span style="font-size:18px;">代码清单15-14 具体的Receiver类
public class ConcreteReciver1 extends Receiver{
//每个接收者都必须处理一定的业务逻辑
public void doSomething(){
}
}</span>
<span style="font-size:18px;">public class ConcreteReciver2 extends Receiver{
//每个接收者都必须处理一定的业务逻辑
public void doSomething(){
}
}</span>


命令角色是命令模式的核心,其抽象的命令类如代码清单15-15所示。
<span style="font-size:18px;">代码清单15-15 抽象的Command类
public abstract class Command {
//每个命令类都必须有一个执行命令的方法
public abstract void execute();
}</span>


根据环境的需求,具体的命令类也可以有N个,其实现类如代码清单15-16所示。
<span style="font-size:18px;">public class ConcreteCommand1 extends Command {
//对哪个Receiver类进行命令处理
private Receiver receiver;
//构造函数传递接收者
public ConcreteCommand1(Receiver _receiver){
this.receiver = _receiver;
}
//必须实现一个命令
public void execute() {
//业务处理
this.receiver.doSomething();
}
}

</span>
<span style="font-size:18px;">public class ConcreteCommand2 extends Command {
//哪个Receiver类进行命令处理
private Receiver receiver;
//构造函数传递接收者
public ConcreteCommand2(Receiver _receiver){
this.receiver = _receiver;
}
//必须实现一个命令
public void execute() {
//业务处理
this.receiver.doSomething();
}
} </span>
定义了两个具体的命令类,读者可以在实际应用中扩展该命令类。在每个命令类中,通过构造函数定义了该命令是针对哪一个接收者发出的,定义一个命令接收的主体。


调用者非常简单,仅实现命令的传递,如代码清单15-17所示。
<span style="font-size:18px;">代码清单15-17 调用者Invoker类
public class Invoker {
private Command command;
//受气包,接受命令
public void setCommand(Command _command){
this.command = _command;
}
//执行命令
public void action(){
this.command.execute();
}
}</span>


那我们来看高层模块如何调用命令模式,如代码清单15-18所示。
<span style="font-size:18px;">代码清单15-18 场景类
public class Client {
public static void main(String[] args) {
//首先声明调用者Invoker
Invoker invoker = new Invoker();
//定义接收者
Receiver receiver = new ConcreteReciver1();
//定义一个发送给接收者的命令
Command command = new ConcreteCommand1(receiver);
//把命令交给调用者去执行
invoker.setCommand(command);
invoker.action();
}
}</span>



实例:项目经理



对类图中增加的几个类说明如下:

● Command抽象类:客户发给我们的命令,定义三个工作组的成员变量,供子类使用;定义一个抽象方法execute,由子类来实现。

● Invoker实现类:项目接头负责人,setComand接收客户发给我们的命令,action方法是执行客户的命令(方法名写成是action,与command的execute区分开,避免混淆)。


首先看抽象类Group,如代码清单15-1所示。
<span style="font-size:18px;">代码清单15-1 抽象组
public abstract class Group {
//甲乙双方分开办公,如果你要和某个组讨论,你首先要找到这个组
public abstract void find();
//被要求增加功能
public abstract void add();
//被要求删除功能
public abstract void delete();
//被要求修改功能
public abstract void change();
//被要求给出所有的变更计划
public abstract void plan();
}  </span>


美工组PageGroup类如代码清单15-3所示。
<span style="font-size:18px;">代码清单15-3 美工组
public class PageGroup extends Group {
//首先这个美工组应该能找到吧,要不你跟谁谈?
public void find() {
System.out.println("找到美工组...");
}
//美工被要求增加一个页面
public void add() {
System.out.println("客户要求增加一个页面...");
}
//客户要求对现有界面做修改
public void change() {
System.out.println("客户要求修改一个页面...");
}
//甲方是老大,要求删除一些页面
public void delete() {
System.out.println("客户要求删除一个页面...");
}
//所有的增、删、改都要给出计划
public void plan() {
System.out.println("客户要求页面变更计划...");
}
}</span>
另外两个实现类和它相似。


Command抽象类只有一个方法execute,其作用就是执行命令,子类非常坚决地实现该命令,客户发送一个删除页面的命令,接头负责人Invoker接收到命令后,立刻执行DeletePageCommand的execute方法。

Command抽象类是整个扩展的核心,其源代码如代码清单15-7所示。  
<span style="font-size:18px;">代码清单15-7 抽象命令类
public abstract class Command {
//把三个组都定义好,子类可以直接使用
protected RequirementGroup rg = new RequirementGroup(); //需求组
protected PageGroup pg = new PageGroup(); //美工组
protected CodeGroup cg = new CodeGroup(); //代码组
//只有一个方法,你要我做什么事情
public abstract void execute();
}</span>

  
具体的实现类只要实现execute方法就可以了。在一个项目中,需求增加是很常见的,那就把增加需求定义为一个命令AddRequirementCommand类,如代码清单15-8所示。
<span style="font-size:18px;">代码清单15-19 修改后的增加需求
public class AddRequirementCommand extends Command {
//执行增加一项需求的命令
public void execute() {
//找到需求组
super.rg.find();
//增加一份需求
super.rg.add();
//页面也要增加
super.pg.add();
//功能也要增加
super.cg.add();
//给出计划
super.rg.plan();
}
}</span>


 删除页面的命令 DeletePageCommand类,如代码清单15-9所示。
<span style="font-size:18px;">代码清单15-9 删除页面的命令
public class DeletePageCommand extends Command {
//执行删除一个页面的命令
public void execute() {
//找到页面组
super.pg.find();
//删除一个页面
super.rg.delete();
//给出计划
super.rg.plan();
}
}</span>

Command抽象类可以有N个子类,如增加一个功能命令(AddFunCommand),删除一份需求命令(DeleteRequirementCommand)等,只要是由客户产生、时常性的行为都可以定义为一个命令,


客户发送的命令已经确定下来,我们再看负责人Invoker,如代码清单15-10所示。
<span style="font-size:18px;">代码清单15-10 负责人
public class Invoker {
//什么命令
private Command command;
//客户发出命令
public void setCommand(Command command){
this.command = command;
}
//执行客户的命令
public void action(){
this.command.execute();
}
}</span>


负责人只要接到客户的命令,就立刻执行。我们模拟增加一项需求的过程,如代码清单15-11所示。
<span style="font-size:18px;">代码清单15-11 增加一项需求
public class Client {
public static void main(String[] args) {
//定义我们的接头人
Invoker xiaoSan = new Invoker(); //接头人就是小三
//客户要求增加一项需求
System.out.println("------------客户要求增加一项需求---------------");
//客户给我们下命令来
Command command = new AddRequirementCommand();
//接头人接收到命令
xiaoSan.setCommand(command);
//接头人执行命令
xiaoSan.action();
}
}
</span>


优点 

● 类间解耦 
调用者角色与接收者角色之间没有任何依赖关系,调用者实现功能时只需调用Command抽象类的execute方法就可以,不需要了解到底是哪个接收者执行。  

● 可扩展性  
Command的子类可以非常容易地扩展,而调用者Invoker和高层次的模块Client不产生严重的代码耦合。  

● 命令模式结合其他模式会更优秀  
命令模式可以结合责任链模式,实现命令族解析任务;结合模板方法模式,则可以减少Command子类的膨胀问题。  


缺点   

Command的子类就可不是几个,而是N个,这个类膨胀得非常大  



只要你认为是命令的地方就可以采用命令模式  

在项目中:约定的优先级最高,每一个命令是对一个或多个Receiver的封装,我们可以在项目中通过有意义的类名或命令名处理命令角色和接收者角色的耦合关系(这就是约定),减少高层模块(Client类)对低层模块(Receiver角色类)的依赖关系,提高系统整体的稳定性。因  



命令模式的扩展  

我们的Receiver角色(也就是Group的三个实现类)并没有暴露给Client,而在通用的类图和源码中却出现了Client类对Receiver角色的依赖,  这是为什么呢

命令模式的Receiver在实际应用中一般都会被封装掉(除非非常必要,例如撤销处理),那是因为在项目中:约定的优先级最高,每一个命令是对一个或多个Receiver的封装,我们可以在项目中通过有意义的类名或命令名处理命令角色和接收者角色的耦合关系(这就是约定),减少高层模块(Client类)对低层模块(Receiver角色类)的依赖关系,提高系统整体的稳定性  
因此,建议大家在实际的项目开发时采用封闭Receiver的方式,减少Client对Reciver的依赖  

对Commandd抽象类及其子类有一定的修改,  
<span style="font-size:18px;">代码清单15-22 完美的Command类
public abstract class Command {
//定义一个子类的全局共享变量
protected final Receiver receiver;
//实现类必须定义一个接收者
public Command(Receiver _receiver){
this.receiver = _receiver;
}
//每个命令类都必须有一个执行命令的方法
public abstract void execute();
}</span>

在Command父类中声明了一个接收者,通过构造函数约定每个具体命令都必须指定接收者,当然根据开发场景要求也可以有多个接收者,那就需要用集合类型。
我们来看具体命令,如代码清单15-23所示。


<span style="font-size:18px;">代码清单15-23 具体的命令
public class ConcreteCommand1 extends Command {
//声明自己的默认接收者
public ConcreteCommand1(){
super(new ConcreteReciver1());
}
//设置新的接收者
public ConcreteCommand1(Receiver _receiver){
super(_receiver);
}
//每个具体的命令都必须实现一个命令
public void execute() {
//业务处理
super.receiver.doSomething();
}
}</span>
<span style="font-size:18px;">public class ConcreteCommand2 extends Command {
//声明自己的默认接收者
public ConcreteCommand2(){
super(new ConcreteReciver2());
}
//设置新的接收者
public ConcreteCommand2(Receiver _receiver){
super(_receiver);
}
//每个具体的命令都必须实现一个命令
public void execute() {
//业务处理
super.receiver.doSomething();
}
}</span>


这确实简化了很多,每个命令完成单一的职责,而不是根据接收者的不同完成不同的职责。在高层模块的调用时就不用考虑接收者是谁的问题,如代码清单15-24所示。
<span style="font-size:18px;">代码清单15-24 场景类
public class Client {
public static void main(String[] args) {
//首先声明调用者Invoker
Invoker invoker = new Invoker();
//定义一个发送给接收者的命令
Command command = new ConcreteCommand1();
//把命令交给调用者去执行
invoker.setCommand(command);
invoker.action();
}
}</span>

高层次的模块不需要知道接收者 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值