注:手機推薦橫屏觀看:-)
幼兒園老師在給一羣小朋友玩報數遊戲,遊戲規則如下:
- 老師給定任意三個特殊個位數:3,5,7
- 總共120位小朋友排成一排順序報數
需求1:
a. 所報數字是第一個特殊數的倍數(本例爲3),不能說該數字,說“石頭”;
b. 所報數字是第二個特殊數的倍數(本例爲5),不能說該數字,說“剪刀”;
c. 所報數字是第三個特殊數的倍數(本例爲7),不能說該數字,說“布”;
d. 如果所報數字同時是兩個特殊數的倍數情況下,也要特殊處理,比如是3和5的倍數,那麼不能說該數字,而是要說“石頭剪刀”, 以此類推。
e. 如果同時是三個特殊數的倍數,那麼要說“石頭剪刀布”
Sprint 1
快速瀏覽題目,從中識別出關鍵字“報數遊戲”,“特殊數:3,5,7”,“120,順序報數”。採用TDD方式,先驅動出接口。
第一個測試用例:
#include "gtest/gtest.h"
#include "CountOffGame.h"
struct GameTest : testing::Test
{
protected:
CountOffGame game;
};
TEST_F(GameTest, should_count_off_given_special_num_3_5_7)
{
ASSERT_EQ("1", game.shout(1));
}
從解決編譯問題開始,快速通過測試:
//CountOffGame.h
#include <string>
struct CountOffGame
{
std::string shout(int n) const;
};
//CountOffGame.cpp
#include "CountOffGame.h"
std::string CountOffGame::shout(int n) const
{
return "1";
}
至此,我們已經驅動出用戶接口,通過第一個用例。
接下來,繼續增加第二個用例:
注:簡單起見,本例不對測試用例進行拆分,請大家按照 F.I.R.S.T. 原則及 given-when-then方式自行編寫測試用例 :-)
TEST_F(GameTest, should_count_off_given_special_num_3_5_7)
{
ASSERT_EQ("1", game.shout(1));
ASSERT_EQ("2", game.shout(2));
}
修改實現,老老實實把數字轉換爲字符串,通過第二個用例。
//CountOffGame.cpp
#include "CountOffGame.h"
std::string CountOffGame::shout(int n) const
{
return std::to_string(n);
}
接下來我們該處理需求1-a了:
所報數字是第一個特殊數的倍數(本例爲3),不能說該數字,說“石頭”
TEST_F(GameTest, should_count_off_given_special_num_3_5_7)
{
ASSERT_EQ("1", game.shout(1));
ASSERT_EQ("2", game.shout(2));
ASSERT_EQ("石頭", game.shout(3));
}
運行測試,校驗失敗,繼續完成需求1-a。這個難不倒我們,求某個數的倍數,用%
即可。
std::string CountOffGame::shout(int n) const
{
if(n % 3 == 0) return "石頭";
return std::to_string(n);
}
繼續完成需求1-b:
TEST_F(GameTest, should_count_off_given_special_num_3_5_7)
{
ASSERT_EQ("1", game.shout(1));
ASSERT_EQ("2", game.shout(2));
ASSERT_EQ("石頭", game.shout(3));
ASSERT_EQ("剪刀", game.shout(5));
}
std::string CountOffGame::shout(int n) const
{
if(n % 3 == 0) return "石頭";
if(n % 5 == 0) return "剪刀";
return std::to_string(n);
}
至此,我們已經完成了需求1-a,需求1-b,但敏銳的你一定發現了不少壞味道:
- 業務代碼中需求1-a,需求1-b的實現存在明顯重複
- 測試用例中,
game.shout(...)
存在明顯重複 - 用戶API
shout()
命名也不太合理
我們先忍忍,繼續完成需求1-c,因爲它並不會帶來新的變化方向
TEST_F(GameTest, should_count_off_given_special_num_3_5_7)
{
ASSERT_EQ("1", game.shout(1));
ASSERT_EQ("2", game.shout(2));
ASSERT_EQ("石頭", game.shout(3));
ASSERT_EQ("剪刀", game.shout(5));
ASSERT_EQ("布", game.shout(7));
}
std::string CountOffGame::shout(int n) const
{
if(n % 3 == 0) return "石頭";
if(n % 5 == 0) return "剪刀";
if(n % 7 == 0) return "布";
return std::to_string(n);
}
至此,我們已經完成需求1-a,b,c,接下來要開啓Refactor
模式了
- 重命名
shout(int n)
接口
//CountOffGame.h
struct CountOffGame
{
std::string countOff(int n) const;
};
//CountOffGame.cpp
std::string CountOffGame::countOff(int n) const
{
...
}
- 消除測試用例接口重複
struct GameTest : testing::Test
{
void ASSERT_COUNTOFF_GAME(int n, const std::string& words)
{
ASSERT_EQ(words, game.countOff(n));
}
protected:
CountOffGame game;
};
TEST_F(GameTest, should_count_off_given_special_num_3_5_7)
{
ASSERT_COUNTOFF_GAME(1, "1");
ASSERT_COUNTOFF_GAME(2, "2");
ASSERT_COUNTOFF_GAME(3, "石頭");
ASSERT_COUNTOFF_GAME(5, "剪刀");
ASSERT_COUNTOFF_GAME(7, "布");
}
- 消除業務代碼中重複,提煉出倍數概念
namespace
{
bool times(int n, int times)
{
return n % times == 0;
}
}
std::string CountOffGame::countOff(int n) const
{
if(times(n, 3)) return "石頭";
if(times(n, 5)) return "剪刀";
if(times(n, 7)) return "布";
return std::to_string(n);
}
- 消除報數“石頭,剪刀,布”硬編碼
namespace
{
...
std::string shout(int n, const std::string& words = "")
{
if(!words.empty()) return words;
return std::to_string(n);
}
}
std::string CountOffGame::countOff(int n) const
{
if(times(n, 3)) return shout(n, "石頭");
if(times(n, 5)) return shout(n, "剪刀");
if(times(n, 7)) return shout(n, "布");
return shout(n);
}
至此,我們已經消除了明顯的重複,但是依然存在結構性重複。即每個語句都是報數時遵循的一個規則,並且都是if(謂詞) return 動作
結構。
我們對其進行抽象,進一步分析發現滿足如下形式化定義:
Rule: (int) -> string
Predicate: (int) -> bool
Action: (int) -> string
我們從武器庫中快速搜尋着各種解決方案,比較着他們的利弊。
- 抽象出
Predicate
與Action
接口類,分別提供bool isMatch(int n) const
與std::string do(int n) const
虛方法;抽象出Rule
類,注入Predicate
與Action
接口 - 定義類模板
Rule
,綁定謂詞與動作,要求謂詞滿足isMatch(int n)
,動作滿足do(int n)
約束 - 定義兩個函數指針,用於抽象約束關係,使用函數模板
rule
將其綁定
綜合考慮後,發現方案3在簡單性和擴展性方面是最合適的
//CountOffGame.cpp
namespace
{
...
typedef bool (*Predicate)(int, int);
typedef std::string (*Action)(int, const std::string& );
template<Predicate isMatch, Action do>
std::string rule(int n, int times, const std::string& words)
{
if(isMatch(n, times))
{
return do(n, words);
}
return std::string("");
}
}
std::string CountOffGame::countOff(int n) const
{
const std::string& r1_1 = rule<times, shout>(n, 3, "石頭");
if( ! r1_1.empty()) return r1_1;
const std::string& r1_2 = rule<times, shout>(n, 5, "剪刀");
if( ! r1_2.empty()) return r1_2;
const std::string& r1_3 = rule<times, shout>(n, 7, "布");
if( ! r1_3.empty()) return r1_3;
return shout(n);
}
舊仇剛報,又添新恨,此時又出現了新的結構性重複,抽象一下就是:報數規則有一個滿足,就執行該規則並返回,否則繼續執行下一個規則,語義爲爲anyof(...)
。我們暫且忍受一下,讓子彈飛一會,繼續完成下面需求。
增加需求1-d,需求1-e測試用例:
TEST_F(GameTest, should_count_off_given_special_num_3_5_7)
{
ASSERT_COUNTOFF_GAME(1, "1");
ASSERT_COUNTOFF_GAME(2, "2");
ASSERT_COUNTOFF_GAME(3, "石頭");
ASSERT_COUNTOFF_GAME(5, "剪刀");
ASSERT_COUNTOFF_GAME(7, "布");
ASSERT_COUNTOFF_GAME(15, "石頭剪刀");
ASSERT_COUNTOFF_GAME(21, "石頭布");
ASSERT_COUNTOFF_GAME(35, "剪刀布");
ASSERT_COUNTOFF_GAME(105, "石頭剪刀布");
}
我們仔細分析發現需求1-d,需求1-e是對上面需求的組合,即如果規則滿足則執行該規則,完成後繼續執行下一個規則,若不滿足則直接執行下一個規則。語義爲allof(...)。本例中表現爲把所有規則結果串起來。
std::string CountOffGame::countOff(int n) const
{
const std::string& r1 = rule<times, shout>(n, 3, "石頭") ;
+ rule<times, shout>(n, 5, "剪刀");
+ rule<times, shout>(n, 7, "布");
if( ! r1.empty()) return r1;
return shout(n);
}
至此,我們全部完成了需求1所有規則,由於需求1-d,e比需求1-a,b,c優先級高,allof()隱式滿足了anyof()需求,所以結構性重複暫時不存在了。
Sprint 2
需求 2:
a. 如果所報數字中包含了第一個特殊數,那麼也不能說該數字,而是要說相應的詞,比如本例中第一個特殊數是3,那麼要報13的同學應該說“石頭”。
b. 如果所報數字中包含了第一個特殊數,那麼忽略規則1,比如要報35的同學只報“石頭”,不報“剪刀布”
增加需求2測試用例:
TEST_F(GameTest, should_count_off_given_special_num_3_5_7)
{
...
ASSERT_COUNTOFF_GAME(13, "石頭");
ASSERT_COUNTOFF_GAME(35, "石頭");
}
有了需求1的抽象,需求2就比較簡單了,僅需要增加一個謂詞即可
namespace
{
...
bool contains(int n, int contained)
{
int units = 0;
while(n > 0)
{
units = n % 10;
n = n /10;
if(units == contained) return true;
}
return false;
}
...
}
std::string CountOffGame::countOff(int n) const
{
const std::string& r2 = rule<contains, shout>(n, 3, "石頭");
if(!r2.empty()) return r2;
const std::string& r1 = rule<times, shout>(n, 3, "石頭");
+ rule<times, shout>(n, 5, "剪刀");
+ rule<times, shout>(n, 7, "布");
if( ! r1.empty()) return r1;
return shout(n);
}
return shout(n);
是什麼鬼,我們進一步分析,將它抽象成一個規則
namespace
{
...
bool always_true(int,int)
{
return true;
}
std::string shout(int, const std::string& words)
{
return words;
}
std::string nop(int n, const std::string&)
{
return std::to_string(n);
}
...
}
std::string CountOffGame::countOff(int n) const
{
const std::string& r2 = rule<contains, shout>(n, 3, "石頭");
if(!r2.empty()) return r2;
const std::string& r1 = rule<times, shout>(n, 3, "石頭");
+ rule<times, shout>(n, 5, "剪刀");
+ rule<times, shout>(n, 7, "布");
if( ! r1.empty()) return r1;
return rule<always_true, nop>(n, 0, "");
}
但是我們討厭的結構性重複又來了,這不就是前面抽象的anyof(...)
麼,看來不得不收拾他們了。
解決這個問題之前,我們先把物理耦合處理下,CountOffGame.cpp
文件的職責已經太多了
//Predicate.h
bool times(int n, int times);
bool contains(int n, int contained);
bool always_true(int,int);
//Predicate.cpp
#include "Predicate.h"
bool times(int n, int times)
{
return n % times == 0;
}
bool contains(int n, int contained)
{
int units = 0;
while(n > 0)
{
units = n % 10;
n = n /10;
if(units == contained) return true;
}
return false;
}
bool always_true(int,int)
{
return true;
}
//Action.h
#include <string>
std::string shout(int n, const std::string& words);
std::string nop(int n, const std::string&);
//Action.cpp
#include "Action.h"
std::string shout(int, const std::string& words)
{
return words;
}
std::string nop(int n, const std::string&)
{
return std::to_string(n);
}
//Rule.h
#include <string>
typedef bool (*Predicate)(int, int);
typedef std::string (*Action)(int, const std::string& );
template<Predicate isMatch, Action do>
std::string rule(int n, int times, const std::string& words)
{
if(isMatch(n, times))
{
return do(n, words);
}
return std::string("");
}
CountOffGame.cpp
文件中僅剩下組合遊戲規則,完成遊戲了
std::string CountOffGame::countOff(int n) const
{
const std::string& r2 = rule<contains, shout>(n, 3, "石頭");
if(!r2.empty()) return r2;
const std::string& r1 = rule<times, shout>(n, 3, "石頭");
+ rule<times, shout>(n, 5, "剪刀");
+ rule<times, shout>(n, 7, "布");
if( ! r1.empty()) return r1;
return shout(n);
}
注:有同學不知道CountOffGame類與Rule的區別,這裏解釋一下,規則僅完成每個規則自己的形式化與定義,遊戲中對規則進行組合,完成遊戲
完成文件物理解耦後,世界一下子清靜多了,需要特別指出的是,在使用c語言進行編程時,由於沒有類的模塊化手段,在解耦方面,文件是留給我們的唯一利器了。
我們回過頭來,再看看前面抽象的anyof(...),allof(...)
概念,形式化一下可以表示爲:
Rule: (int) -> string
allof: rule1 && rule2 ...
anyof: rule1 || rule2 ...
這顯然是規則的組合關係管理,我們又陷入了深深的思索,使用什麼方法解決這個問題呢?
繼續搜索武器庫:
- 使用面向對象方法,抽象出
rule
對應接口,使用組合模式解決該問題 - 使用類模板,將不同
rule
通過模板參數注入,需要使用變參模板 - 使用函數模板,將不同
rule
通過模板參數注入,需要使用變參函數模板 - 使用函數式編程,可以使用
std::function
定義每個rule
綜合比較以上方案,
- 面向對象設計,組合模式中集合類要求每個成員
rule
不可以異構,並且僅能存放指向rule
的指針,即要求每個rule
都是一個對象。我們就需要管理每個對象的生命週期,需要使用shared_ptr
,或者使用c++11
std::move
語義,延長臨時對象生命週期。越想越複雜,打住! - 模板元編程,需要使用
c++11
變參模板,或者使用repeate宏了(參看boost庫), - 函數模板,需要使用
c++11
變參函數模板,使用尾遞歸完成組合,直接注入規則即可 - 函數式編程,形式化表示規則,天生具有優勢,又加之函數的無狀態,組合起來很方便,可以結合右值引用及
std::move
完成,考慮熟悉同學較少,暫不考慮
綜合分析後,我們選方案3
//Rule.h
...
template<typename Head>
Head allof(Head head)
{
return head;
}
template<typename Head, typename... Tail>
Head allof(Head head, Tail... tail)
{
return head + allof<Head>(tail...);
}
template<typename Head>
Head anyof(Head head)
{
if(!head.empty()) return head;
return std::string("");
}
template<typename Head, typename... Tail>
Head anyof(Head head, Tail... tail)
{
if(!head.empty()) return head;
return anyof<Head>(tail...);
}
//CountOffGame.cpp
#include "CountOffGame.h"
#include "Rule.h"
#include "Predicate.h"
#include "Action.h"
std::string CountOffGame::countOff(int n) const
{
auto r1_1 = rule<times, shout>(n, 3, "石頭");
auto r1_2 = rule<times, shout>(n, 5, "剪刀");
auto r1_3 = rule<times, shout>(n, 7, "布");
auto r1 = allof(r1_1, r1_2, r1_3);
auto r2 = rule<contains, shout>(n, 3, "石頭");
auto rd = rule<always_true, nop>(n, 0, "");
return anyof(r2, r1, rd);
}
現在我們徹底解決了規則管理問題,你可以任意調整優先級了,也可以任意忽略規則了。在整個設計過程中,我們發現重複總是我們識別壞味道最好的引子。當變化方向來的時候,也就是我們被第二顆子彈擊中的時候(好吧,有時候是第三顆子彈),我們需要把該方向的所有問題解決,而不是僅解決該問題。
注: 坦白講,這裏的
CountOffGame
類使用的完全沒有必要,你完全可以使用一個簡單的const char* count_off(int n)
函數代替,再把std::string
使用char*
取代,這樣就完全是一份c的代碼了:-)
Sprint 3
需求 3:
a. 第二天,幼兒園換了新老師,新老師對遊戲進行了修改
三個特殊的個位數變更爲5,7,9。規則1,規則2依然有效,例如:
遇到 5 的倍數說“石頭”,7的倍數說“剪刀”,9的倍數說“布”;
遇到 63 說“剪刀布”
遇到 53 說“石頭”,遇到35說“石頭”
b. 需求1,2 測試用例斷言不允許修改,僅允許修改前置條件
需求3已經說的很明顯了,三個特殊數可以發生變化,並且需要通過遊戲注入,而不需要修改遊戲規則
簡單重構測試用例斷言,增加需求3用例
#include "gtest/gtest.h"
#include "CountOffGame.h"
struct GameTest : testing::Test
{
GameTest() : game_sprint_1_2(3, 5, 7)
, game_sprint_3(5, 7, 9)
{
}
void ASSERT_COUNTOFF_3_5_7(int n, const std::string& words)
{
ASSERT_EQ(words, game_sprint_1_2.countOff(n));
}
void ASSERT_COUNTOFF_5_7_9(int n, const std::string& words)
{
ASSERT_EQ(words, game_sprint_3.countOff(n));
}
protected:
CountOffGame game_sprint_1_2;
CountOffGame game_sprint_3;
};
TEST_F(GameTest, should_count_off_given_special_num_3_5_7)
{
ASSERT_COUNTOFF_3_5_7(1, "1");
ASSERT_COUNTOFF_3_5_7(2, "2");
ASSERT_COUNTOFF_3_5_7(3, "石頭");
ASSERT_COUNTOFF_3_5_7(5, "剪刀");
ASSERT_COUNTOFF_3_5_7(7, "布");
ASSERT_COUNTOFF_3_5_7(15, "石頭剪刀");
ASSERT_COUNTOFF_3_5_7(21, "石頭布");
ASSERT_COUNTOFF_3_5_7(105, "石頭剪刀布");
ASSERT_COUNTOFF_3_5_7(13, "石頭");
ASSERT_COUNTOFF_3_5_7(35, "石頭");
}
TEST_F(GameTest, should_count_off_given_special_num_5_7_9)
{
ASSERT_COUNTOFF_5_7_9(1, "1");
ASSERT_COUNTOFF_5_7_9(2, "2");
ASSERT_COUNTOFF_5_7_9(5, "石頭");
ASSERT_COUNTOFF_5_7_9(7, "剪刀");
ASSERT_COUNTOFF_5_7_9(9, "布");
ASSERT_COUNTOFF_5_7_9(63, "剪刀布");
ASSERT_COUNTOFF_5_7_9(15, "石頭");
ASSERT_COUNTOFF_5_7_9(35, "石頭");
}
快速完成需求3,僅是規則中Predicate匹配內容發生了變化,修改CountOffGame即可
//CountOffGame.h
#include <string>
struct CountOffGame
{
CountOffGame(int n1, int n2, int n3);
std::string countOff(int n) const;
private:
int n1;
int n2;
int n3;
};
//CountOffGame.cpp
CountOffGame::CountOffGame(int n1, int n2, int n3) : n1(n1), n2(n2), n3(n3)
{
}
std::string CountOffGame::countOff(int n) const
{
auto r1_1 = rule<times, shout>(n, n1, "石頭");
auto r1_2 = rule<times, shout>(n, n2, "剪刀");
auto r1_3 = rule<times, shout>(n, n3, "布");
auto r1 = allof(r1_1, r1_2, r1_3);
auto r2 = rule<contains, shout>(n, n1, "石頭");
auto rd = rule<always_true, nop>(n, 0, "");
return anyof(r2, r1, rd);
}
繼續...
Sprint 4
需求 4:
第三天,原來老師又回來了,對遊戲又做了如下修改
a. 老師又把三個特殊的個位數變回爲3,5,7
b. 規則1與規則2中“石頭、剪刀、布”變更爲 “老虎,棒子,雞”。
c. 打印120位小朋友報數的結果到文件(count_off.txt),並提交到 document 文件夾下。可以不處理IO,打印出來拷貝到 count_off.txt
這壓根沒引入什麼新的變化方向,僅是規則中Action的內容發生了變化,修改CountOffGame即可,我們愛死這個老師了!
增加需求4測試用例:
struct GameTest : testing::Test
{
GameTest() : game_sprint_1_2(3, 5, 7, "石頭", "剪刀", "布")
, game_sprint_3(5, 7, 9, "石頭", "剪刀", "布")
, game_sprint_4(3, 5, 7, "老虎", "棒子", "雞")
{
}
void ASSERT_COUNTOFF_3_5_7(int n, const std::string& words)
{
ASSERT_COUNTOFF(game_sprint_1_2, n, words);
}
void ASSERT_COUNTOFF_5_7_9(int n, const std::string& words)
{
ASSERT_COUNTOFF(game_sprint_3, n, words);
}
void ASSERT_COUNTOFF_3_5_7_EX(int n, const std::string& words)
{
ASSERT_COUNTOFF(game_sprint_4, n, words);
}
private:
void ASSERT_COUNTOFF(const CountOffGame& game, int n, const std::string& words)
{
ASSERT_EQ(words, game.countOff(n));
}
protected:
CountOffGame game_sprint_1_2;
CountOffGame game_sprint_3;
CountOffGame game_sprint_4;
};
TEST_F(GameTest, should_count_off_given_special_num_3_5_7)
{
ASSERT_COUNTOFF_3_5_7(1, "1");
ASSERT_COUNTOFF_3_5_7(2, "2");
ASSERT_COUNTOFF_3_5_7(3, "石頭");
ASSERT_COUNTOFF_3_5_7(5, "剪刀");
ASSERT_COUNTOFF_3_5_7(7, "布");
ASSERT_COUNTOFF_3_5_7(15, "石頭剪刀");
ASSERT_COUNTOFF_3_5_7(21, "石頭布");
ASSERT_COUNTOFF_3_5_7(105, "石頭剪刀布");
ASSERT_COUNTOFF_3_5_7(13, "石頭");
ASSERT_COUNTOFF_3_5_7(35, "石頭");
}
TEST_F(GameTest, should_count_off_given_special_num_5_7_9)
{
ASSERT_COUNTOFF_5_7_9(1, "1");
ASSERT_COUNTOFF_5_7_9(2, "2");
ASSERT_COUNTOFF_5_7_9(5, "石頭");
ASSERT_COUNTOFF_5_7_9(7, "剪刀");
ASSERT_COUNTOFF_5_7_9(9, "布");
ASSERT_COUNTOFF_5_7_9(63, "剪刀布");
ASSERT_COUNTOFF_5_7_9(15, "石頭");
ASSERT_COUNTOFF_5_7_9(35, "石頭");
}
TEST_F(GameTest, should_count_off_given_special_num_3_5_7_countof_other)
{
ASSERT_COUNTOFF_3_5_7_EX(1, "1");
ASSERT_COUNTOFF_3_5_7_EX(2, "2");
ASSERT_COUNTOFF_3_5_7_EX(3, "老虎");
ASSERT_COUNTOFF_3_5_7_EX(5, "棒子");
ASSERT_COUNTOFF_3_5_7_EX(7, "雞");
ASSERT_COUNTOFF_3_5_7_EX(15, "老虎棒子");
ASSERT_COUNTOFF_3_5_7_EX(21, "老虎雞");
ASSERT_COUNTOFF_3_5_7_EX(105, "老虎棒子雞");
ASSERT_COUNTOFF_3_5_7_EX(13, "老虎");
ASSERT_COUNTOFF_3_5_7_EX(35, "老虎");
}
快速實現需求
//CountOffGame.h
#include <string>
struct CountOffGame
{
CountOffGame(int n1, int n2, int n3, const std::string&, const std::string&, const std::string&);
std::string countOff(int n) const;
private:
int n1;
int n2;
int n3;
const std::string w1;
const std::string w2;
const std::string w3;
};
//CountOffGame.cpp
CountOffGame::CountOffGame(int n1, int n2, int n3, const std::string& w1, const std::string& w2, const std::string& w3)
: n1(n1), n2(n2), n3(n3), w1(w1), w2(w2), w3(w3)
{
}
std::string CountOffGame::countOff(int n) const
{
auto r1_1 = rule<times, shout>(n, n1, w1);
auto r1_2 = rule<times, shout>(n, n2, w2);
auto r1_3 = rule<times, shout>(n, n3, w3);
auto r1 = allof(r1_1, r1_2, r1_3);
auto r2 = rule<contains, shout>(n, n1, w1);
auto rd = rule<always_true, nop>(n, 0, "");
return anyof(r2, r1, rd);
}
雖然實現了需求,但這一長串初始化參數,搞的人都快吐了,
仔細分析一下,報數遊戲中規則條件及動作結果是一個一一映射關係,我們可以定義一個RuleMap
進行化簡
//CountOffGame.h
#include <string>
#include <initializer_list>
#include <vector>
struct RuleMap
{
int n;
std::string words;
};
struct CountOffGame
{
CountOffGame(const std::initializer_list<RuleMap>&);
std::string countOff(int n) const;
private:
std::vector<RuleMap> rules;
};
//CountOffGame.cpp
#include "CountOffGame.h"
#include "Rule.h"
#include "Predicate.h"
#include "Action.h"
CountOffGame::CountOffGame(const std::initializer_list<RuleMap>& rules) : rules(rules)
{
}
std::string CountOffGame::countOff(int n) const
{
auto r1_1 = rule<times, shout>(n, rules[0].n, rules[0].words);
auto r1_2 = rule<times, shout>(n, rules[1].n, rules[1].words);
auto r1_3 = rule<times, shout>(n, rules[2].n, rules[2].words);
auto r1 = allof(r1_1, r1_2, r1_3);
auto r2 = rule<contains, shout>(n, rules[0].n, rules[0].words);
auto rd = rule<always_true, nop>(n, 0, "");
return anyof(r2, r1, rd);
}
每個規則中rules[...]
又出現了重複,繼續消除
//rule.h
#include <string>
struct RuleMap
{
int n;
std::string words;
};
typedef bool (*Predicate)(int, int);
typedef std::string (*Action)(int, const std::string& );
template<Predicate isMatch, Action do>
std::string rule(int n, const RuleMap& map = {})
{
if(isMatch(n, map.n))
{
return do(n, map.words);
}
return std::string("");
}
...
//CountOffGame.cpp
...
std::string CountOffGame::countOff(int n) const
{
auto r1_1 = rule<times, shout>(n, rules[0]);
auto r1_2 = rule<times, shout>(n, rules[1]);
auto r1_3 = rule<times, shout>(n, rules[2]);
auto r1 = allof(r1_1, r1_2, r1_3);
auto r2 = rule<contains, shout>(n, rules[0]);
auto rd = rule<always_true, nop>(n);
return anyof(r2, r1, rd);
}
代碼地址:https://github.com/liyongshun/count-off
[email protected] @ November 26, 2017 11:31 PM