在Rust中,创建”依赖反转”的一些惯用方法有哪些?

huangapple go评论90阅读模式
英文:

What are some idiomatic ways to create “dependency inversion” in rust?

问题

我正在寻找在Rust中创建"依赖反转"(如Robert C. Martin的《Clean Architecture》中所描述)的方法。

我一直在尝试在我的Rust项目中实现这一点:能够编写依赖于它所组成的模块的代码,而不是相反(因此我不需要任何实际的运行时多态性,这就是为什么选择Rust的原因)。

有哪些干净的方法可以实现这样的系统?感谢您的帮助。

我对这个问题的解决方案是使用特质(traits)和特质绑定泛型(trait bound generics)作为接口。

例如:

struct Car<W: WheelsTrait, S: SeatTrait>{
    wheels: [W; 4],
    seat: S,
}

但是这导致泛型在更高级别的模块中扩散和累积。更不用说特质绑定(trait bounds)让我的impl变得混乱。

我尝试了一些其他越来越不符合惯例的方法,比如为每个去除了泛型的struct定义类型别名。

英文:

I’m looking to create “dependency inversion” in rust (as described in Robert C. Martin‘s Clean Architecture).

I’ve been trying to implement this in my rust projects: Being able to write code thats dependent on the modules that it makes up, rather than vise-versa. (I don't need any actual runtime polymorphism, hence rust.)

What would be some clean ways to implement such a system? Thanks for the help.

My solution to the problem has been to use traits and trait bound generics to act as interfaces.

ie:

struct Car&lt;W: WheelsTrait, S: SeatTrait&gt;{
wheels: [W;4],
seat: S,
}

But this led to generics spreading and building up in higher level modules. Not to mention that trait bounds were cluttering my impls.

I’ve tried some other, increasingly un-idiomatic, methods: like having a type aliases for each struct with the generics removed.

答案1

得分: 2

如果你想使用泛型实现依赖注入(DI),那么泛型将会在整个代码库中传播。此外,我不确定你是否能够获得使用泛型实现DI的所有好处。

你想要的是运行时多态性,也就是动态分派。

struct Car {
  wheels: [Box<dyn Wheel>; 4],
  seat: Box<dyn Seat>,
}

有了这个,你就不再需要处理泛型的传播。但是,如果你对所有类型都这样做,你可能还不如使用Java。至少Java的运行时针对大规模动态性进行了优化。

相反,在Rust中,你需要定义领域边界。这些组件确实可以切换的组件。结构体做出重大决策:使用哪个数据库,使用哪种输出格式,如何读取输入。换句话说,这些决策会影响程序与外部世界的交互方式。

对于其他事情,你可能会使用DI来模拟内部部件。解决这个问题的方法是使用特性标志,或者干脆不使用。在不同的环境中,只在边界处切换来测试你的结构体。

英文:

If you want to implement DI with generics, then the generics will spread all throughout the codebase. Furthermore, I am not sure you will even get all the benefits of DI with generics.

What you want is runtime polymorphism. That is, dynamic dipatch.

struct Car {
  wheels: [Box&lt;dyn Wheel&gt;; 4],
  seat: Box&lt;dyn Seat&gt;,
}

With this, you no longer have to deal with generics spreading. But if you do this for all your types, you might as well use Java. At least that runtime is optimised for massive dynamicism.

Instead, in Rust, what you would do is that you would define domain boundaries. Components that really do make sense to switch around. Structs that make big decisions: which database to use, which output format to use, how to read input. In other words, decisions that affect how your program will interact with the rest of the world.

For other things, you will probably use DI to mock the internal parts. The solution to that is, use feature-flags, or just, don't. Test your structs end to end, switching out only the boundaries when in different environments.

huangapple
  • 本文由 发表于 2023年8月9日 01:45:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/76862016.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定