kata文中图片均来自互联网
Kata一词来源于空手道的形或招式,而Code kata或者称之为编码招式的概念由David Thomas提出,是指针对某一种特定技术或技能进行重复性的练习,从而将其熟练掌握。由此诞生了不少编码小题来进行Code kata,这次我们星云道场的目标就是通过一个经典的题目FizzBuzz来练习kata。
背景介绍
FizzBuzz的背景很简单(如下),大致的意思就是编一个程序,它将打印1~100的数字,当遇到3的倍数时打印Fizz,当遇到5的倍数时打印Buzz,而当遇到3和5的倍数时则打印FizzBuzz。
Write a program that prints the numbers from 1 to 100. But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz”. For numbers which are multiples of both three and five print “FizzBuzz “.
当完成上述任务后,还将给出一个阶段二的目标(如下),即不仅3的倍数要打印Fizz,数字中包含3的也需要输出Fizz,而类似地,不仅5的倍数要打印Buzz,数字中包含5的也需要打印Buzz。
A number is fizz if it is divisible by 3 or if it has a 3 in it
A number is buzz if it is divisible by 5 or if it has a 5 in it
由于逻辑简单,此题是TDD的入门教案,除此之外还可以练习设计模式的运用,因此这一道小小的题目其中蕴含的能量却是相当丰富的。
实现
使用TDD的方法很快就能实现阶段一,测试代码如下:
@Test
public void should_output_fizz_when_multiple_of_3() throws Exception {
// given
// when
final String out1 = fizzBuzz.output(3);
final String out2 = fizzBuzz.output(99);
// then
assertThat(out1, is("Fizz"));
assertThat(out2, is("Fizz"));
}
@Test
public void should_output_buzz_when_multiple_of_5() throws Exception {
// given
// when
final String out1 = fizzBuzz.output(5);
final String out2 = fizzBuzz.output(100);
// then
assertThat(out1, is("Buzz"));
assertThat(out2, is("Buzz"));
}
FizzBuzz的实现代码也不复杂,如下所示:
public class FizzBuzz {
public String output(int number) {
if (number %3 == 0 && number % 5 == 0) {
return "FizzBuzz";
}
if (number % 5 == 0) {
return "Buzz";
}
if (number % 3 == 0) {
return "Fizz";
}
return String.valueOf(number);
}
}
这样看起来似乎就完成了FizzBuzz,你也许会说这样太简单了,kata也不过如此,先别这么着急下结论,让我们来看看它的变换。
变换
分析一下FizzBuzz的实现,方法的主体是三个条件分支,自上而下分布,并且这种顺序不能任意地调整,这是一种隐性的约束;再细想一下,就会发现所有的分支之后其实还包括了一个缺省的行为,即对于所有不满足条件的输入直接输出,所以从流程看FizzBuzz就是将4个条件串联后顺序执行。
上面的分析反复地强调了执行顺序,这种隐性的约束是否有办法将其显示地表达出来呢?答案是肯定的,不过在这之前还需要考察一下FizzBuzz使用的条件。
条件是什么?是规则、约束或者策略,无论是哪一种都可以从概念上形成一个聚合体,或者用编码的话来讲就是对象或封装,而规则的任务就是执行一个动作,所以可以抽象如下:
public interface Rule {
String out(int number);
}
回到之前的执行顺序上,要想显式地表达,可以有两种方式。一种是规则之间的相互连接,比如责任链模式;另一种是借助有序集合,比如策略模式。
责任链模式是一种设计模式。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。
责任链模式需要显式地为每个规则指定明确的后续处理者,按照这个模式,上述的抽象和规则就变换为:
public abstract class Rule {
private Rule successor;
public abstract String out(int number);
public void setSuccessor(Rule successor) {
this.successor = successor;
}
}
public class FizzRule extends Rule{
@Override
public String out(int number) {
if (number % 3 == 0) {
return "Fizz";
}
return getSuccessor().out(number);
}
}
在此基础上进一步重构,利用模板模式进一步减少重复代码,如下。并且再次分析规则又可发现规则之间还存在组合关系,比如Fizz规则与Buzz规则的合集就是FizzBuzz规则,如下。
public abstract class Rule {
private Rule successor;
public String out(int number) {
return verify (number) ? execute(number) : successor.out(number);
}
protected abstract String execute(int number);
protected abstract boolean verify(int number);
public void setSuccessor(Rule successor) {
this.successor = successor;
}
}
public class FizzRule extends Rule{
@Override
protected String execute(int number) {
return "Fizz";
}
@Override
protected boolean verify(int number) {
return number % 3 == 0;
}
}
public class NopRule extends Rule{
@Override
protected String execute(int number) {
return String.valueOf(number);
}
@Override
protected boolean verify(int number) {
return true;
}
}
public class FizzBuzz {
private Rule rule;
public FizzBuzz() {
rule = new FizzBuzzRule();
Rule buzz = new BuzzRule();
Rule fizz = new FizzRule();
Rule nop = new NopRule();
rule.setSuccessor(buzz);
buzz.setSuccessor(fizz);
fizz.setSuccessor(nop);
}
public String output(int number) {
return rule.out(number)
}
}
体会
现在,一个与第一版实现完全不一样的FizzBuzz就诞生了,由于这是一个利用设计模式(责任链、模板、组合)重构的FizzBuzz,因此我们可以把它称之为设计模式kata。
还有这种sao操作就像我们在开篇所说,虽然FizzBuzz是个很小的程序,但它蕴藏的能力却是非常大的,这就是kata的神奇之处,可以根据编程者的需要进行多样化的专题练习。
不过此篇描述的FizzBuzz实现基本上还是中规中矩,只是利用一些技巧来进行转换,在下篇文章中我将从另一个的角度继续探讨FizzBuzz。
网友评论