4 个版本
使用旧的 Rust 2015
0.2.1 | 2017年6月29日 |
---|---|
0.2.0 | 2017年6月24日 |
0.1.1 | 2017年6月20日 |
0.1.0 | 2017年6月20日 |
#7 in #inversion
用于 he_di_derive
63KB
631 代码行
什么是依赖注入(又称依赖倒置)?
控制反转的核心理念是,不是将您的应用程序中的类绑定在一起并让类“new up”它们的依赖项,而是将其颠倒过来,以便在类构造期间传递依赖项。它是 SOLID 编程的 5 个核心原则之一。
如果您想了解更多关于这个主题的内容
- [Martin Fowler 有一个关于依赖注入/控制反转的精彩文章](https://martinfowler.com.cn/articles/injection.html)
- [关于依赖倒置原则的维基百科文章](https://en.wikipedia.org/wiki/Dependency_inversion_principle)
入门
组织您的应用程序
首先,编写一个使用 struct & 类型(作为对 [AutoFac](https://autofac.org/) 的致敬,我移植了他们的经典 "入门" 示例)的经典应用程序。下面使用了代码片段来说明这个小指南,完整的示例可在这里找到。
trait IOutput {
fn write(&self, content: String);
}
struct ConsoleOutput {
prefix: String,
other_param: usize,
}
impl IOutput for ConsoleOutput {
fn write(&self, content: String) {
println!("{} #{} {}", self.prefix, self.other_param, content);
}
}
trait IDateWriter {
fn write_date(&self);
}
struct TodayWriter {
output: Box<IOutput>,
today: String,
year: String,
}
impl IDateWriter for TodayWriter {
fn write_date(&self) {
let mut content = "Today is ".to_string();
content.push_str(self.today.as_str());
content.push_str(" ");
content.push_str(self.year.to_string().as_str());
self.output.write(content);
}
}
标记结构体为组件
组件是一个表达式或其他代码片段,它公开一个或多个服务并可以接受其他依赖项。
在我们的示例中,我们有 2 个组件
TodayWriter
类型为IDateWriter
ConsoleOutput
类型为IOutput
为了能够将它们识别为组件,he_di 提供了一个 #[derive)]
宏(尽管使用了 he_di_derive 包)。这只需使用以下属性即可完成。
#[derive(Component)] // <--- mark as a Component
#[interface(IOutput)] // <--- specify the type of this Component
struct ConsoleOutput {
prefix: String,
other_param: usize,
}
在当前版本中,您还需要使用 #[interface)]
属性指定您的组件类型。
表达依赖项
某些组件可以依赖其他组件,这使得 DI 逻辑也可以将这些组件注入另一个组件。
在我们的示例中,ConsoleOuput
是一个没有依赖项的组件,而 TodayWriter
是一个依赖于 IOutput
组件的组件。
为了表达这种依赖关系,请在您的结构体中使用#[inject]
属性来标记属性,并将属性声明为一个特质对象。
在我们的例子中
#[macro_use] extern crate he_di_derive;
#[derive(Component)] // <--- mark a struct as a Component that can be registered & resolved
#[interface(IDateWriter)] // <--- specify which interface it implements
struct TodayWriter {
#[inject] // <--- flag 'output' as a property which can be injected
output: Box<IOutput>, // <--- trait object using the interface `IOutput`
today: String,
year: usize,
}
应用程序启动
在应用程序启动时,您需要创建一个ContainerBuilder,并将您的组件注册到它上面。
在我们的例子中,我们使用以下方式将ConsoleOutput
和TodayWriter
注册到ContainerBuilder
中:
// Create your builder.
let mut builder = ContainerBuilder::new();
builder
.register::<ConsoleOutput>()
.as_type::<IOutput>();
builder
.register::<TodayWriter>()
.as_type::<IDateWriter>();
// Create a Container holding the DI magic
let mut container = builder.build().unwrap();
Container
引用是您稍后用于解析类型和组件的内容。您可以根据需要将其存储。
应用程序执行
在应用程序执行过程中,您需要使用您已注册的组件。您可以通过使用3个resolve()
方法之一从Container
中解析它们。
传递参数
在大多数情况下,您需要向组件传递参数。这可以在将组件注册到ContainerBuilder时或从Container中解析组件时完成。
您可以使用属性名称或属性类型注册参数。在后一种情况下,您需要确保它是唯一的。
在注册组件时
在注册时传递参数使用with_named_parameter()
或with_typed_parameter()
链式方法,如下所示:
builder
.register::<ConsoleOutput>()
.as_type::<IOutput>()
.with_named_parameter("prefix", "PREFIX >".to_string())
.with_typed_parameter::<usize>(117 as usize);
在解析组件时
在解析时传递参数使用您Container实例相同的with_named_parameter()
或with_typed_parameter()
方法。
对于我们的示例应用程序,我们创建了一个write_date()
方法,从容器中解析写入器,并演示了如何使用其名称或类型传递参数。
fn write_date(container: &mut Container) {
let writer = container
.with_typed_parameter::<IDateWriter, String>("June 20".to_string())
.with_named_parameter::<IDateWriter, usize>("year", 2017 as usize)
.resolve::<IDateWriter>()
.unwrap();
writer.write_date();
}
现在,当您运行程序时...
write_date()
方法请求he_di提供IDateWriter
。- he_di看到
IDateWriter
映射到TodayWriter
,因此开始创建一个TodayWriter
。 - he_di看到
TodayWriter
的构造函数需要一个IOutput
。 - he_di看到
IOutput
映射到ConsoleOutput
,因此创建了一个新的ConsoleOutput
实例。 - 由于
ConsoleOutput
没有更多的依赖关系,它使用此实例来完成TodayWriter
的构造。 - he_di将完全构造的
TodayWriter
返回给write_date()
以供使用。
稍后,如果我们想使我们的应用程序写入不同的日期,我们只需实现一个不同的IDateWriter
,然后更改应用程序启动时的注册。我们不需要更改任何其他类。太棒了,这是控制反转!
路线图
此crate的当前实现仍然是WIP。一些已确定的限制性知识(将被进一步探索)是:
#[derive(Component)]
应该针对复杂情况进行测试,并需要编写更多测试(例如,带有生命周期的结构体、泛型等)- 我们应该支持闭包作为创建参数的方式(在注册或解析时)
依赖关系
~0.2–1MB
~27K SLoC