序言
最近FizzBuzzWhizz比较热,很多OSCER们也写出了自己的版本,有写的最快的,有写的最短的。
前面写过一篇文章叫 悠然乱弹:拉钩网FizzBuzzWhizz试题之悠然版解答,是悠然闲来无事写的一种算法,当时的文章只有写了实现与结果,但是没有详细说明作者为什么这么设计,所以导致一些人可能没有看明白,觉得有些设计是脱裤子放屁,多此一举。
今天悠然就来谈谈,悠然为什么这么设计,这么设计有什么好处?以与广大朋友们分享。
从题目来看,并不复杂,就是几种报数规则,并且有一些解决冲突时的规则,然后同学们就可以按规则进行游戏了。但是很显然,体育老师不希望每天玩的游戏都一样,同学们也不希望每天玩一样的游戏,这样就可能导致一个必然会出现的结果,那就是:游戏规则及解决冲突的规则可以方便的进行变换。
示例
但是这个时候,程序架构必须要保证,当游戏规则或冲突解决规则出现的时候,程序的代码修改量及修改范围要最小化,如果能达到这一目标,说明程序的架构与设计是合理的。
由于输出100,数量太多,因此悠然把输出数量调整为20,来看看悠然的FizzBuzzWhizz是怎么玩的:
1.只加入普通读法
1
2
3
4
5
6
7
|
public
static
void
main(String[] args) {
NumberReaderEngine numberReaderEngine =
new
NumberReaderEngine();
numberReaderEngine.add(
new
CommonNumberReader(
1
));
for
(
int
i =
1
; i <=
20
; i++) {
numberReaderEngine.readNumber(i);
}
}
|
运行结果:
1
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
2. 只加入普通读法及一个能整除某个数字的规则
a.只加入普通读法及整除数字3的规则
1
2
3
4
5
6
7
8
|
public
static
void
main(String[] args) {
NumberReaderEngine numberReaderEngine =
new
NumberReaderEngine();
numberReaderEngine.add(
new
MultipleNumberReader(
2
,
3
,
"Fizz"
));
numberReaderEngine.add(
new
CommonNumberReader(
1
));
for
(
int
i =
1
; i <=
20
; i++) {
numberReaderEngine.readNumber(i);
}
}
|
运行结果:
1
|
1
2
Fizz
4
5
Fizz
7
8
Fizz
10
11
Fizz
13
14
Fizz
16
17
Fizz
19
20
|
b.只加入普通读法及整除数字3,5的规则
1
2
3
4
5
6
7
8
9
|
public
static
void
main(String[] args) {
NumberReaderEngine numberReaderEngine =
new
NumberReaderEngine();
numberReaderEngine.add(
new
MultipleNumberReader(
2
,
3
,
"Fizz"
));
numberReaderEngine.add(
new
MultipleNumberReader(
2
,
5
,
"Buzz"
));
numberReaderEngine.add(
new
CommonNumberReader(
1
));
for
(
int
i =
1
; i <=
20
; i++) {
numberReaderEngine.readNumber(i);
}
}
|
1
|
1
2
Fizz
4
Buzz Fizz
7
8
Fizz Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
|
c.只加入普通读法及整除数字3,7的规则
1
2
3
4
5
6
7
8
9
10
|
public
static
void
main(String[] args) {
NumberReaderEngine numberReaderEngine =
new
NumberReaderEngine();
numberReaderEngine.add(
new
MultipleNumberReader(
2
,
3
,
"Fizz"
));
numberReaderEngine.add(
new
MultipleNumberReader(
2
,
5
,
"Buzz"
));
numberReaderEngine.add(
new
MultipleNumberReader(
2
,
7
,
"Whizz"
));
numberReaderEngine.add(
new
CommonNumberReader(
1
));
for
(
int
i =
1
; i <=
20
; i++) {
numberReaderEngine.readNumber(i);
}
}
|
1
|
1
2
Fizz
4
Buzz Fizz Whizz
8
Fizz Buzz
11
Fizz
13
Whizz FizzBuzz
16
17
Fizz
19
Buzz
|
1
2
3
4
5
6
7
8
9
10
11
12
|
public
static
void
main(String[] args) {
NumberReaderEngine numberReaderEngine =
new
NumberReaderEngine();
numberReaderEngine.add(
new
MultipleNumberReader(
2
,
3
,
"Fizz"
));
numberReaderEngine.add(
new
MultipleNumberReader(
2
,
5
,
"Buzz"
));
numberReaderEngine.add(
new
MultipleNumberReader(
2
,
7
,
"Whizz"
));
numberReaderEngine.add(
new
IncludeNumberReader(
3
,
3
,
"Whizz"
));
numberReaderEngine.add(
new
CommonNumberReader(
1
));
numberReaderEngine.sortNumberReader();
for
(
int
i =
1
; i <=
20
; i++) {
numberReaderEngine.readNumber(i);
}
}
|
运行结果:
1
|
1
2
Whizz
4
Buzz Fizz Whizz
8
Fizz Buzz
11
Fizz Whizz Whizz FizzBuzz
16
17
Fizz
19
Buzz
|
目前为止,体育老师拿到悠然写的程序,通过调整游戏规则及其优先级,可以有N种玩法,但是除了调用代码之外,不必修改任何代码。
调用代码是什么?是体育老师在开始玩游戏之前宣布的游戏规则,而游戏规则是要经常变化的,要不就没有新意了(具体到业务中,就是无法适应业务的变化了)。
小结
FizzBuzzWhizz确实是一道非常有代表意义的试题,它可以做得很简单,也可以做得很复杂。
悠然把游戏运行机理的内容归到不变的部分,把游戏规则的扩展及游戏规则的声明归到变的部分。从而保证了FizzBuzzWhizz具有良好的架构稳定性及扩展性,同时也对游戏的可玩性提供了良好的支持。
有的同学问,为什么在玩游戏之前要执行一下下面的语句:
1
|
numberReaderEngine.sortNumberReader();
|
有几种做法,一种是把直接放到readNumber方法中,好处是对调用者不可见,缺点是性能会稍差。
一种做法是把规则列表直接通过构造方法传入,但是带来的问题是规则不可以后续进行调整。
另外一种是通过set方法设置进去,然后在里面进行排序,这种就需要,每次整个传入。
最后一种是放在add方法之内,每个添加一个规则进行进行一次排序,这同样会导致性能会差一点点。
当然这里只是一个示例,因此在这里单独调用一下,也没有太大问题。
同样做了 FizzBuzzWhizz试题的同学,也可以思考一下,如果也要完成上面的各种游戏变化,代码上的变化是否容易呢?