从易于扩展扩展的角度来设计FizzBuzzWhizz

时间:2021-12-16 09:05:21

更多内容参见本人网站:www.tinygroup.org

序言

最近FizzBuzzWhizz比较热,很多同学们也写出了自己的版本,有写的最快的,有写的最短的。

前面写过一篇文章叫 悠然乱弹:拉钩网FizzBuzzWhizz试题之悠然版解答,是悠然闲来无事写的一种算法,当时的文章只有写了实现与结果,但是没有详细说明作者为什么这么设计,所以导致一些人可能没有看明白,觉得有些设计是脱裤子放屁,多此一举。

今天悠然就来谈谈,悠然为什么这么设计,这么设计有什么好处?以与广大朋友们分享。

从题目来看,并不复杂,就是几种报数规则,并且有一些解决冲突时的规则,然后同学们就可以按规则进行游戏了。但是很显然,体育老师不希望每天玩的游戏都一样,同学们也不希望每天玩一样的游戏,这样就可能导致一个必然会出现的结果,那就是:游戏规则及解决冲突的规则可以方便的进行变换。

示例

但是这个时候,程序架构必须要保证,当游戏规则或冲突解决规则出现的时候,程序的代码修改量及修改范围要最小化,如果能达到这一目标,说明程序的架构与设计是合理的。

由于输出100,数量太多,因此悠然把输出数量调整为20,来看看悠然的FizzBuzzWhizz是怎么玩的:

1.只加入普通读法

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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

2. 只加入普通读法及一个能整除某个数字的规则

a.只加入普通读法及整除数字3的规则

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 2 Fizz 4 5 Fizz 7 8 Fizz 10 11 Fizz 13 14 Fizz 16 17 Fizz 19 20

b.只加入普通读法及整除数字3,5的规则

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 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz 

c.只加入普通读法及整除数字3,7的规则

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 2 Fizz 4 Buzz Fizz Whizz 8 Fizz Buzz 11 Fizz 13 Whizz FizzBuzz 16 17 Fizz 19 Buzz 

3.加入普通规则及整除规则及包含规则
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 2 Whizz 4 Buzz Fizz Whizz 8 Fizz Buzz 11 Fizz Whizz Whizz FizzBuzz 16 17 Fizz 19 Buzz

目前为止,体育老师拿到悠然写的程序,通过调整游戏规则及其优先级,可以有N种玩法,但是除了调用代码之外,不必修改任何代码。

调用代码是什么?是体育老师在开始玩游戏之前宣布的游戏规则,而游戏规则是要经常变化的,要不就没有新意了(具体到业务中,就是无法适应业务的变化了)。

小结

FizzBuzzWhizz确实是一道非常有代表意义的试题,它可以做得很简单,也可以做得很复杂。

悠然把游戏运行机理的内容归到不变的部分,把游戏规则的扩展及游戏规则的声明归到变的部分。从而保证了FizzBuzzWhizz具有良好的架构稳定性及扩展性,同时也对游戏的可玩性提供了良好的支持。

有的同学问,为什么在玩游戏之前要执行一下下面的语句:

numberReaderEngine.sortNumberReader();

有几种做法,一种是把直接放到readNumber方法中,好处是对调用者不可见,缺点是性能会稍差。

一种做法是把规则列表直接通过构造方法传入,但是带来的问题是规则不可以后续进行调整。

另外一种是通过set方法设置进去,然后在里面进行排序,这种就需要,每次整个传入。

最后一种是放在add方法之内,每个添加一个规则进行进行一次排序,这同样会导致性能会差一点点。

当然这里只是一个示例,因此在这里单独调用一下,也没有太大问题。

同样做了  FizzBuzzWhizz试题的同学,也可以思考一下,如果也要完成上面的各种游戏变化,代码上的变化是否容易呢?