FizzBuzzWhizz in C++

序言

控制復(fù)雜性是計(jì)算機(jī)編程的本質(zhì)?!?Brian Kernighan

前幾天有幸參加了劉光聰同學(xué)組織的Code Retreat活動(dòng),收獲比較大,其中印象最深刻的是結(jié)對(duì)編程實(shí)踐。通過和幾位高手的結(jié)對(duì),開了眼界,他們不光設(shè)計(jì)能力超強(qiáng),而且是鍵盤俠,兩手一抹,數(shù)行高質(zhì)量代碼就躍然眼前,非常過癮。
在Code Retreat活動(dòng)后,筆者打算寫篇文章梳理一下學(xué)到的知識(shí),同時(shí)分享給大家。

FizzBuzzWhizz問題

FizzBuzzWhizz問題是某公司的面試題目,具體如下所示:

你是一名體育老師,在某次課距離下課還有五分鐘時(shí),你決定搞一個(gè)游戲。此時(shí)有100名學(xué)生在上課。游戲的規(guī)則是:

  1. 你首先說出三個(gè)不同的特殊數(shù),要求必須是個(gè)位數(shù),比如3、5、7。
  1. 讓所有學(xué)生拍成一隊(duì),然后按順序報(bào)數(shù)。
  1. 學(xué)生報(bào)數(shù)時(shí),如果所報(bào)數(shù)字是第一個(gè)特殊數(shù)(3)的倍數(shù),那么不能說該數(shù)字,而要說Fizz;如果所報(bào)數(shù)字是第二個(gè)特殊數(shù)(5)的倍數(shù),那么要說Buzz;如果所報(bào)數(shù)字是第三個(gè)特殊數(shù)(7)的倍數(shù),那么要說Whizz。
  1. 學(xué)生報(bào)數(shù)時(shí),如果所報(bào)數(shù)字同時(shí)是兩個(gè)特殊數(shù)的倍數(shù)情況下,也要特殊處理,比如第一個(gè)特殊數(shù)和第二個(gè)特殊數(shù)的倍數(shù),那么不能說該數(shù)字,而是要說FizzBuzz, 以此類推。如果同時(shí)是三個(gè)特殊數(shù)的倍數(shù),那么要說FizzBuzzWhizz。
  1. 學(xué)生報(bào)數(shù)時(shí),如果所報(bào)數(shù)字包含了第一個(gè)特殊數(shù),那么也不能說該數(shù)字,而是要說相應(yīng)的單詞,比如本例中第一個(gè)特殊數(shù)是3,那么要報(bào)13的同學(xué)應(yīng)該說Fizz。如果數(shù)字中包含了第一個(gè)特殊數(shù),那么忽略規(guī)則3和規(guī)則4,比如要報(bào)35的同學(xué)只報(bào)Fizz,不報(bào)BuzzWhizz。
  1. 否則,直接說出要報(bào)的數(shù)字。

DDD建模

該問題域只涉及一個(gè)BC(Bounded Context,限界上下文),我們先找UL(Ubiquitous language,通用語言)。

通用語言

  1. 題目中有三個(gè)數(shù),我們假定為(n1, n2, n3),(3, 5, 7)是這三個(gè)數(shù)的一個(gè)例子。
  2. 原子操作記作atom,題目中有三個(gè)原子操作,分別為倍數(shù)times、包含contains和默認(rèn)default。
  3. 一個(gè)數(shù)如果是ni(i=1,2,3)的倍數(shù),我們記作times_ni,如果包含ni,我們記作contains_ni。
  4. 每個(gè)atom包含兩部分,即匹配器和執(zhí)行器二元組,記作(matcher, Action),那么針對(duì)三個(gè)原子操作,就有(matcher_times_ni, action_times_ni)、(matcher_contains_ni, action_contains_ni)和(matcher_default, action_default)
  5. 題目中有多個(gè)規(guī)則rule,atom是基本的rule,rule可以組合成新rule,組合可以是“與”的關(guān)系allof,也可以是“或”的關(guān)系anyof。

語義模型

我們將rule簡(jiǎn)寫為r,使用UL形式化表達(dá)一下問題域:

r1_n1 = atom(matcher_times_n1, action_times_n1) -> (true, "Fizz") | (false, "")
r1_n2 = atom(matcher_times_n2, action_times_n2) -> (true, "Buzz") | (false, "")
r1_n3 = atom(matcher_times_n2, action_times_n2) -> (true, "Whizz") | (false, "")
r1 = allof(r1_n1, r1_n2, r1_n3)
r2 = atom(matcher_contains_n1, action_contains_n1) -> (true, "Fizz") | (false, "")
rd = atom(matcher_default, action_default) -> "num"
spec = anyof(r2, r1, rd)

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

rule: int -> string
matcher: int -> bool
action: int -> string

其中rule存在三種基本類型:

rule: atom | allof | anyof

三者之間構(gòu)成了樹型結(jié)構(gòu):

atom: (matcher, action) -> string
allof: rule1 && rule2 && ... && rulen
anyof: rule1 || rule2 || ... || rulen

領(lǐng)域模型

先看core domain的模型圖:

core-domain.png

然后是matcher domain的模型圖:

matcher-domain.png

最后是action domain的模型圖:

action-domain.png

代碼實(shí)現(xiàn)

測(cè)試用例

筆者的xUnit工具使用的是劉光聰同學(xué)的作品cut,感興趣的同學(xué)可以從github上直接下載 :)

FIXTURE(FizzBuzzWhizzSpec)
{
   Game* game;

   SETUP()
   {
       game = new Game(3, 5, 7);
   }

   TEARDOWN()
   {
       delete game;
   }

   void rule(int num, const std::string& expect)
   {
       ASSERT_THAT(game->saying(num), eq(expect));
   }

   TEST("fizz buzz whizz")
   {
       rule(3, "Fizz");
       rule(5, "Buzz");
       rule(7, "Whizz");
       rule(3 * 5, "FizzBuzz");
       rule(3 * 7, "FizzWhizz");
       rule(5 * 7 /* 35 */, "Fizz");
       rule(5 * 7 * 2, "BuzzWhizz");
       rule(3 * 5 * 7, "FizzBuzzWhizz");
       rule(13,"Fizz");
       rule(11, "11");
   }

DSL

r1_n1 = new Atom(matcher_times_n1, action_times_n1);
r1_n2 = new Atom(matcher_times_n2, action_times_n2);
r1_n3 = new Atom(matcher_times_n3, action_times_n3);
r1 = new AllOf({r1_n1, r1_n2, r1_n3});
r2 = new Atom(matcher_contains_n1, action_contains_n1);
rd = new Atom(matcher_default, action_default);
spec = new AnyOf({r2, r1, rd});

細(xì)心的讀者會(huì)發(fā)現(xiàn),DSL和語義模型一節(jié)中的形式化表達(dá)完全一致 :)

Rule

我們先看Interface:

struct Rule
{
   virtual std::string apply(int num) = 0;

   virtual ~Rule() = default;
};

Atom的實(shí)現(xiàn):

//Atom.h
struct Atom : Rule
{
   Atom(Matcher* matcher, Action* action);

   virtual std::string apply(int num) override;

private:
   Matcher* matcher;
   Action* action;
};

//Atom.cpp
Atom::Atom(Matcher* matcher, Action* action)
: matcher(matcher), action(action)
{

}

std::string Atom::apply(int num)
{
   if (matcher->match(num)) return action->exec(num);

   return "";
}

AllOf的實(shí)現(xiàn):

//AllOf.h
struct AllOf : Rule
{
   AllOf(const std::vector<Rule*>& group);

   virtual std::string apply(int num) override;

private:
   std::vector<Rule*> group;
};

//AllOf.cpp
AllOf::AllOf(const std::vector<Rule*>& group)
: group(group)
{

}

std::string AllOf::apply(int num)
{
   std::string output;
   for (Rule* p : group)
   {
       output += p->apply(num);
   }
   return output;
}

AnyOf的實(shí)現(xiàn)和AllOf類似,不同的是apply函數(shù)實(shí)現(xiàn)時(shí)會(huì)短路,不再贅述。

Mather

我們先看Interface:

struct Matcher
{
   virtual bool match(int num) = 0;

   virtual ~Matcher() = default;
};

Matcher子類的實(shí)現(xiàn)都很簡(jiǎn)單,我們以TimersMatcher為例介紹一下:

//TimesMatcher.h
struct TimesMatcher : Matcher
{
   TimesMatcher(int base);

   virtual bool match(int num) override;

private:
   int base;
};

//TimesMatcher.cpp
TimesMatcher::TimesMatcher(int base)
: base(base)
{

}

bool TimesMatcher::match(int num)
{
   return num % base == 0;
}

Action

我們先看Interface:

struct Action
{
   virtual std::string exec(int num) = 0;

   virtual ~Action() = default;
};

Action子類的實(shí)現(xiàn)都很簡(jiǎn)單,我們以DefaultAction為例介紹一下:

//DefaultAction.h
struct DefaultAction : Action
{
   virtual std::string exec(int num) override;
};

//DefaultAction.cpp
std::string DefaultAction::exec(int num)
{
   return toString(num);
}

其中toString接口由基礎(chǔ)實(shí)施提供。

小結(jié)

FizzBuzzWhizz問題本身并不復(fù)雜,本文給出了一種解決思路,即通過尋找通用語言、分析語義模型和建立領(lǐng)域模型等三個(gè)步驟來完成DDD建模,然后使用基本的C++語法對(duì)語義模型和領(lǐng)域模型進(jìn)行了實(shí)現(xiàn),其中語義模型對(duì)應(yīng)DSL層,領(lǐng)域模型對(duì)應(yīng)Domain層,希望DDD建模的步驟和代碼實(shí)現(xiàn)的思路對(duì)讀者有一定的價(jià)值。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 序言 控制復(fù)雜性是計(jì)算機(jī)編程的本質(zhì)?!?Brian Kernighan 有一次給某團(tuán)隊(duì)培訓(xùn)TDD時(shí),團(tuán)隊(duì)選擇的語...
    _張曉龍_閱讀 1,375評(píng)論 0 4
  • 形式化 FizzBuzzWhizz詳細(xì)描述請(qǐng)自行查閱相關(guān)資料。此處以3, 5, 7為例,形式化地描述一下問題。 接...
    劉光聰閱讀 642評(píng)論 0 5
  • 即使水墨丹青,何以繪出半妝佳人。 Scala是一門優(yōu)雅而又復(fù)雜的程序設(shè)計(jì)語言,初學(xué)者很容易陷入細(xì)節(jié)而迷失方向。這也...
    劉光聰閱讀 3,205評(píng)論 4 9
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,545評(píng)論 19 139
  • Supporting文件夾下面有個(gè)Info.plist文件。在里面加個(gè)字段,字段對(duì)應(yīng)的值寫上你想要app顯示的名稱...
    Song1025閱讀 5,181評(píng)論 0 1

友情鏈接更多精彩內(nèi)容