代码运行结果如下:
网络异常,图片无法展示
|
通过状态模式,我们把事件触发的 状态转移和动作执行,拆分到不同的状态类中,避免了分支判断结构。
顺带带出UML类图、组成角色、使用场景及优缺点~
网络异常,图片无法展示
|
- Context (上下文信息类) → 存储当前状态类,并负责具体状态的切换;
- State (抽象状态类) → 定义声明状态更新的操作方法,可以是接口或抽象类;
- ConcreteState (具体状态类) → 实现抽象状态类中定义的方法,根据具体场景指定对应状态改变后的代码逻辑;
使用场景:
- 某个操作含有庞大的分支判断结构,且分支决定于对象的状态时;
- 对象行为取决于状态,且必须在运行时根据状态改变其行为时;
优点:
- 符合单一职责原则:将与特定状态相关的代码组织到单独的类中;
- 更好的扩展性:扩展新的状态只需增加实现类,在需要维护的地方设置下新状态即可;
- 提前定好可能的状态,降低代码实现复杂度,避免写大量的if-else条件语句;
缺点:
- 类增加,每个状态对应一个具体状态类;
- 不满足开闭原则,状态模式虽然降低了状态之间的耦合,但是新增或修改状态都会涉及前/后一个状态的修改;
- 逻辑零散,无法在一个地方就看出整个状态机的转换逻辑;
0x3、补充:有限状态机的概念
英文翻译 Finite State Machine,缩写FSM,简称状态机,它有三个组成部分:状态(State)
、事件(Event)
、动作(Action)
。其中的事件又称为 转移条件
,事件触发状态的转移和动作的执行(非必须)。
也可以理解为一种数学模型,该模型中有几个状态(有限的),在不同场景下,不同的状态间发生转移,在状态转移过程中可能伴随着不同的事件发生。
状态机有三种常见的实现方式:
- 分支逻辑法 → 缺点是改变业务逻辑,改起来容易出错,代码也不易看懂。适合简单状态机;
- 查表法 → 适用于状态很多、状态转移比较复杂的状态机,用二维数组表示状态转移图,可极大提高代码的可读性与可维护性;
- 状态模式 → 适用于状态并不多、状态转移较简单,事件触发动作包含的业务逻辑可能较复杂的状态机。
0x4、加餐:Android源码中是如何使用16进制进行状态管理的?
在Android系统源码中涉及到 多状态 管理总是通过十六进制数字来表示,如ViewGroup中:
static final int FLAG_CLIP_CHILDREN = 0x1; private static final int FLAG_CLIP_TO_PADDING = 0x2; static final int FLAG_INVALIDATE_REQUIRED = 0x4; private static final int FLAG_RUN_ANIMATION = 0x8; static final int FLAG_ANIMATION_DONE = 0x10; private static final int FLAG_PADDING_NOT_NULL = 0x20; private static final int FLAG_ANIMATION_CACHE = 0x40; static final int FLAG_OPTIMIZE_INVALIDATE = 0x80; static final int FLAG_CLEAR_TRANSFORMATION = 0x100; private static final int FLAG_NOTIFY_ANIMATION_LISTENER = 0x200;
这是为什么呢?先复习下几种二进制运算:
按位与(&)
→ 对应位都为1才为1,否则为0,如0x1 & 0x2 → 0001 & 0010 → 0000;
按位或(|)
→ 对应位有一个为1即为1,如0x1 | 0x2 → 0001 | 0010 → 0011
取反(~)
→ 按位取反,如~0x1 → 0001 → 1110
接着以上面手机状态为例,写个状态管理的例子:
private static int state = 0; private final static int CLOSE = 0x1; // 关机状态 private final static int FIRST_BOOT = 0x2; // 首次启动 private final static int NOT_FIRST_BOOT = 0x4; // 非首次启动 private final static int AFTER_BOOT = 0x8; // 启动后
状态增加 → 或运算
state | CLOSE → (0000 | 0001) → 0001 → 此时状态:CLOSE state | FIRST_BOOT → (0001 | 0010) → 0011 → 此时状态:CLOSE + FIRST_BOOT state | NOT_FIRST_BOOT → (0011 | 0100) → 0111 → 此时状态:CLOSE + FIRST_BOOT + NOT_FIRST_BOOT
状态移除 → 对应的位数从1改为0,先取反,再与运算
state &= ~NOT_FIRST_BOOT → (0111 & 1011) → 0011 → 此时状态:CLOSE + FIRST_BOOT state &= ~CLOSE → (0011 & 1110) → 0010 → 此时状态:FIRST_BOOT
状态判断 → 与运算判断结果是否为0
// 假设此时状态为:CLOSE + FIRST_BOOT + NOT_FIRST_BOOT state & FIRST_BOOT → 0111 & 0010 = 0010 → 0010 → 结果不为0,包含此状态; // 假设此时状态为:CLOSE + FIRST_BOOT state & NOT_FIRST_BOOT → 0011 & 0100 → 结果为0,不包含此状态;
疑惑:用来标识状态的十六进制并不是连续的,如跳过了0x3:
如果把上面的NOT_FIRST_BOOT从0x4改为0x3,而CLOSE + FIRST_BOOT 结果为0011,同为0x3,此时进行状态判断结果不为0,难道说增加了NOT_FIRST_BOOT状态吗?所以这里的取值是有固定规则的,即 左移一位。
private final static int CLOSE = 1 << 0; private final static int FIRST_BOOT = 1 << 1; private final static int NOT_FIRST_BOOT = 1 << 2; private final static int AFTER_BOOT = 1 << 3;
选择十六进制的原因而不用其他进制的原因(如十进制):
计算机中,一个字节有八位,最大值为1111111,对应十进制255,十六进制FF,半个字节用十六进制 通过一个字母 就能表示,而转换成十进制则是一个无规律的数字,相比起十进制,十六进制转二进制 更直观一些。