FizzBuzzWhizz in C++

序言

控制复杂性是计算机编程的本质。—— Brian Kernighan

前几天有幸参加了刘光聪同学组织的Code Retreat活动,收获比较大,其中印象最深刻的是结对编程实践。通过和几位高手的结对,开了眼界,他们不光设计能力超强,而且是键盘侠,两手一抹,数行高质量代码就跃然眼前,非常过瘾。
在Code Retreat活动后,笔者打算写篇文章梳理一下学到的知识,同时分享给大家。

FizzBuzzWhizz问题

FizzBuzzWhizz问题是某公司的面试题目,具体如下所示:

你是一名体育老师,在某次课距离下课还有五分钟时,你决定搞一个游戏。此时有100名学生在上课。游戏的规则是:

  1. 你首先说出三个不同的特殊数,要求必须是个位数,比如3、5、7。
  1. 让所有学生拍成一队,然后按顺序报数。
  1. 学生报数时,如果所报数字是第一个特殊数(3)的倍数,那么不能说该数字,而要说Fizz;如果所报数字是第二个特殊数(5)的倍数,那么要说Buzz;如果所报数字是第三个特殊数(7)的倍数,那么要说Whizz。
  1. 学生报数时,如果所报数字同时是两个特殊数的倍数情况下,也要特殊处理,比如第一个特殊数和第二个特殊数的倍数,那么不能说该数字,而是要说FizzBuzz, 以此类推。如果同时是三个特殊数的倍数,那么要说FizzBuzzWhizz。
  1. 学生报数时,如果所报数字包含了第一个特殊数,那么也不能说该数字,而是要说相应的单词,比如本例中第一个特殊数是3,那么要报13的同学应该说Fizz。如果数字中包含了第一个特殊数,那么忽略规则3和规则4,比如要报35的同学只报Fizz,不报BuzzWhizz。
  1. 否则,直接说出要报的数字。

DDD建模

该问题域只涉及一个BC(Bounded Context,限界上下文),我们先找UL(Ubiquitous language,通用语言)。

通用语言

  1. 题目中有三个数,我们假定为(n1, n2, n3),(3, 5, 7)是这三个数的一个例子。
  2. 原子操作记作atom,题目中有三个原子操作,分别为倍数times、包含contains和默认default。
  3. 一个数如果是ni(i=1,2,3)的倍数,我们记作times_ni,如果包含ni,我们记作contains_ni。
  4. 每个atom包含两部分,即匹配器和执行器二元组,记作(matcher, Action),那么针对三个原子操作,就有(matcher_times_ni, action_times_ni)、(matcher_contains_ni, action_contains_ni)和(matcher_default, action_default)
  5. 题目中有多个规则rule,atom是基本的rule,rule可以组合成新rule,组合可以是“与”的关系allof,也可以是“或”的关系anyof。

语义模型

我们将rule简写为r,使用UL形式化表达一下问题域:

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

三者之间构成了树型结构:

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

领域模型

先看core domain的模型图:

core-domain.png

然后是matcher domain的模型图:

matcher-domain.png

最后是action domain的模型图:

action-domain.png

代码实现

测试用例

笔者的xUnit工具使用的是刘光聪同学的作品cut,感兴趣的同学可以从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});

细心的读者会发现,DSL和语义模型一节中的形式化表达完全一致 :)

Rule

我们先看Interface:

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

   virtual ~Rule() = default;
};

Atom的实现:

//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的实现:

//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的实现和AllOf类似,不同的是apply函数实现时会短路,不再赘述。

Mather

我们先看Interface:

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

   virtual ~Matcher() = default;
};

Matcher子类的实现都很简单,我们以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子类的实现都很简单,我们以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接口由基础实施提供。

小结

FizzBuzzWhizz问题本身并不复杂,本文给出了一种解决思路,即通过寻找通用语言、分析语义模型和建立领域模型等三个步骤来完成DDD建模,然后使用基本的C++语法对语义模型和领域模型进行了实现,其中语义模型对应DSL层,领域模型对应Domain层,希望DDD建模的步骤和代码实现的思路对读者有一定的价值。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容

  • 序言 控制复杂性是计算机编程的本质。—— Brian Kernighan 有一次给某团队培训TDD时,团队选择的语...
    _张晓龙_阅读 1,225评论 0 4
  • 形式化 FizzBuzzWhizz详细描述请自行查阅相关资料。此处以3, 5, 7为例,形式化地描述一下问题。 接...
    刘光聪阅读 553评论 0 5
  • 即使水墨丹青,何以绘出半妆佳人。 Scala是一门优雅而又复杂的程序设计语言,初学者很容易陷入细节而迷失方向。这也...
    刘光聪阅读 2,978评论 4 9
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • Supporting文件夹下面有个Info.plist文件。在里面加个字段,字段对应的值写上你想要app显示的名称...
    Song1025阅读 5,107评论 0 1