Android设计模式源码解析之命令模式
本文为 Android 设计模式源码解析 中 命令模式 分析
Android系统版本: 2.3
分析者:lijunhuayc,分析状态:完成,校对者:Mr.Simple,校对状态:未开始
1. 模式介绍
模式的定义
将一个请求封装成一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
模式的使用场景
- 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
- 系统需要在不同的时间指定请求、将请求排队和执行请求。
- 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
- 系统需要将一组操作组合在一起,即支持宏命令。
2. UML类图
角色介绍
命令角色(Command):定义命令的接口,声明具体命令类需要执行的方法。这是一个抽象角色。
具体命令角色(ConcreteCommand):命令接口的具体实现对象,通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
调用者角色(Invoker):负责调用命令对象执行请求,通常会持有命令对象(可以持有多个命令对象)。Invoker是Client真正触发命令并要求命令执行相应操作的地方(使用命令对象的入口)。
接受者角色(Receiver):Receiver是真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
客户角色(Client):Client可以创建具体的命令对象,并且设置命令对象的接收者。Tips:不能把Clinet理解为我们平常说的客户端,这里的Client是一个组装命令对象和接受者对象的角色,或者你把它理解为一个装配者。
3. 模式的简单实现
简单实现的介绍
命令模式其实就是对命令进行封装,将命令请求者和命令执行者的责任分离开来实现松耦合。
这里我们通过一个简单的实例来剖析一下命令模式:命令接收者ReceiverRole拥有一个PeopleBean类型成员,通过Invoker发出不同的命令来修改PeopleBean的相对应的属性,具体命令实现类ConcreteCommandImpl1执行修改年龄命令,ConcreteCommandImpl2执行修改姓名的命令等等,ClientRole负责组装各个部分。
例子代码如下(resource目录下也可以查看)。
实现源码
package com.command;
/**
* 命令接口 [命令角色]
*/
public interface Command {
public void execute();
public void undo();
public void redo();
}
ConcreteCommandImpl1.java类.
package com.command;
/**
* 更新年龄的命令类 [ 具体命令角色 ]
*/
public class ConcreteCommandImpl1 implements Command{
private ReceiverRole receiverRole1;
public ConcreteCommandImpl1(ReceiverRole receiverRole1) {
this.receiverRole1 = receiverRole1;
}
@Override
public void execute() {
/*
* 可以加入命令排队等等,未执行的命令支持redo操作
*/
receiverRole1.opActionUpdateAge(1001);//执行具体的命令操作
}
@Override
public void undo() {
receiverRole1.rollBackAge();//执行具体的撤销回滚操作
}
@Override
public void redo() {
//在命令执行前可以修改命令的执行
}
}
ConcreteCommandImpl2.java类.
package com.command;
/**
* 更新姓名的命令类[具体命令角色]
*/
public class ConcreteCommandImpl2 implements Command{
private ReceiverRole receiverRole1;
public ConcreteCommandImpl2(ReceiverRole receiverRole1) {
this.receiverRole1 = receiverRole1;
}
@Override
public void execute() {
/*
* 可以加入命令排队等等,未执行的命令支持redo操作
*/
receiverRole1.opActionUpdateName("lijunhuayc");//执行具体的命令操作
}
@Override
public void undo() {
receiverRole1.rollBackName();//执行具体的撤销回滚操作
}
@Override
public void redo() {
//在命令执行前可以修改命令的执行
}
}
InvokerRole.java.
package com.command;
/**
* 命令调用[调用者角色]
*/
public class InvokerRole {
private Command command1;
private Command command2;
//持有多个命令对象[实际的情况也可能是一个命令对象的集合来保存命令对象]
public void setCommand1(Command command1) {
this.command1 = command1;
}
public void setCommand2(Command command2) {
this.command2 = command2;
}
/**
* 执行正常命令,1执行回滚命令
*/
public void invoke(int args) {
//可以根据具体情况选择执行某些命令
if(args == 0){
command1.execute();
command2.execute();
}else if(args == 1){
command1.undo();
command2.undo();
}
}
}
ReceiverRole.java.
package com.command;
/**
* 命令的具体执行类[接收者角色], 命令接收者可以是任意的类,只要实现了命令要求实现的相应功能即可。
*/
public class ReceiverRole {
private PeopleBean people;
//具体命令操作的缓存栈,用于回滚。这里为了方便就用一个PeopleBean来代替
private PeopleBean peopleCache = new PeopleBean(); public ReceiverRole() {
this.people = new PeopleBean(-1, "NULL");//初始化年龄为-1,姓名为NULL
}
public ReceiverRole(PeopleBean people) {
this.people = people;
}
/**
* 具体操作方法[修改年龄和姓名]
*/
public void opActionUpdateAge(int age) {
System.out.println("执行命令前:"+people.toString());
this.people.update(age);
System.out.println("执行命令后:"+people.toString()+"\n");
}
//修改姓名
public void opActionUpdateName(String name) {
System.out.println("执行命令前:"+people.toString());
this.people.update(name);
System.out.println("执行命令后:"+people.toString()+"\n");
}
/**
* 回滚操作,用于撤销opAction执行的改变
*/
public void rollBackAge() {
people.setAge(peopleCache.getAge());
System.out.println("命令回滚后:"+people.toString()+"\n");
}
public void rollBackName() {
people.setName(peopleCache.getName());
System.out.println("命令回滚后:"+people.toString()+"\n");
}
}
PeopleBean.java
package com.command;
/**
* @Desc: 辅助类,作为接收者Receiver的成员,包含两个属性,用来观察命令的执行情况
* @author ljh
* @date 2015-3-16 上午11:29:11
*/
public class PeopleBean {
private int age = -1; //年龄
private String name = "NULL"; //姓名
public PeopleBean() {
}
public PeopleBean(int age, String name) {
this.age = age;
this.name = name;
}
public void update(int age, String name) {
this.age = age;
this.name = name;
}
public void update(int age) {
this.age = age;
}
public void update(String name) {
this.name = name;
}
/**
* @return 返回一个PeopleBean的克隆对象
*/
protected PeopleBean clone(){
return new PeopleBean(age, name);
}
@Override
public String toString() {
return " 【年龄:" + age + "\t姓名:" + name + "】";
}
// setter and getter
}
ClientRole.java
package com.command;
/**
* 命令对象和接受者对象的组装类[客户角色].
* 我这把类名定义成ClientRole更方便读者理解这只是命令模式中的一个客户角色,而不是我们常规意义上说的客户端
*/
public class ClientRole {
/**
* 组装操作
*/
public void assembleAction() {
//创建一个命令接收者
ReceiverRole receiverRole1 = new ReceiverRole(); //创建一个命令的具体实现对象,并指定命令接收者
Command command1 = new ConcreteCommandImpl1(receiverRole1); Command command2 = new ConcreteCommandImpl2(receiverRole1);
InvokerRole invokerRole = new InvokerRole();//创建一个命令调用者
invokerRole.setCommand1(command1);//为调用者指定命令对象1
invokerRole.setCommand2(command2);//为调用者指定命令对象2
invokerRole.invoke(0); //发起调用命令请求
invokerRole.invoke(1); //发起调用命令请求
}
}
测试类.
package com.command;
public class MainTest {
public static void main(String[] args) {
ClientRole client = new ClientRole();
client.assembleAction();
}
}
输出结果如下:
总结
- 每一个命令都是一个操作:请求的一方发出请求,要求执行一个操作;接收的一方收到请求,并执行操作。
- 命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。
- 命令模式使请求本身成为一个对象,这个对象和其他对象一样可以被存储和传递。
- 命令模式的关键在于引入了抽象命令接口,且发送者针对抽象命令接口编程,只有实现了抽象命令接口的具体命令才能与接收者相关联。
Android源码中的模式实现
Command接口中定义了一个execute方法,客户端通过Invoker调用命令操作再来调用Recriver执行命令;把对Receiver的操作请求封装在具体的命令中,使得命令发起者和命令接收者解耦。
以Android中大家常见的Runnable为例:客户端只需要new Thread(new Runnable(){}).start()就开始执行一系列相关的请求,这些请求大部分都是实现Runnable接口的匿名类。
【O_o 模式就在我们身边~】
命令接口Runnable接口定义如下:
package java.lang;
/**
* Represents a command that can be executed. Often used to run code in a
* different {@link Thread}.
*/
public interface Runnable {
/**
* Starts executing the active part of the class' code. This method is
* called when a thread is started that has been created with a class which
* implements {@code Runnable}.
*/
public void run();
}
调用者Thread源码如下(省略部分代码): Tips:命令模式在这里本来不需要继承Runnable接口,但为了方便性等,继承了Runnable接口实现了run方法,这个run是Thread自身的运行run的方法,而不是命令Runnable的run。
public class Thread implements Runnable {
//省略部分无关代码...
/* some of these are accessed directly by the VM; do not rename them */
volatile VMThread vmThread;
volatile ThreadGroup group;
volatile boolean daemon;
volatile String name;
volatile int priority;
volatile long stackSize;
Runnable target;
private static int count = 0;
public synchronized void start() {
if (hasBeenStarted) {
throw new IllegalThreadStateException("Thread already started."); // TODO Externalize?
}
hasBeenStarted = true;
VMThread.create(this, stackSize);
}
//省略部分代码...
}
上面可以看到执行start()方法的时候实际执行了VMThread.create(this, stackSize)方法;create是VMThread的本地方法,其JNI实现在 android/dalvik/vm/native/java_lang_VMThread.cpp 中的 Dalvik_java_lang_VMThread_create方法,如下:
static void Dalvik_java_lang_VMThread_create(const u4* args, JValue* pResult)
{
Object* threadObj = (Object*) args[0];
s8 stackSize = GET_ARG_LONG(args, 1);
/* copying collector will pin threadObj for us since it was an argument */
dvmCreateInterpThread(threadObj, (int) stackSize);
RETURN_VOID();
}
而dvmCreateInterpThread的实现在Thread.app中,如下:
bool dvmCreateInterpThread(Object* threadObj, int reqStackSize){
Thread* self = dvmThreadSelf();
Thread* newThread = allocThread(stackSize);
newThread->threadObj = threadObj;
Object* vmThreadObj = dvmAllocObject(gDvm.classJavaLangVMThread, ALLOC_DEFAULT);
dvmSetFieldInt(vmThreadObj, gDvm.offJavaLangVMThread_vmData, (u4)newThread);
dvmSetFieldObject(threadObj, gDvm.offJavaLangThread_vmThread, vmThreadObj);
pthread_t threadHandle;
int cc = pthread_create(&threadHandle, &threadAttr, interpThreadStart, newThread);
dvmLockThreadList(self);
assert(newThread->status == THREAD_STARTING);
newThread->status = THREAD_VMWAIT;
pthread_cond_broadcast(&gDvm.threadStartCond);
dvmUnlockThreadList();
}
static Thread* allocThread(int interpStackSize)
{
Thread* thread;
thread = (Thread*) calloc(1, sizeof(Thread));
thread->status = THREAD_INITIALIZING;
}
这里是底层代码,简单介绍下就行了: 第4行通过调用 allocThread 创建一个名为newThread的dalvik Thread并设置一些属性,第5行设置其成员变量threadObj为传入的Android Thread,这样dalvik Thread就与Android Thread对象关联起来了;第7行然后创建一个名为vmThreadObj的VMThread对象,设置其成员变量vmData为前面创建的newThread,设置 Android Thread threadObj的成员变量vmThread为这个vmThreadObj,这样Android Thread通过VMThread的成员变量vmData就和dalvik Thread关联起来了。
接下来在12行通过pthread_create创建pthread线程,并让这个线程start,这样就会进入该线程的thread entry运行,下来我们来看新线程的thread entry方法 interpThreadStart,同样只列出关键的地方:
//pthread entry function for threads started from interpreted code.
static void* interpThreadStart(void* arg){
Thread* self = (Thread*) arg;
std::string threadName(dvmGetThreadName(self));
setThreadName(threadName.c_str());
//Finish initializing the Thread struct.
dvmLockThreadList(self);
prepareThread(self);
while (self->status != THREAD_VMWAIT)
pthread_cond_wait(&gDvm.threadStartCond, &gDvm.threadListLock);
dvmUnlockThreadList();
/*
* Add a JNI context.
*/
self->jniEnv = dvmCreateJNIEnv(self);
//修改状态为THREAD_RUNNING
dvmChangeStatus(self, THREAD_RUNNING);
//执行run方法
Method* run = self->threadObj->clazz->vtable[gDvm.voffJavaLangThread_run];
JValue unused;
ALOGV("threadid=%d: calling run()", self->threadId);
assert(strcmp(run->name, "run") == 0);
dvmCallMethod(self, run, self->threadObj, &unused);
ALOGV("threadid=%d: exiting", self->threadId);
//移出线程并释放资源
dvmDetachCurrentThread();
return NULL;
}
//Finish initialization of a Thread struct.
static bool prepareThread(Thread* thread){
assignThreadId(thread);
thread->handle = pthread_self();
thread->systemTid = dvmGetSysThreadId();
setThreadSelf(thread);
return true;
}
//Explore our sense of self. Stuffs the thread pointer into TLS.
static void setThreadSelf(Thread* thread){
int cc;
cc = pthread_setspecific(gDvm.pthreadKeySelf, thread);
}
在新线程的interpThreadStart方法中,首先设置线程的名字,然后调用prepareThread设置线程id以及其它一些属性,其中调用了setThreadSelf将新dalvik Thread自身保存在TLS中,这样之后就能通过dvmThreadSelf方法从TLS中获取它。然后在29行处修改状态为THREAD_RUNNING,并在36行调用对应Android Thread的run()方法,其中调用了Runnable的run方法,运行我们自己的代码。 绕这么深才执行到我们的run方法,累不累? v_v
/**
* Calls the <code>run()</code> method of the Runnable object the receiver
* holds. If no Runnable is set, does nothing.
* @see Thread#start
*/
public void run() {
if (target != null) {
target.run();
}
}
到此我们已经完成一次命令调用,至于底层run调用完毕后续执行代码,读者可以自行跟进看看~~~
4. 杂谈
优点与缺点
优点
- 降低对象之间的耦合度。
- 新的命令可以很容易地加入到系统中。
- 可以比较容易地设计一个组合命令。
- 调用同一方法实现不同的功能
缺点
使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。
比如上面的PeopleBean的属性增加,Receiver针对PeopleBean一个属性一个执行方法,一个Command的实现可以调用Receiver的一个执行方法,由此得需要设计多少个具体命令类呀!!