形式化
FizzBuzzWhizz
詳細(xì)描述請(qǐng)自行查閱相關(guān)資料。此處以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
問題的設(shè)計(jì)和實(shí)現(xiàn)。
語義模型
從上面的形式化描述,可以很容易地得到FizzBuzzWhizz
問題的語義模型。
Rule: (Int) -> String
Matcher: (Int) -> Boolean
Action: (Int) -> String
其中,Rule
存在三種基本的類型:
Rule ::= atom | allof | anyof
三者之間構(gòu)成了「樹型」結(jié)構(gòu)。
atom: (Matcher, Action) -> String
allof: rule1 && rule2 ...
anyof: rule1 || rule2 ...
測(cè)試用例
借助C++11
增強(qiáng)了的「類型系統(tǒng)」能力,可拋棄掉很多重復(fù)的「樣板代碼」,使得設(shè)計(jì)更加簡(jiǎn)單、漂亮。此外,C++11
構(gòu)造DSL
的能力也相當(dāng)值得稱贊,而且非常直接,簡(jiǎn)單。
Rule spec(int n1, int n2, int n3) {
auto r_n1 = atom(times(n1), to("Fizz"));
auto r_n2 = atom(times(n2), to("Buzz"));
auto r_n3 = atom(times(n3), to("Whizz"));
auto r2 = allof( { r_n1, r_n2, r_n3 });
auto r3 = atom(contains(n1), to("Fizz"));
auto rd = atom(always(true), nop());
return anyof( { r3, r2, rd });
}
此處,使用表驅(qū)動(dòng)的方式構(gòu)造用例集。
TEST("fizz buzz whizz") {
rule(3, "Fizz");
rule(5, "Buzz");
rule(7, "Whizz");
rule(3 * 5 * 7, "FizzBuzzWhizz");
rule(3 * 5, "FizzBuzz");
rule(3 * 7, "FizzWhizz");
rule((5 * 7) * 2, "BuzzWhizz");
rule(13, "Fizz");
rule(35 /* 5*7 */, "Fizz");
rule(2, "2");
}
void rule(int n, const std::string& expect) {
ASSERT_THAT(spec(n), eq(expect));
}
};
匹配器:Matcher
Matcher
是一個(gè)「一元函數(shù)」,入?yún)?code>int,返回值為bool
,是一種典型的「謂詞」。設(shè)計(jì)采用了C++11
函數(shù)式的風(fēng)格,并利用強(qiáng)大的「閉包」能力,讓代碼更加簡(jiǎn)潔,并富有表達(dá)力。
從OO
的角度看,always
是一種典型的Null Object
。
using Matcher = std::function<bool(int)>;
Matcher times(int times) {
return [=](auto n) {
return n % times == 0;
};
}
Matcher contains(int num) {
return [=](auto n) {
return cui::toString(n).find(cui::toString(num)) != std::string::npos;
};
}
Matcher always(bool value) {
return [=](auto) {
return value;
};
}
執(zhí)行器:Action
Action
也是一個(gè)「一元函數(shù)」,入?yún)?code>int,返回值為std::string
,其本質(zhì)就是定制常見的map
操作,將定義域映射到值域。
using Action = std::function<std::string(int)>;
Action to(std::string&& str) {
return [str = std::move(str)](auto) {
return str;
};
}
Action nop() {
return [](auto n) {
return cui::toString(n);
};
}
規(guī)則:Rule
Composition Everywhere
Rule
是FizzBuzzWhizz
最核心的抽象,也是設(shè)計(jì)的靈魂所在。從語義上Rule
分為2
種基本類型,并且兩者之間形成了優(yōu)美的、隱式的「樹型」結(jié)構(gòu),體現(xiàn)了「組合式設(shè)計(jì)」的強(qiáng)大威力。
Atomic
Compositions: anyof, allof
Rule
是一個(gè)「一元函數(shù)」,入?yún)?code>int,返回值為std::string
。
using Rule = std::function<std::string(int)>;
Rule atom(Matcher&& matcher, Action&& action) {
return [ matcher = std::move(matcher)
, action = std::move(action)](auto n) {
return matcher(n) ? action(n) : "";
};
}
Rule anyof(std::vector<Rule>&& rules) {
return [rules = std::move(rules)](auto n) {
auto found = std::find_if(rules.cbegin(), rules.cend(),
[n](const auto& r) { return !r(n).empty(); });
return found != std::cend(rules) ? (*found)(n) : "";
};
}
Rule allof(std::vector<Rule>&& rules) {
return [rules = std::move(rules)](auto n) {
return std::accumulate(rules.cbegin(), rules.cend(), std::string(""),
[n](const auto& acc, const auto& r) {
return acc + r(n);
});
};
}```