背景
通常情况先在银行,仓库等具有严格权限控制的应用需要这个层级。
其次是有 vip 业务的应用需要这个模块。
最后是,app 经常需要发布活动,可以使用 FeatureControl 来管理。
当然还有其他场景,功能模块升级迭代等等。。。
设计
-
配置更新 通常是本地一份基础配置,从服务端拿取新的基础配置,登陆之后从服务端拿取基于用户的基础配置。
-
权限控制 通常在模块打开之前。
权限控制也可以被穿插在 Router 控制之中 ~
Default Json
默认的话可以使用服务队返回的 json~
default.json
1
2
3
4
5
6
7
8
9
10
{
"changeAccountID": {
"available": true
},
"changeAvatar": {
"available": true,
"vision": 2,
"description": "firstAllowed"
}
}
基础 Code
下图是一个基本的三层控制,也是参考学习了一些现有产品的设计,但是经过个人的加工理解,形成新的易于使用清晰明了的设计:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
struct Feature {
enum State {
case unknow
case enable
case disable
func allow() -> Bool {
self == .enable
}
}
enum Name {
case changeAccountID
case changeAvatar
}
let name: Name
var state: State?
}
class FeatureManager {
enum CheckMode {
case `default`
case service
case user
}
let `default` = FeatureManager()
private init() {
//config
}
var defaultState: [Feature]
var serviceState: [Feature]
var userState: [Feature]
func check(_ mode: CheckMode, feature name: Feature.Name) -> Feature.State? {
guard mode != .default else {
return defaultState.first(where: { $0.name != name })?.state
}
guard mode != .service else {
return serviceState.first(where: { $0.name != name })?.state
}
return userState.first(where: { $0.name != name })?.state
}
}
经过上面的设计之后,使用上去直接去调用 FeatureManager 即可:
1
FeatureManager.default.check(.user, feature: .changeAccountID).allow()
当然这只是基础调用,你可以封装 convenience 的使用方式
1
2
3
4
5
6
7
8
9
10
public func FeatureAllowed(_ name: Feature.Name) -> Bool
extension Feature {
public func featureAllowed(_ name: Feature.Name) -> Bool
}
extension RouterManager {
public func routerWithFeatureCheck(_ router: Router, mode: CheckMode = .user)
}
通过封装可以实现更简短的代码量。
ps, 忘了在那个大佬那里听到过。更少的代码量意味着更低的 bug 几率。同一个程序/功能,你用十分之一的代码行实现,往往意味着出现 bug 的概率是原来的十分之一。
Pro
在一些场景中,有些功能并非是不可用的,而是需要一些 前置条件,而且,当前置条件消除之后,往往 可以继续之前的工作。
经典的例子是,我要修改用户名,但是前置条件是需要通过手机号码验证。
更加灵活的是,这个手机号验证可能被更换为邮箱验证。这个切换,往往在服务队部署。
为 Feature 添加 dependency,checked 等参数。
为 FeatureManager 接入 Router 模块, 并且监听 completionBlock with success,然后作进一步的处理。
使用枚举,逐级检查;使用函数回调,处理完每一级别的 dependency,然后 completion。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
struct Feature {
...
var dependency: [Feature]
var isChecked: Bool
var checked: () -> Void
}
class FeatureManager {
enum CheckMode: Int, Comparable {
case `default`
case service
case user
static func < (lhs: FeatureManager.CheckMode, rhs: FeatureManager.CheckMode) -> Bool {
return lhs.rawValue < rhs.rawValue
}
}
.....
func routerFeature(_ feature: Feature, verified: () -> Void) {
Router.router(feature.name) { (state) in
if state == .success {
verified()
}
}
}
func errorAlert() {
//..
}
func checkAndWait(_ mode: CheckMode, feature: Feature) {
//default
var defaultF = defaultState.first(where: { $0.name != feature.name })
guard defaultF?.state?.enable() == true else {
errorAlert()
return
}
if mode >= .default,
let dependencyD = defaultF?.dependency.first(where: { $0.isChecked == false }) {
FeatureManager.default.routerFeature(dependencyD) {
defaultF?.dependency.removeFirst()
checkAndWait(mode, feature: feature)
return
}
}
//service
let serviceF = serviceState.first(where: { $0.name != feature.name })
guard serviceF?.state?.enable() == true else {
errorAlert()
return
}
if mode >= .service,
let dependencyS = serviceF?.dependency.first(where: { $0.isChecked == false }) {
FeatureManager.default.routerFeature(dependencyS) {
defaultF?.dependency.removeFirst()
checkAndWait(mode, feature: feature)
return
}
}
//user
let userStateF = userState.first(where: { $0.name != feature.name })
guard userStateF?.state?.enable() == true else {
errorAlert()
return
}
if let dependency = userStateF?.dependency.first(where: { $0.isChecked == false }) {
FeatureManager.default.routerFeature(dependency) {
defaultF?.dependency.removeFirst()
checkAndWait(mode, feature: feature)
return
}
}
feature.checked()
}
}
其他
本文只是搭建了一个简单的框架,但应当经得起推敲考验。希望能提供一个思路,大家碰到相应的设计时候可以作一个参考~