哈喽,各位盆友们!我是你们亲爱的学徒小z,今天给大家分享一篇有关设计模式的文章,希望可以帮助到大家。
文章目录
定义
- 将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请 求排队或者记录请求日志,可以提供命令的撤销和恢复功能
- 命令模式是一种行为型设计模式(行为型设计模式:它主要关注对象之间的交互和职责分配,用于处理系统中对象的行为)
样例介绍
我们先来看一个项目和甲方客户之间的样例
图一
图二
- 图一是未修改的类图,可以发现,客户(client)每次提新的需求都要和对应的组进行沟通CodeGroup、PageGroup、RequirementGroup。这样一开始客户可能觉得没什么,但事件一长,是个人都烦,并且各个组也会感觉难受。所以客户提出需求:你们给我派一个负责人来和我沟通,我将需求给他说,只要最终实现了就行。于是就出现了Invoker来听取客户的需求,于是Invoker接受客户的string类型命令,等等...,string类型,看起来不太好,客户命令是string类型的,这有非常多的变化,而且没有约束力。我们想办法解决这个问题,就产生了对命令的封装类Command(抽象类),DeletePageCommand、AddRequirementCommand(具体实现类,有N个命令就有N个实现类)。
1.类简介
-
Command抽象类:客户发给我们的命令,定义三个工作组的成员变量,供子类使用; 定义一个抽象方法execute,由子类来实现
Command抽象类可以有N个子类,如增加一个功能命令(AddFunCommand),删除一份需求命令(DeleteRequirementCommand)等,这里就不再描述了,只要是由客户产生、时常性的行为都可以定义为一个命令,其实现类都比较简单
-
CInvoker实现类:项目接头负责人,setComand接收客户发给我们的命令,action方法是执行客户的命令(方法名写成是action,与command的execute区分开,避免混淆)
2.具体代码
java
public abstract class Command {
//把三个组都定义好,子类可以直接使用
protected RequirementGroup rg = new RequirementGroup(); //需求组
protected PageGroup pg = new PageGroup(); //美工组
protected CodeGroup cg = new CodeGroup(); //代码组
//只有一个方法,你要我做什么事情
public abstract void execute();
}
//增加需求的命令
public class AddRequirementCommand extends Command {
//执行增加一项需求的命令
public void execute() {
//找到需求组
super.rg.find();
//增加一份需求
super.rg.add();
//给出计划
super.rg.plan();
}
}
//删除页面的命令
public class DeletePageCommand extends Command {
//执行删除一个页面的命令
public void execute() {
//找到页面组
super.pg.find();
//删除一个页面
super.rg.delete();
//给出计划
super.rg.plan();
}
}
//负责人
public class Invoker {
//什么命令
private Command command;
//客户发出命令
public void setCommand(Command command){
this.command = command;
}
//执行客户的命令
public void action(){
this.command.execute();
}
}
//增加一项需求,如果客户要删除一个页面,修改有多大?
public class Client {
public static void main(String[] args) {
//定义我们的接头人
Invoker xiaoSan = new Invoker(); //接头人就是小三
//客户要求增加一项需求
System.out.println("------------客户要求增加一项需求---------------");
//客户给我们下命令来
Command command = new AddRequirementCommand();
//Command command = new DeletePageCommand();
//接头人接收到命令
xiaoSan.setCommand(command);
//接头人执行命令
xiaoSan.action();
}
}
通用类图
1.通用类介绍
-
Receiver:该角色是干活的角色,命令传递到这里是应该被执行的,具体到我们上面的例子中就是Group的三个实现类
-
Command命令角色:需要执行的所有命令都在这里声明
-
Invoker调用者角色:接收到命令,并执行命令。
-
命令模式比较简单,但是在项目中非常频繁地使用,因为它的封装性非常好,把请求方 (Invoker)和执行方(Receiver)分开了,扩展性也有很好的保障,通用代码比较简单
2.通用代码
java
//通用Receiver类,为什么Receiver是一个抽象类?那是因为接收者可以有多个,有多个就需要定
//义一个所有特性的抽象集合------抽象的接收者
public abstract class Receiver {
//抽象接收者,定义每个接收者都必须完成的业务
public abstract void doSomething();
}
//具体的Receiver类
public class ConcreteReciver1 extends Receiver{
//每个接收者都必须处理一定的业务逻辑
public void doSomething(){
}
}
public class ConcreteReciver2 extends Receiver{
//每个接收者都必须处理一定的业务逻辑
public void doSomething(){
}
}
//抽象的Command类
public abstract class Command {
//每个命令类都必须有一个执行命令的方法
public abstract void execute();
}
//具体的Command类
public class ConcreteCommand1 extends Command {
//对哪个Receiver类进行命令处理
//这里可以移到抽象类中中,
private Receiver receiver;
//构造函数传递接收者
public ConcreteCommand1(Receiver _receiver){
this.receiver = _receiver;
}
//必须实现一个命令
public void execute() {
//业务处理
this.receiver.doSomething();
}
}
//ConcreteCommand2类似
//调用者Invoker类
public class Invoker {
private Command command;
public void setCommand(Command _command){
this.command = _command;
}
//执行命令
public void action(){
this.command.execute();
}
}
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();
}
}
3.实际应用
每一个模式到实际应用的 时候都有一些变形,命令模式的Receiver在实际应用中一般都会被封装掉(除非非常必要, 例如撤销处理),那是因为在项目中:约定的优先级最高,每一个命令是对一个或多个 Receiver的封装,我们可以在项目中通过有意义的类名或命令名处理命令角色和接收者角色 的耦合关系(这就是约定),减少高层模块(Client类)对低层模块(Receiver角色类)的依赖关系,提高系统整体的稳定性。因此,建议大家在实际的项目开发时采用封闭Receiver的方式,减少Client对Reciver的依赖,
java
//修改如下
//完美的Command类
public abstract class Command {
//定义一个子类的全局共享变量,这里是修改的地方
protected final Receiver receiver;
//实现类必须定义一个接收者
public Command(Receiver _receiver){
this.receiver = _receiver;
}
//每个命令类都必须有一个执行命令的方法
public abstract void execute();
}
//具体的命令
public class ConcreteCommand1 extends Command {
//声明自己的默认接收者
public ConcreteCommand1(){
super(new ConcreteReciver1());
}
//设置新的接收者
public ConcreteCommand1(Receiver _receiver){
super(_receiver);
}
//每个具体的命令都必须实现一个命令
public void execute() {
//业务处理
super.receiver.doSomething();
}
}
// 场景类,高层次的模块不需要知道接收者
public class Client {
public static void main(String[] args) {
//首先声明调用者Invoker
Invoker invoker = new Invoker();
//定义一个发送给接收者的命令
Command command = new ConcreteCommand1();
//把命令交给调用者去执行
invoker.setCommand(command);
invoker.action();
}
}
4.优点
- 类间解耦
- 可扩展性
- 命令模式结合其他模式更好用。结合责任链模式,实现命令族解析任务;结合模板方法,减少子类膨胀问题。
5.缺点
- 有N个命令,子类膨胀非常大
使用场景
- 解决在软件系统中请求者和执行者之间的紧耦合问题,特别是在需要对行为进行记录、撤销/重做或事务处理等场景。
撤销问题
-
发出一个命令,执行后撤回(执行后撤回是状态变更)如何实现
//修改后的Group类 public abstract class Group { //甲乙双方分开办公,你要和那个组讨论,你首先要找到这个组 public abstract void find(); //被要求增加功能 public abstract void add(); //被要求删除功能 public abstract void delete(); //被要求修改功能 public abstract void change(); //被要求给出所有的变更计划 public abstract void plan(); //每个接收者都要对直接执行的任务可以回滚 public void rollBack(){ //根据日志进行回滚 } } /*仅仅增加了一个rollBack的方法,每个接收者都可以对自己实现的任务进行回滚。怎么 回滚?根据事务日志进行回滚!新增加的一个命令CancelDeletePageCommand实现撤销刚刚 发出的删除命令*/ // 撤销命令 public class CancelDeletePageCommand extends Command { //撤销删除一个页面的命令 public void execute() { super.pg.rollBack(); } }