The Coding Kata: FizzBuzzWhizz in Java8

Functional programming leads to deep insights into the nature of computation. -- Martin Odersky

形式化

FizzBuzzWhizz詳細描述請自行查閱相關資料。此處以3, 5, 7為例,形式化地描述一下問題。

r1
- times(3) -> Fizz
- times(5) -> Buzz
- times(7) -> Whizz
r2
- times(3) && times(5) && times(7) -> FizzBuzzWhizz
- times(3) && times(5) -> FizzBuzz
- times(3) && times(7) -> FizzWhizz
- times(5) && times(7) -> BuzzWhizz
r3
- contains(3) -> Fizz
- the priority of contains(3) is highest
rd
- others -> others

接下來我將使用Scala嘗試FizzBuzzWhizz問題的設計和實現。

語義模型

從上面的形式化描述,可以很容易地得到FizzBuzzWhizz問題的語義模型。

Rule: (Int) -> String
Matcher: (Int) -> Boolean
Action: (Int) -> String

其中,Rule存在三種基本的類型:

Rule ::= atom | allof | anyof

三者之間構成了「樹型」結構。

atom: (Matcher, Action) -> String
allof: rule1 && rule2 ... 
anyof: rule1 || rule2 ... 

測試用例

借助Java8增強了的「函數式」能力,可拋棄掉很多重復的「樣板代碼」,使得設計更加簡單、漂亮。此外,Java8構造DSL的能力也相當值得稱贊,非常直接,簡單。測試用例此處選擇Spock(基于Groovy語言),可提高用例的可讀性和可維護性。

class RuleSpec extends Specification {
  private static def spec() {
    Rule r1_3 = atom(times(3), to("Fizz"))
    Rule r1_5 = atom(times(5), to("Buzz"))
    Rule r1_7 = atom(times(7), to("Whizz"))

    Rule r1 = anyof(r1_3, r1_5, r1_7)

    Rule r2 = anyof(allof(r1_3, r1_5, r1_7),
        allof(r1_3, r1_5),
        allof(r1_3, r1_7),
        allof(r1_5, r1_7))

    Rule r3 = atom(contains(3), to("Fizz"))
    Rule rd = atom(always(true), nop())

    anyof(r3, r2, r1, rd)
  }

  def "fizz buzz whizz"() {
    expect:
    spec().apply(n) == expect

    where:
    n            | expect
    3            | "Fizz"
    5            | "Buzz"
    7            | "Whizz"
    3 * 5 * 7    | "FizzBuzzWhizz"
    3 * 5        | "FizzBuzz"
    3 * 7        | "FizzWhizz"
    (5 * 7) * 2  | "BuzzWhizz"
    13           | "Fizz"
    35 /* 5*7 */ | "Fizz"  /* not "BuzzWhizz" */
    2            | "2"
  }
}

匹配器:Matcher

Matcher是一個「一元函數」,入參為Int,返回值為Boolean,是一種典型的「謂詞」。從OO的角度看,always是一種典型的Null Object

import static java.lang.String.valueOf;

@FunctionalInterface
public interface Matcher {
  boolean matches(int n);

  static Matcher times(int n) {
    return x -> x % n == 0;
  }

  static Matcher contains(int n) {
    return x -> valueOf(x).contains(valueOf(n));
  }

  static Matcher always(boolean bool) {
    return n -> bool;
  }
}

執行器:Action

Action也是一個「一元函數」,入參為Int,返回值為String,其本質就是定制常見的map操作,將定義域映射到值域。

@FunctionalInterface
public interface Action {
  String to(int n);

  static Action to(String str) {
    return n -> str;
  }

  static Action nop() {
    return n -> String.valueOf(n);
  }
}

規則:Rule

Composition Everywhere

RuleFizzBuzzWhizz最核心的抽象,也是設計的靈魂所在。從語義上Rule分為2種基本類型,并且兩者之間形成了優美的、隱式的「樹型」結構,體現了「組合式設計」的強大威力。
- Atom

  • Compositions: anyof, allof

Rule是一個「二元函數」,入參為(Int),返回值為String

@FunctionalInterface
public interface Rule {
  String apply(int n);
}

Rules為一個工廠類,用于生產各種Rule。其中,allof, anyof使用了Java8 StreamAPI

public final class Rules {
  public static Rule atom(Matcher matcher, Action action) {
    return n -> matcher.matches(n) ? action.to(n) : "";
  }

  public static Rule anyof(Rule... rules) {
    return n ->sstream(n, rules)
        .filter(s -> !s.isEmpty())
        .findFirst()
        .orElse("");
  }

  public static Rule allof(Rule... rules) {
    return n -> sstream(n, rules)
        .collect(joining());
  }

  private static Stream<String> sstream(int n, Rule[] rules) {
    return Arrays.stream(rules)
        .map(r -> r.apply(n));
  }

  private Rules() {
  }
}

源代碼

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,406評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,034評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,413評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,449評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,165評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,559評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,606評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,781評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,327評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,084評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,278評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,849評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,495評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,927評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,172評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,010評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,241評論 2 375

推薦閱讀更多精彩內容