Rust【策略·设计模式】Strategy / Policy design pattern【Rust - Strategy / Policy策略·模式】与【OOP - Dependency Inversion依赖倒置·模式】和【Javascript - Callback Functon回调函数·模式】皆同属一类设计模式组合Inversion of Control + Dependency Injection(控制反转 + 依赖注入)。为了描述简洁,后文将该组合记作:IoC + DI。
先上图(一图抵千词)

就着上图,我再进一步展开论述。
IoC容器在IoC容器内定义
workflow。一般IoC容器会对外导出一个pub函数来
DI依赖注入利用DI从“业务总线”上扣出可·填入·自定义实现细节的“trait坑位” — 非具体类型,避免IoC容器和单一类型“捆绑”。
作为“坑位”,有两个特质不能少:
struct实例才被允许注入IoC容器内的“坑位”里。impl TraitBox<dyn Trait> — 允许将【依赖项·构造】业务逻辑抽象至一个独立的【构造函数】内。&dyn Trait — 【依赖项·构造】代码必须与【依赖·注入】程序处于同一个函数内,而不能再被抽离·封装于一个独立【构造函数】了。因为没有【所有权·智能指针】保持所有权“不灭”,所以【胖指针】背后的实际变量值会随着【构造函数】的结束执行而被释放掉 — 这会给【构造函数】调用端造成【野指针】困扰,借入检查器是不会答应的。若不明白的话,你再体会,体会!rust中,由trait书面定义“填充·标准”。而且,因为rust区分【编译时·抽象】与【运行时·抽象】,所以“坑位·规格”又进一步分为:OOP中,由interface书面约定“填充·标准”。js是弱类型的,所以不需要“书面的”坑位规格描述,开发者把【回调函数】约定记在心里或写到代码注释里即好。trait具体·实现类·实例 — 瘦指针。编译器会自动将【泛型·类型·参数】的【具体·类型】实参展开 — 这叫单态化。trait Object — 胖指针。而trait Object实例是被保存在【栈】上,还是被存储于【堆】内,并不重要。rust中,还是区分【编译时·抽象】与【运行时·抽象】两种情况OOP中,就是实现了interface的class实例。js中,就是满足了(你在代码注释里备注的)函数签名约定的回调函数。trait坑位就IoC容器而言,仅有trait定义里的
是可见的。另外,因为rust允许为trait method提供默认实现,所以trait坑位也能为自己提供缺省实现项,若调用端·程序员没有注入定制解决方案的话。
trait坑位·填充物首先,在Rust语境中,该“填充物”有一个专属名词叫作Strategy Structs。
其次,【闭包Closure】与【函数指针fn】被允许经由DI接口·注入至IoC容器内·不是什么语言“特例”,而是仅只因为【闭包Closure】与【函数指针fn】本质上就是实现了Fn / FnMut / FnOnce trait的struct实例。至于它们在字面量上不像struct,那是因为语法糖:
struct类型,并将被捕获变量作为该struct类型的(私有)字段。此外,因为每个【闭包】的上下文环境与捕获变量都是不同的,所以每个【闭包】也都有专属的、一个独一无二的匿名struct类型和不同的私有字段。而在【闭包】体内定义的业务代码则会被封装于【闭包】struct的Fn::call(&self, args: Args) -> FnOnce::Output成员方法里。fn】而言,fn自身就是一个无字段的Fn trait实现类。于是,因为fn类型没有字段,所以【函数】也就不能捕获任何的外部变量。编译器真的为我们做了许多的事情。
最后,凭借trait实现类的(私有)字段,还能实现
的功能。
IoC + DI在rust的技术落地相对于弱类型的js,强类型的rust
trait method,约定“回调函数”的函数签名 — js没有类型,也就不需要书面地声明(回调)函数签名IoC容器透明的方式被封装于此回调函数里。trait实现类的(私有)字段,从IoC容器外捕获变量 — js函数的天赋技能之一就是【捕获变量】,所以不用显示地写这类代码。DI接口注入就不只是功能“行为”,还有(独立于输入数据的)额外状态信息。相对于玩转【堆】的java,rust还允许向IoC容器注入复杂数据类型的【栈】变量值,而无论该变量值是被【静态分派】还是【动态分派】。
于是,我的总结是在rust里的IoC + DI的设计模式落地·比js严谨,比java灵活。
该【例程】实现的功能是:
该【例程】代码分成三个子模块。它们分别对应IoC + DI设计模式内的三大构件:
IoC容器mod ioc_container和ioc_container::Report类型。并且,在ioc_container::Report::generate()关联函数内定义了ioc_container::Report::sign_me()给【报表】生成【数字签名】。DI注入标准(也称trait坑位规格)mod di_spec。只有满足了该规格要求的struct实例或closure才能被注入到IoC容器内。在本例中,包括:di_spec::Ingredient— 这是一个被动态分派的【闭包】签名。di_spec::Formatter — 这是一个待实现的traitDI依赖项(也称trait坑位·填充物)mod di_stuff。在本例中,包括:di_spec::Ingredient定义的函数签名。fn data_builder()。di_stuff::TextJSON格式化【源数据】的代码di_stuff::Json最后,在main函数内,依次
DI依赖项DI依赖项注入IoC容器 — 就是给ioc_container::Report::generate()关联函数传参。Report结构体实例。其包括了【条件编译】plus【策略·设计模式】是一套非常棒的多平台适配方案。即,
trait坑位。trait坑位准备多套·适配不同(交叉编译)目标平台的·Strategy Structs具体实现。rustc --cfg或cargo --features命令行参数,(利用#[cfg(...)]元属性)将恰当的Strategy Struct(依赖)注入到·封装了核心业务IoC容器里的trait坑位内。exe或dll文件。哎呀!怎么越讲,越像serde crate了。但是,这么设计真是很【优雅】!
经由【回调函数】将·可定制技术细节·甩出【主函数】是一条比较常见的编程套路。可是,一旦给“土·方子”赋上一个fancy name,好似一切都变得好高端、好抽象、好难理解!所以,我个人提议:将Rust - Strategy设计模式重命名为更接地气的和土得掉渣的名字“回调函数·模式”。