这是设计模式的最后一篇,由于年后一直忙于团队的管理和技术工作,一周都没有好好休息,今天这章只讲讲常用的命令,备忘录和状态模式 (访问者,中介者和解释器模式这边就不讲了,平常开发很少用到)。后面的主题是 NDK,敬请期待。
目录:
- 命令模式的简介和应用
- 备忘录模式的简介和应用
- 状态模式的简介和应用
1. 命令模式的简介和应用
- 1.1 简介
命令模式:将请求封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。命令模式通过这种封装的方式实现将客户端和接收端解耦。
命令模式的几个角色:
- 抽象命令接口 Command:定义命令的接口,声明执行的方法。
- 具体的命令对象 ConcreteCommand:持有具体的接受者对象,完成具体的具体的命令。
- 接受者对象 Receiver:接受者对象,真正执行命令的对象。
- 传递命令对象 Invoker:持有命令对象,要求命令对象执行请求。
- 客户端对象 Client:创建具体命令的对象并且设置命令对象的接受者。
关于命令撤销的理解:
在我们发出某个请求并执行之后,将命令的执行状态进行保存,如果我们想要回归执行这个命令之前的状态,我们就可以通过命令撤销的方式回归到之前的状态。
命令模式优点:
- 实现客户端和接受者之间的解耦。
- 可以动态的添加新的命令。
- 只需要调用同一个方法 (doCommand方法) 便可以实现不同的功能。
命令模式缺点:
- 实现一个具体的命令系统,可能要创建很多的具体命令对象。
- 1.2 demo
下面是一个小例子,来帮助理解命令模式。
/**
* 抽象的命令接口,定义具体命名的接口
*/
public interface Command {
void execute();
}
/**
* 接受者对象
*/
public class Light {
public void turnOn() {
System.out.println("turnOn the light");
}
public void turnOff() {
System.out.println("turnOff the light");
}
}
/**
* 开灯命令
*/
public class TurnOnLightCommand implements Command {
private final Light light;
public TurnOnLightCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.turnOn();
}
}
/**
* 关灯命令
*/
public class TurnOffLightCommand implements Command {
private final Light light;
public TurnOffLightCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.turnOff();
}
}
/**
* 传递命令的 Invoker
*/
public class CommandInvoker {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void executeCommand() {
command.execute();
}
}
public static void main(String[] args) {
TestBehaviorDesignModel model = new TestBehaviorDesignModel();
CommandInvoker invoker = model.new CommandInvoker();
// 创建命名的接受者对象
Light light = model.new Light();
// 创建开灯命令
Command turnOnLightCommand = model.new TurnOnLightCommand(light);
// 设置命令
invoker.setCommand(turnOnLightCommand);
// 执行命令
invoker.executeCommand();
// 创建关灯命令
Command turnOffLightCommand = model.new TurnOffLightCommand(light);
// 设置命令
invoker.setCommand(turnOffLightCommand);
// 执行命名
invoker.executeCommand();
}
执行输出:
turnOn the light
turnOff the light
- 1.3 Android PackageManagerService 中的命令模式
Application Framework 中 PackageManagerService 类用到了命令模式。PackageManagerService 是 Android 系统的 Service 之一,主要功能是实现对应用包的解析、管理、卸载等操作。下面是它的类图结构:
在 PackageManagerService 中定义了 HandlerParams 类:
private abstract class HandlerParams {
HandlerParams(UserHandle user) {
mUser = user;
}
UserHandle getUser() {
return mUser;
}
HandlerParams setTraceMethod(String traceMethod) {
this.traceMethod = traceMethod;
return this;
}
HandlerParams setTraceCookie(int traceCookie) {
this.traceCookie = traceCookie;
return this;
}
abstract void handleStartCopy() throws RemoteException;
abstract void handleServiceError();
abstract void handleReturnCode();
}
然后有几个实现类:
class MeasureParams extends HandlerParams {
public MeasureParams(PackageStats stats, IPackageStatsObserver observer) {
super(new UserHandle(stats.userHandle));
mObserver = observer;
mStats = stats;
}
@Override
void handleStartCopy() throws RemoteException {
// ......
}
@Override
void handleReturnCode() {
// ......
}
@Override
void handleServiceError() {
// ......
}
}
class InstallParams extends HandlerParams {
InstallParams(OriginInfo origin, MoveInfo move, IPackageInstallObserver2 observer,
int installFlags, String installerPackageName, String volumeUuid,
VerificationInfo verificationInfo, UserHandle user, String packageAbiOverride,
String[] grantedPermissions, Certificate[][] certificates) {
super(user);
// ......
}
@Override
void handleReturnCode() {
// ......
}
@Override
void handleServiceError() {
//......
}
public boolean isForwardLocked() {
// ......
}
}
这边就不一一列举了,HandlerParams 为抽象的命令,UserHandler 为接受者对象,MeasureParams、InstallParams 等为具体的命令,传递命令对象为 PackageHandler,看看它的定义:
class PackageHandler extends Handler {
private boolean mBound = false;
final ArrayList<HandlerParams> mPendingInstalls =
new ArrayList<HandlerParams>();
private boolean connectToService() {
// ......
}
private void disconnectService() {
// ......
}
PackageHandler(Looper looper) {
super(looper);
}
public void handleMessage(Message msg) {
// 处理消息,消息里面包含命令 ......
}
void doHandleMessage(Message msg) {
switch (msg.what) {
case INIT_COPY: {
HandlerParams params = (HandlerParams) msg.obj;
// ......
break;
}
case MCS_UNBIND: {
// ......
break;
}
case MCS_GIVE_UP: {
HandlerParams params = mPendingInstalls.remove(0);
// ......
break;
}
case SEND_PENDING_BROADCAST: {
// ......
break;
}
case START_CLEANING_PACKAGE: {
// ......
} break;
case POST_INSTALL: {
// ......
} break;
case UPDATED_MEDIA_STATUS: {
// ......
break;
}
}
}
}
可以看到它持有了一个 HandlerParams 集合,可以执行命令,也可以撤销命令。
2. 备忘录模式的简介和应用
- 2.1 简介
备忘录是这样一种设计模式:又叫做快照模式,在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可将该对象恢复到原先保存的状态。
- 2.2 demo
public class Memento {
/**
* 状态
*/
private String state;
/**
* 根据状态创建备忘录对象
*/
public Memento(String state) {
this.state = state;
}
/**
* 获取状态
*/
public String getState() {
return state;
}
/**
* 设置状态
*/
public void setState(String state) {
this.state = state;
}
}
/**
* 发起人角色类,发起人角色利用一个新创建的备忘录对象将自己的内部状态存储起来
*/
public class Originator {
private String state;
public Memento createMemento() {
return new Memento(state);
}
/**
* 将发起人恢复到备忘录对象所记载的状态
*/
public void restoreMemento(Memento memento) {
this.state = memento.getState();
}
public String getState() {
return this.state;
}
public void setState(String state) {
this.state = state;
}
}
/**
* 负责人角色类,负责人角色负责保存备忘录对象,但是从不修改(甚至不查看)备忘录对象的内容
*/
public class Caretaker {
private Memento memento;
/**
* 备忘录的取值方法
*/
public Memento retrieveMemento() {
return this.memento;
}
/**
* 备忘录的赋值方法
*/
public void saveMemento(Memento memento) {
this.memento = memento;
}
}
public static void main(String[] args) {
TestBehaviorDesignModel model = new TestBehaviorDesignModel();
Originator o = model.new Originator();
Caretaker c = model.new Caretaker();
// 改变负责人对象的状态
o.setState("On");
System.out.println("current state: " + o.getState());
// 创建备忘录对象,并将发起人对象的状态储存起来
c.saveMemento(o.createMemento());
// 修改发起人的状态
o.setState("Off");
System.out.println("modify state: " + o.getState());
// 恢复发起人对象的状态
o.restoreMemento(c.retrieveMemento());
System.out.println("restore state: " + o.getState());
}
执行输出:
current state: On
modify state: Off
restore state: On
- 2.3 Git 版本控制中的备忘录模式
备忘录的使用场景很多,主要用于回退,撤销等操作。
Git 是最常见的版本管理软件,每提交一个新版本,实际上 Git 就会把它们自动串成一条时间线,每个版本都有一个版本号,使用 git reset --hard 版本号即可回到指定的版本,让代码时空穿梭回到过去某个历史时刻。
除了 Git,浏览器回退、数据库备份与还原、编辑器撤销与重做、棋牌游戏悔棋都用到了备忘录模式。
3. 状态模式的简介和应用
- 3.1 简介
状态模式:允许一个对象在其内部状态改变时改变它的行为。
在很多情况下,一个对象的行为取决于一个或多个动态变化的属性,这样的属性叫做状态,这样的对象叫做有状态的(stateful) 对象。当这样的对象与外部事件产生互动时,其内部状态就会改变,从而使得系统的行为也随之发生变化。
- 3.2 demo
/**
* 抽象状态类
*/
public interface State {
void handle(String params);
}
/**
* 具体状态类
*/
public class ConcreteStateA implements State {
@Override
public void handle(String params) {
System.out.println("ConcreteStateA handle :" + params);
}
}
public class ConcreteStateB implements State {
@Override
public void handle(String params) {
System.out.println("ConcreteStateB handle :" + params);
}
}
/**
* 上下文类
*/
public class Context {
// 持有一个State类型的对象实例
private State state;
public void setState(State state) {
this.state = state;
}
public void request(String sampleParameter) {
state.handle(sampleParameter);
}
}
public static void main(String[] args) {
TestBehaviorDesignModel model = new TestBehaviorDesignModel();
// 创建环境
Context context = model.new Context();
// 创建状态
State state = model.new ConcreteStateA();
// 将状态设置到环境中
context.setState(state);
// 请求
context.request("test");
// 切换一个状态
State state1 = model.new ConcreteStateB();
context.setState(state1);
context.request("test1");
}
执行输出:
ConcreteStateA handle :test
ConcreteStateB handle :test1
是不是很像策略模式?状态模式与策略模式的区别:
- 1. 状态模式重点在各状态之间的切换从而做不同的事情,而策略模式更侧重于根据具体情况选择策略,并不涉及切换。
- 2. 状态模式不同状态下做的事情不同,而策略模式做的都是同一件事,例如排序,有选择排序、冒泡排序,虽然策略不同,但最终做的事情都是排序,也就是说它们之间是可替换的。反观状态模式,各个状态的同一方法做的是不同的事,不能互相替换。状态模式封装了对象的状态,而策略模式封装算法或策略。
- 3.3 Android StateMachine 中的状态模式
StateMachine 是 Android 源码中的一个分层状态机,它可以处理各种 State 类的转化。State 状态类必须实现 processMessage 方法,为了创建/摧毁工作环境,还可以继承实现 enter/exit 等方法。
StateMachine 可以在每一个状态内,定义其接收不同的指令,会切换到哪个状态,而不需要状态机主动去设定状态,降低了主体和状态之间的耦合,增加一个新状态时更加方便。
其主要方法有:
// 增加状态
addState(State state)
// 增加状态, 并且告诉状态机后者为父状态
addState(State state, State parent)
// 设置初始状态
setInitialState(State initialState)
// 发送消息,消息由子状态processMsg处理,若没有子状态处理,则调用父状态处理
sendMessage(int what)
// 变换状态
transitionTo(IState destState)
transitionTo 方法详解:
mP0
/ \
mP1 mS0
/ \
mS2 mS1
/ \ \
mS3 mS4 mS5 ---> 初始状态
假设初始状态 mS5,各个父状态同样也是活动的,于是 mP0, mP1, mS1 和 mS5 都是活动的。当有一个消息发出来,就会依次调用mS5, mS1, mP1, mP0 的 processMessage 方法 (前提是都会返回 false 或者 NOT_HANDLED)。
假设 mS5 的 processMessage 可以处理这个消息,并且会调用 transitionTo(mS4) 将状态转为 mS4,然后返回 true 或 HANDLED。processMessage 返回后会进入 performTransitions 方法,其会找到 mS5 和 mS4 的共同父状态,也就是 mP1。紧接着会依次调用 mS5.exit, mS1.exit 然后是 mS2.enter, mS4.enter。这时 mP0, mP1, mS2, mS4 这四个状态是活动的,当下一个消息到来的时候,就会激活 mS4.processMessage 方法。使用该状态机可以很好的实现各个状态之间的切换。