Perl 原定 E04:Moo 角色不是给对象套一层名字
Moo 的价值不只是 has。属性负责对象状态,角色负责可组合的行为契约;如果写一个空 role 再 with,只是换了语法,没有证明组合边界。课程实验把传统 bless 聚合器和 Moo 聚合器放在同一个 new/add/rows 协议下比较,避免把对象系统演示变成另一份不相干代码。
LogTool::Course::Role::LevelRows 是真实角色:level_from_record 从 hash 或拥有 level 方法的对象取级别,并拒绝非 ERROR|INFO|WARN;sorted_level_rows 把计数 hash 转成按 level 排序的 {level,count} 数组引用。LogTool::Course::MooAggregator 用 with 'LogTool::Course::Role::LevelRows' 组合角色,counts 属性保存状态,add 复用角色校验,rows 复用角色排序。TAP 断言该类 does 这个角色,并与传统 LogTool::Course::Aggregator 的输出逐项相同。
1 | |
反例是直接暴露内部散列。传统 blessed hash 允许外部代码写 $obj->{counts},这在教学上能看清 Perl 对象本质,也会形成脆弱耦合;Moo 版本通过方法协议交流,示例没有承诺内部结构。Moo 不是安全沙箱,counts 返回的引用仍可被调用者修改;本篇只验证角色组合和协议一致,不宣称不可变对象。
来源固定在 Moo 2.005005、Moo::Role、Role::Tiny 2.002004 的 POD 和 CPAN 源码包,课程安装脚本记录了每个 tarball 的 SHA-256。练习:为什么 rows 返回数组引用而不是直接打印?因为调用者可能要输出 TSV、JSON Lines 或写入 SQLite;对象层只给结构化数据,格式化留给输出层。答案可以从传统聚合器和 Moo 聚合器共用同一 rows 结果看出来。









