形式化
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 ...
测试用例
借助C++11
增强了的「类型系统」能力,可抛弃掉很多重复的「样板代码」,使得设计更加简单、漂亮。此外,C++11
构造DSL
的能力也相当值得称赞,而且非常直接,简单。
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 });
}
此处,使用表驱动的方式构造用例集。
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
是一个「一元函数」,入参为int
,返回值为bool
,是一种典型的「谓词」。设计采用了C++11
函数式的风格,并利用强大的「闭包」能力,让代码更加简洁,并富有表达力。
从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;
};
}
执行器:Action
Action
也是一个「一元函数」,入参为int
,返回值为std::string
,其本质就是定制常见的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);
};
}
规则:Rule
Composition Everywhere
Rule
是FizzBuzzWhizz
最核心的抽象,也是设计的灵魂所在。从语义上Rule
分为2
种基本类型,并且两者之间形成了优美的、隐式的「树型」结构,体现了「组合式设计」的强大威力。
Atomic
Compositions: anyof, allof
Rule
是一个「一元函数」,入参为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);
});
};
}```