Rust 如何在不支持继承的情况实现 getter/setter 的代码复用?

2021-05-26 11:12:16 +08:00
 xiaopanzi

Rust 不支持继承,这对从 Java 转过来的人有点困扰。近日在读设计模式的书籍,里面有个例子是:有一个抽象的 Duck 类,它有一个属性是 FlyBehavior (是个接口)。

abstract class Duck {
   private FlyBehavior flyBehavior;
   public void setFlyBehavior(FlyBehavior flyBehavior) { this.flyBehavior = flyBehavior; }
   public FlyBehavior getFlyBehavior() { return this.flyBehavior; }
}

再 Rust 里面,只能这样写:

trait Duck {
    fn get_fly_behavior(&self) -> &dyn FlyBehavior;
    fn set_fly_behavior(&mut self, fly_behavior: Box<dyn FlyBehavior>);
}

这样的话,所有实现 Duck 的其他 struct 都必须写 一模一样 的 getter/setter 。

所以,在 Rust 中针对这个场景,如何能够实现代码复用?

2611 次点击
所在节点    Rust
13 条回复
jedrek
2021-05-26 13:17:08 +08:00
吃饭有吃饭的方式,不需要用吃屎的方式吃饭
VDimos
2021-05-26 13:42:56 +08:00
写个宏呗,derive macro 也可以
iikebug
2021-05-26 13:44:14 +08:00
1 楼说的对,不要用吃屎的方式吃饭
Vegetable
2021-05-26 13:46:46 +08:00
你是换了语言,不是换了一种 JAVA,不要被写法限制是想法
xiaopanzi
2021-05-26 13:47:36 +08:00
@jedrek 请大佬明示。用 Rust 模仿 Java 确实不妥。但如果要实现类似功能(比如这里的 Strategy Pattern ),Rust 的“吃饭”方式应该是什么?
xiaopanzi
2021-05-26 13:48:20 +08:00
@VDimos 谢谢提示。我学习一下 Macro 的写法。
Jirajine
2021-05-26 13:50:43 +08:00
rust 有 sum type,大多数情况下传统 oo 里的继承都是不必要的。
如果是多个类型共同的行为,那就用 trait 。
xiaopanzi
2021-05-26 14:05:07 +08:00
@Jirajine I see 。我能理解只有 trait 而不用继承的 trade-off 。那么具体到对于“试图共享 get/set 的具体实现”这一基本问题,在 Rust 中是不是除了重复就别无他法(不引入 macro 的情况下)?另外,Rust 中针对 Strategy Pattern 是否有更好的解决方案,而不用写 get/set ?再进一步,鉴于 Rust 不是传统的 OO 语言,是不是很多时候写 Design Pattern 就注定了是“吃屎”的写法?
Jirajine
2021-05-26 14:49:22 +08:00
@xiaopanzi #8 trait 中方法的实现当然可以共享,但你这种共享属性的用法肯定不适用。你把 Java 专用的 Design Pattern 八股文往其他语言上套,那当然是“吃屎”的写法。

因为范式不同,代码组织的方式也不同,很难直接套上。你要类似的可以看这个 https://rust-unofficial.github.io/patterns/patterns/behavioural/strategy.html

被 JavaOOP 设计模式毒害太深的话可以学一下 haskell,放宽思路,回过头来就豁然开朗了。
Jirajine
2021-05-26 14:51:59 +08:00
@xiaopanzi #8 总的来说,一般使用继承的场景,表达 sum type -> 用 enum,表达 subtyping -> 用泛型,表达接口 ->用 trait 。
jedrek
2021-05-26 15:17:13 +08:00
设计模式的出现,某种程度上是针对某种某类编程语言特点或弥补它的不足,若有些编程语言没有这些问题,自不必要考虑这些设计模式,硬套只会创造本不应有的问题。

getter/setter 概念是 OOP 宗教化出现的东西,本质上控制某个属性读写权限,实际使用中也是如此。Rust 的设计没有这种将值函数化的意图,

如果你仅仅想使用 Rust 实现策略模式,可以这样写

```rust
fn main() {
Context { strategy: StrategyA }.execute();
Context { strategy: StrategyB }.execute();
}

struct Context<T: Strategy> {
strategy: T
}
impl<T: Strategy> Context<T> {
fn execute(&self) {}
}

trait Strategy {
fn do_something();
}

struct StrategyA;
struct StrategyB;

impl Strategy for StrategyA {
fn do_something() { }
}

impl Strategy for StrategyB {
fn do_something() { }
}
```
xiaopanzi
2021-05-26 15:54:57 +08:00
@Jirajine 学习了。多谢!
xiaopanzi
2021-05-26 15:55:09 +08:00
@jedrek Thanks 。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/779270

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX