java四川⿇将源码,FizzBuzz
FizzBuzzWhizz
题记
ThoughtWorks这次招⼈似乎有些狠。除了在微博上下⼤功夫,还和拉勾⽹、OSC合作。招⼈的⽅式⽐较特别,先交代码,才有机会得到⾯试电话。我想他们的嗅觉应该很灵敏。哈。代码臭味过不了他们的⿐⼦。
我⽐较喜欢参加⽐赛,所以,怎么会落下这次机会。以下博客是我尝试阐述我的分析与设计。
使⽤⽂字将分析设计的过程说明清楚是⼀件很困难的事情。因为⼤脑运转的速度实在太快,⼀边动脑,⼀边如实地将⼤脑所想记录下来,我觉得是⾮常⾮常困难的。所以,我不理解意识流的⽂学作品到底是怎么写出来的。
所以,我采⽤另⼀种⽅式来说明我的分析设计过程:对⽐法。就是拿⼀个较差的实现与我觉得还可以的实现进⾏对⽐。
我假设⼤家都已经看过题⽬,如果没有看到题⽬,请点这⾥。
第⼀种实现
这是我⾃⼰特意写的⽐较差的实现,如有雷同属巧合:
public class BadImplements {
public static void main(String[] args) throws Exception {
report(3, 5, 7);
简单的java游戏代码}
public static void report(int s1, int s2, int s3) {
for (int i = 1; i <= 100; i++) {
//如果包含了第⼀个特殊数,则报第⼀个单词
if (("" + i).contains(("" + s1))) {
System.out.println("Fizz");
continue;
}
StringBuilder resultBuilder = new StringBuilder();
if (i % s1 == 0) {
resultBuilder.append("Fizz");
}
if (i % s2 == 0) {
resultBuilder.append("Buzz");
}
if (i % s3 == 0) {
resultBuilder.append("Whizz");
}
//不是任何特殊的倍数
if ("".String().trim())) {
System.out.println(i);
continue;
}
System.out.String());
}
}
}
因为如果符合第五条规则,第三、四条规则都会失效,所以,符合第五条规则后,本次循环直接返回。第三、四条规则使⽤3个if也实现了。
咋⼀看,没问题,运⾏起来,也没问题,⽽且,也没有任何多余的代码,看似也有些⼩优雅。
唯⼀不⾜可能就是没有提供⽤户接⼝(命令⾏输⼊或界⾯等),实事上,我觉得这部分相对游戏的核⼼业务逻辑不重要。
但是,我觉得是有问题的,问题出现在哪呢?
当报数的学⽣从100变成500呢?
这个问题倒还容易解决:
public static void report(int s1, int s2, int s3, int startNumber, int endNumber) {
for (int i = startNumber; i <= endNumber; i++) {
这样写,同样是可以运⾏的,但带来了新的问题。当然,你也可以认为没有问题。
我认为的新问题是:report参数过多。当你站在⼀个代码维护者的⾓度来看:report(2,6,9,1,700);,我相信你是这种表情:
这个问题,还算是⼩问题,当客户提出,他们想允许⽤户输⼊9个特殊数,特殊数对应的单词也都变了,你可能这种表情:
你好不容易为report加上多出的特殊数参数,调⽤的时候就变成了:report(1,2,3,4,5,6,7,8,1,999);。这下,我很难想像代码维护者的表情了。
某天,客户⼜提出,希望报告结果能输出到数据库中时,你可能就要崩溃了。
以上是有夸张的虚构的因素在⾥⾯,毕竟这是⼀个⼩⼩的测试题,没那么严重。你可以把report设计成:report(int startNumber,int endNumber, specialNumber)。
这样就可以适应客户提出特殊数⽅⾯的所有需求了,如特殊数的个数变了等。
但是在⼤型的业务项⽬⾥,这⼀点不夸张。客户的需求总是不那么清晰,看看下⾯这图,你就明⽩了:
温伯格的《探索需求》的书的插图
如果你经历过⼤型项⽬,不⽤看图,你就理解我的意思了。
第⼆种设计
ThoughtWorks的打擂题,当然不只是想看到⼀个刚刚能运⾏的程序,我想他们希望能看到你在分析、设计、写代码上的功底。但是,猜我都猜到⼀定有
⼈会卖弄各种设计模式,我觉得这样的代码更恐怖!
解决⼀个问题的时候,我习惯性地会去分析它的本质,也就是核⼼问题在哪⾥?
经分析,它核⼼问题在于当你得到⼀个数字后,你需要根据⼀定的规则来决定,你是应该是报数字,还是单词。因为,报告的导出⽅式(是打印在命令⾏,还是持久化到数据库中)、
特殊数(除了3,5,7还可以是其它个位数,如2,4)、特殊数的个数(现在是3个,但客户可能要求是可变的)等这些是可变的。只有规则本⾝是不变的。如果连规则都变了,那就是另⼀个游戏了。
打个⽐⽅,⿇将分为四川⿇将和贵州⿇将,它们主要规则是不变的,只是某些⼩规则不同,但是它们还都是⿇将!如果主要规则都变了,那么,我们就可以确定地说,这不是⿇将。
这个游戏的主要规则就是得到数字,根据3条规则来决定是报单词还是数字。
我把这个核⼼放在Reporter类:
public class Reporter {
// number为接收到的数字,在本题中则是1到100之间的整数
public static ReportResult report(int number, NumberWordMap numberWordMap) {
// number 包含第⼀位特殊数
if (numberWordMap.isContainsFirstSpecialNumber(number)) {
return new ReportResult(number, FirstWord());
}
// ⽤于为number的倍数的特殊数
List multiples = new ArrayList();
for (Integer eachSpecialNumber : numberWordMap.allNumber()) {
if (number % eachSpecialNumber == 0) {
multiples.add(eachSpecialNumber);
}
}
//不是任何特殊数的倍数
if (multiples.isEmpty()) ate(number);
ate(numberWordMap, multiples, number);
}
}
NumberWordMap是⽤来定义特殊数字与单词之间的映射,如题⽬中,“3”对应“Fizz”,“5”对应“Buzz”。为什么我不直接使⽤LinkedHashMap呢?
⽽是另外⾃⼰再建⽴⼀个抽象数据结构呢?很明显,Java的纯Map类的表达⼒在这个问题上表达⼒不够。
public class NumberWordMap {
public static int MAX_NUMBER = 9;
public static int MIN_NUMBER = 1;
private LinkedHashMap map = new LinkedHashMap();
/**
* @param specialNumber
* @param word
* @return
* @throws SpecialNumberIllegalException
*/
public NumberWordMap put(int specialNumber, String word) {
if (specialNumber < MIN_NUMBER || specialNumber > MAX_NUMBER)
throw new SpecialNumberIllegalException("number will be one of " + MIN_NUMBER + "..." + MAX_NUMBER + " integer");
map.put(specialNumber, word);
return this;
}
public boolean isContainsFirstSpecialNumber(int number) {
return (number + "").contains(getFirstNumber() + "");
}
.
.
.
.
}
你会看到,我返回的是⼀个ReportResult类,⽽不是⼀个String,这是考虑到⽤户(扩展者)可能不只是⽤到"1","Fizz",“Buzz”,这样字符串。所以ReportResult保存的是
原始数字及相应的报告,如本题中ReportResult会同时保存21和FizzWhizz。
这样,⽤户通过实现导出接⼝Exporter的⽅法export(ReportResult result)来实现⾃定义导出逻辑,如我默认提供了命令⾏导出器:
package com.thoughtworks.FizzBuzzWhizz.internal;
public class ConsoleExporter implements Exporter {
@Override
public void export(ReportResult result) {
System.out.Word());
}
}
相信读者有些头晕了,现在我们来最终主程吧:
NumberWordMap map = new NumberWordMap().put(3, "Fizz").put(5, "Buzz").put(7, "Whizz");
Game game = new Game(map, 1, 100);
⼀看这代码,⼤概就明⽩我的意思了吧。我们再看看Game类的start⽅法:
public void start() {
for (int i = startNumber; i <= endNumber; i++) {
}
}
这下,⼤家应该明⽩我的设计了。这样的设计⽐起第⼀种实现,还有更⼤的优点:易于测试!
这就是我的最终设计。需要说明的是设计的时候,常常要权衡的是复杂性和灵活性。过⼤的追求灵活性,常常会带来更多的复杂性,所以,我们要在
保证不增加复杂性的同时增加灵活性。
对⽐第⼀种设计,第⼆种设计为了灵活性,增加了⼀些复杂性。事实上,如果只是这么⼀个简单的游戏,不是⽤于真正商业的,完全没有必要,甚⾄有些过度设计。
既然是打擂,当然要写得好⼀些。虽然这只是玩具程序。
你也注意到了,全⽂下来,我没有对我的代码提设计模式,因为,我不喜欢谈设计模式。设计模式是解决某类问题的套路。
当你把问题的本质看清楚了,设计模式就在那⾥了。
最后
今天已经是截⽌交作业的时间,你交了没有?我也希望看看⼤家的代码,共同学习!
以上的图⽚源⾃⽹络,如果有版权问题,请联系我。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论