很高兴见到你!java
上上周我在掘金碰巧遇到了一篇 用设计模式管理状态 的文章,一时兴奋不已,在评论区安利了,一直以来我司在封装商业级 SDK 时,使用的十六进制状态管理机制。sql
原觉得会无人对此感兴趣,没想到,留言很快就收到文章做者的回复,而且在评论区耐心地和我探讨了设计模式的 独占式状态机 和十六进制的 复合状态管理 在使用场景上的区别。数据库
遗憾的是,经过评论区的只言片语,并不能让人体会到 十六进制状态管理 的真正魅力。编程
因而做为回馈,我特意分享了这一篇:当咱们封装商业级 SDK 时,咱们是怎么使用十六进制来完成状态管理。设计模式
😉安全
此外,是只有封装 SDK 这种大动做,才值得使用十六进制吗?不是的,偏偏相反,正由于 十六进制状态管理是如此地普适,乃至于连封装 SDK 都优先使用这种方式。bash
考虑到部分读者可能对十六进制自己不太了解,本文会连同十六进制一块儿介绍。ide
因此若是阅读完这篇文章,你对 十六进制的状态管理 有了感性的认识,那个人愿望也就达到了。工具
最开始对十六进制产生了兴趣,或者说,知道了它在何时能派上用场,是在 2015 年观看《火星救援》这部电影时发现的。ui
为了和 “远在 4 亿千米外、电磁波须要 40 分钟才能完成一次完整的请求响应的” 地球通讯,形单影只的主角 Mark 想到了一个办法,就是经过十六进制和 ACSII 码表:
将字符转换成字母 ,这样地球上的人就能够经过控制 “Mark 从沙漠中掏来的、1997 年服役的” 探路者号(PathFinder)的摄像头偏移角度,来指明一连串的字符,从而 Mark 能够将它们转译成英文。
第二次接触十六进制是在 2016 年,当我阅读 View/ViewGroup 源码时,发现一些状态标记都是经过十六进制状态管理,但当时由于不知道为什么这么使用、这样使用究竟有什么好处,也就没大注意。
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
复制代码
直到 2017 年的夏天,我在和一位彼时 3 年经验的同事,联手完成当年扛鼎项目的核心功能时,因同事提出使用十六进制管理状态,而亲眼见证了十六进制在状态管理方面的绝佳优点。
为了记念同事的这一分享,此后每当有新同事入职,我提供的培训课程必包含十六进制状态管理。
该项目有个需求:当指定图形编辑的模式时,图形工具栏的按钮状态要随之发生配套性地变化。
例如,存在 3 种图形编辑模式,和 8 个图形编辑按钮。
模式 A 下,要求 按钮一、按钮二、按钮3 可用,其余按钮禁用。
模式 B 下,要求 按钮一、按钮四、按钮五、按钮6 可用,其余按钮禁用。
模式 C 下,要求 按钮一、按钮七、按钮8 可用,其余按钮禁用。
若是是传统方式编写,咱们势必会在类中为 3 个模式定义 boolean 变量,为 8 个按钮状态定义 boolean 变量。
那么在模式切换时,就须要将每一个按钮状态的变量都 “清洗” 一遍。例如:
public void setModeA() {
status1 = true;
status2 = true;
status3 = true;
status4 = false;
status5 = false;
status6 = false;
status7 = false;
status8 = false;
}
public void setModeB() {
status1 = true;
status2 = false;
status3 = false;
status4 = true;
status5 = true;
status6 = true;
status7 = false;
status8 = false;
}
public void setModeC() {
...
}
复制代码
那要是往后模式变多、按钮状态变多,类中就会尽是这种 setMode 的方法,看起来很蠢,并且密密麻麻的 true、false,极容易出错。
这是一点。
另外一点就是,若是按钮状态是用 boolean 变量来管理,那么状态的存储和读取怎么办呢?
这工做量也太大了!并且往后每添加或修改一个状态,数据库都要新增或修改字段,这很是低效和不安全!
十六进制能够作到:
在具体了解十六进制是怎么作到状态管理最佳实践以前,咱们先简单过一遍十六进制自己的运做机制。
首先,在编程中,利用开头 0x 表示十六进制数。
例如 0x0001,0x0002。
而后,十六进制的计算,咱们能够借助二进制的 “按位计算” 方式来理解。
二进制存在 与、或、异或、取反 等操做:
a & b,a | b,a ^ b,~a
复制代码
例如,十六进制数 0x0004 | 0x0008,能够理解为:
0100
|
1000
=
1100
复制代码
十六进制 (0x0004 | 0x0008) & 0x0004 能够获得:
1100
&
0100
=
0100
复制代码
也即状态集中包含某状态时,再与上该状态,就会获得非 0 的结果。
因而,咱们就能够利用这个特性来完成状态管理:
private int STATUSES;
复制代码
private final int STATUS_1 = 0x0001;
private final int STATUS_2 = 0x0002;
private final int STATUS_3 = 0x0004;
private final int STATUS_4 = 0x0008;
private final int STATUS_5 = 0x0010;
private final int STATUS_6 = 0x0020;
private final int STATUS_7 = 0x0040;
private final int STATUS_8 = 0x0080;
private final int MODE_A = STATUS_1 | STATUS_2 | STATUS_3;
private final int MODE_B = STATUS_1 | STATUS_4 | STATUS_5 | STATUS_6;
private final int MODE_C = STATUS_1 | STATUS_7 | STATUS_8;
复制代码
STATUSES | STATUS_1
复制代码
STATUSES & ~ STATUS_1
复制代码
public static boolean isStatusEnabled(int statuses, int status) {
return (statuses & status) != 0;
}
复制代码
STATUSES = MODE_A;
复制代码
如此,复杂度从 m * n 骤减为 m + n,随着往后模式和状态的增多,十六进制的优点将指数级增加!
是否是超简洁?不再须要定义和修改各类 “setModeXXX” 方法了。
并且这还只是一半。另外一半是关于十六进制状态的存取。
因为状态集是 int 类型,于是咱们最少只需一个字段,便可存储状态集:
insert into tableXXX TITLE,DATE,STATUS values ('xxx','20190703',32)
复制代码
读取也十分简单,读取后直接赋值给 STATUSES 便可。
除此以外,你还能够直接在 SQL 中经过按位计算来查询!例如查询包含状态 0x0004 的记录:
select * from tableXXX where STATUS & 4 != 0
复制代码
在没有十六进制的日子里,状态管理是个繁琐的、极易出错的操做。
有了十六进制后:
这样说,你理解了吗?
看不过瘾?这里只为你 而准备了一份 简洁有力的 《重学安卓》认知地图 😉
Q1: 细心的朋友可能注意到了,上文声明的状态都是 一、二、四、8,而后进一位继续。
为何这样使用呢?
由于当十六进制转成二进制来计算时,十六进制的每位数占 4 个二进制位,例如:
0x0001 0x0004 0x0020
👇 👇 👇
0001 0100 0010 0000
复制代码
而且,惟有独占每个二进制位,咱们才能区分出不一样的状态。
于是咱们对十六进制数的每一位只安排 一、二、四、8,一旦用完,就前进一位继续。
Q2: 那既然如此,状态的声明为什么不直接用二进制来表示呢?
—— 一目了然的 0x4218 和密密麻麻的 0100001000011000b,在代码中声明 哪一个更费时、更容易出错呢? —— 特别是当有 20、30 个状态要声明的时候呢?