行列转化——编程心法(一)

难度等级:地阶中级

行列转化是我用的最多的编程技巧之一,不论是对领域建模还是表结构设计都大有裨益。适用于实体向聚合的转化阶段。我将通过一系列用户故事来描述其作用。

背景

小明是某NB公司的后端开发工程师,最近NB公司发展十分迅猛,日活大涨,高层决定搞活动拉新。最后落地下来就决定以抽奖发红包的形式来搞。

Story-1

PD:抽奖咋们也没搞过,只要满足XXPay实名认证就可以抽奖吧。

小明:这简单,给我一天。

于是乎就诞生了如下代码:

1
2
3
4
5
6
7
8
9
10
11
class Lottery {

public Result execute(long uid) {
//检查XXXPay实名认证
if(!checkIsXXXPayCertifact(uid))
return Result.fail();
}
//执行真正抽奖逻辑
return doLottery();
}
}

小明直接把XXXPay验证逻辑写道了抽奖流程中,作为前置流程。

Story-2

PD:上次活动效果很好,拉新的目的达到了。我们要给高消费能力的人群再发红包,就再上次规则上加上积分大于1000的限制吧。

小明:好吧,你说什么就是什么咯。

1
2
3
4
5
6
7
8
9
10
11
class Lottery {

public Result execute(long uid) {
//检查XXXPay实名认证 && 积分大于1000
if(!checkIsXXXPayCertifact(uid) || !checkPointAbove1000(uid))
return Result.fail();
}
//执行真正抽奖逻辑
return doLottery();
}
}

Story-3

PD:不好,上次活动引来了大批羊毛党,下次活动要接一下风控系统。

小明:WTF,搞个活动真麻烦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Lottery {

public Result execute(long uid) {
//检查XXXPay实名认证
if(!checkIsXXXPayCertifact(uid)) {
return Result.fail();
}
// 积分大于1000
if(!checkPointAbove1000(uid))
return Result.fail();
}
// 风控系统校验通过
if(!isPassRiskSystem(uid))
return Result.fail();
}
//执行真正抽奖逻辑
return doLottery();
}
}

Story-4

PD: 老板们决定把抽奖做成常态工具,需要经常搞,你看看怎么弄。前置条件就那三个,但不一定都有。

小明:我就知道没啥好事。

小明认值考虑了下,这三个都是验证条件,返回结果只有true/false,我可以抽象一下提取成一个前置条件(Rule),得到如下代码。

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
class Rule {
private String name;

Rule(String name) {
this.name = name;
}

abstract public boolean check(long uid);
}

class XXXPayRule extends Rule {
XXXPay() {
super("XXXPayRule");
}

@Override
public boolean check(long uid) {
return checkIsXXXPayCertifact(uid);
}
}

class PointRule extends Rule {
//省略
}

class RiskRule extends Rule {
//省略
}

Lottery实体加了3个属性,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Lottery {
private Rule rule1;
private Rule rule2;
private Rule rule3;

public Result execute(long uid) {
if(rule1!=null!rule1.check(uid)) {
return Result.fail();
}
if(rule2!=null!rule2.check(uid)) {
return Result.fail();
}
if(rule3!=null!rule3.check(uid)) {
return Result.fail();
}
//执行真正抽奖逻辑
return doLottery();
}
}

表结构也修改为:

id name rule_1 rule_2 rule_3
1 XXX拉新活动 XXXPayRule RiskRule NULL

经过这么一修改,小明十分满意,内心OS:还有谁!现在抽奖可自由配置规则,根据需要加载,比以前灵活度高了很多。

几个月内PD再没找过小明,小明也过了几天舒服日子。

Story-5

PD:现在发展太快了,很多子产品都用我们的抽奖工具,他们的验证规则太多了。什么注册满7天,只能中一次,XXX等等。
小明:这…让我想想。

看到这么多人用小明的抽奖系统,他内心很开心,但是这么多规则不知道如何承接。

这个时候其实就要用到 行列转换 这个技巧了。小明已经有意识的将规则抽象成了一个独立的类Rule,但是却没有建立专门的表。对于不确定数量的规则,可以在Lottery中加入规则的聚合List<Rule> rules来表示独立的规则,而每个Rule对于规则表中的一行记录。这样就把Lottery表中的列rule_123转化为了rule表中的一行记录。

受到启发后,小明最终做出了如下修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Lottery {
private List<Rule> rules;

public Result execute(long uid) {
for(Rule rule:rules) {
if(!rule.check(uid)) {
return Result.fail();
}
}
//执行真正抽奖逻辑
return doLottery();
}
}

这样一来,Lottery的代码也清爽了许多,最后一丝坏味道也去除了。


回过头来看行列转化,本质上是实体(Lottery)演变成了一个聚合(Lottery)。总结一下该技巧的使用条件:

列转行

适用时机:实体演变成聚合

关键点:抽象聚合子类,建立独立表存储。

行转列

适用时机:聚合蜕变成实体

关键点:与上述过程相反

就我个人的开发经验来看,大部分情况下都是列转行,这样代表你的业务在进一步发展,很少遇到行转列行列转化这其中有个阈值,个人觉得一般超过3个比较合适。

坚持原创技术分享,您的支持将鼓励我继续创作!