本文共 3437 字,大约阅读时间需要 11 分钟。
命令模式,其为了达到动作的请求者从动作的执行者当中进行解耦
命令模式当中关系结构图
定义命令的接口,以及执行命令的抽象方法
命令的接口的实现对象,这个对象当中通常都持有一个接收者,调用接收者的功能来执行命令
接收者,真正执行命令的对象,注意这个地方在ConcreteCommand当中很有可能持有的是Receiver的接口
要求对象命令持有请求,通常会持有命令对象,也就是Command类型的引用,这个是客户端真正触发命令并要求命令执行的地方
创建具体的命令对象,并设置命令的接受者,而真正的执行是在Invoker当中去调用的。
class Invoker { //调用者,持续一个命令接口 private Command command; public void setCommand(Command command) { this.command = command; } public void action(){ this.command.execute(); }} abstract class Command { public abstract void execute();} class ConcreteCommand extends Command { //具体命令的执行者,需要持有接收者,因为命令是由具体的接受者执行的 private Receiver receiver; public ConcreteCommand(Receiver receiver){ this.receiver = receiver; } public void execute() { this.receiver.doSomething(); }} class Receiver { public void doSomething(){ System.out.println("接受者-业务逻辑处理"); }} public class Client { public static void main(String[] args){ Receiver receiver = new Receiver(); Command command = new ConcreteCommand(receiver); //客户端直接执行具体命令方式(此方式与类图相符) command.execute(); //客户端通过调用者来执行命令 Invoker invoker = new Invoker(); invoker.setCommand(command); invoker.action(); }}
接下来我们看下java框架当中是如何使用命令模式的,以下代码摘自Netty 源码
Netty当中的Command的接口
public interface Executor { void execute(Runnable command);}
Executor框架是jdk当中提供的
具体的ConcreteCommand是ThreadPerTaskExecutor,这个类是位于netty包当中 io.netty.util.concurrent
public final class ThreadPerTaskExecutor implements Executor { private final ThreadFactory threadFactory; public ThreadPerTaskExecutor(ThreadFactory threadFactory) { if (threadFactory == null) { throw new NullPointerException("threadFactory"); } this.threadFactory = threadFactory; } @Override public void execute(Runnable command) { threadFactory.newThread(command).start(); }}
可以看到在具体命令实现当中持有的是一个接受者ThreadFactory对象,我们再来看下这个对象
public interface ThreadFactory { Thread newThread(Runnable r);}
其实接受者也是一个接口,再调用的时候,会提前把接受者当中的具体实现方式,我们这里可以找一个Netty当中接受者的具体实现代码
DefaultThreadFactory, 值贴出来newThread方法的具体实现,其他的未展示
public class DefaultThreadFactory implements ThreadFactory { @Override public Thread newThread(Runnable r) { Thread t = newThread(new DefaultRunnableDecorator(r), prefix + nextId.incrementAndGet()); try { if (t.isDaemon()) { if (!daemon) { t.setDaemon(false); } } else { if (daemon) { t.setDaemon(true); } } if (t.getPriority() != priority) { t.setPriority(priority); } } catch (Exception ignored) { // Doesn't matter even if failed to set. } return t; }}
Client类进行绑定具体的Command对象和Receiver对象,在这个源码当中充当Client角色的就是MultithreadEventExecutorGroup类
接下来我们看具体的调用类
在这个类当中我们可以看到进行调用了exectue方法,并且传递进去了一个Runable接口的具体实现类,EventLoop这个接口最终实现的是我们上面说的Executor接口。我们可以说AbstractBootstrap类就是类似于我们上面Invoker的角色,因为他持有了channel对象,channel对象当中持有了eventLoop,其实也相当于是我们上面所说的,Invoker这个角色持有了Command接口对象。
JDK当中我们也可以看到对命令模式的使用,在线程池当中,同样的也是Command角色是Executor, ConcreteCommand 角色是ThreadPoolExcutor, Receiver 角色是 DefaultThreadFacotry,在初始化一个线程池的时候如果没有ThreadFactory,会使用DefaultThreadFactory,当我们往线程池当中提交线程任务时,调用execute方法,该方法实现具体的业务逻辑,对用户提交过来的线程任务进行封装,放在线程队列当中。
对于上述源码这个例子,我们可以看到其实是将线程信息的配置和线程信息的执行方法进行了分离。命令模式其本质是将命令的请求和命令的执行进行了分离,当我们看网上提供的简单例子的时候,都很容易理解,但是结合源码的时候,很容易就陷入到每行代码当中,而没有意识到原来框架是使用了设计模式的。