一个空间怎么做多个网站,做视频解析网站是犯法的么,做小程序好还是做微网站好,免费的网页模版模板方法 Rust提供了trait#xff0c;类似于面向对象的接口#xff0c;不同的是#xff0c;将传统面向对象的虚函数表从对象中分离出来#xff0c;trait仍然是一个函数表#xff0c;只不过是独立的#xff0c;它的参数self指针可以指向任何实现了该trait的结构。 从对象中… 模板方法 Rust提供了trait类似于面向对象的接口不同的是将传统面向对象的虚函数表从对象中分离出来trait仍然是一个函数表只不过是独立的它的参数self指针可以指向任何实现了该trait的结构。 从对象中分离出虚函数表的trait带来了使用上与面向对象一些根本的不同这在我看来算是“很大”的不同了。让我们以模版方法设计模式为例来感受一下。先想一下rust怎么依赖trait和结构继承实现模板方法所谓模板方法就是父类留一个空白方法作为虚函数交给子类实现这样子类只负责不同的算法部分是面向对象中很基础很常用的手法了。用Rust语言照葫芦画瓢先描述一下大概框架如下 /// 一个父类A
struct A {...
}impl A {fn do_step1_common(mut self) { ... }// 缺省实现留给子类实现如果是C/Java这类面向对象语言很容易。若是Rust该怎么搞fn do_step2_maybe_different(mut self) { ... }fn do_step3_common(mut self) { ... }pub fn do_all_steps(mut self) {self.do_step1_common();self.do_step2_maybe_different();self.do_step3_common();}
}// 具体的某个子类实现
struct A1 {a: A,...
}impl A1 {// 开始实现fn do_step2_maybe_different(mut self) {// A1提供一种实现}
} 不瞒大家我初识rust时就被这样一个面向对象上的简单案例用rust实现给难住了当时卡在父类看起来像是一个完整的类型Rust怎么能未卜先知调用子类的方法呢 其实Rust要想实现这种效果不能A1继承A这种了而是A包含A1子类来实现反着来将不同的实现单独拆出来作为trait再交给子类实现。 trait DoStep2 {fn do_step2_maybe_different(mut self);
}/// 另一个父类B
struct BT: DoStep2 {t: T, // 或者Boxdyn DoStep2...
}implT BT {fn do_step1_common(mut self) { ... }fn do_step3_common(mut self) { ... }
}implT: DoStep2 BT {pub fn do_all_steps(mut self) {self.do_step1_common();self.t.do_step2_maybe_different();self.do_step3_common();}
}/// 具体的子类实现
struct B1 {...
}impl DoStep2 for B1 {fn do_step2_maybe_different(mut self) {// B1提供一种实现}
}// 这样
// BB1 相当于面向对象中的 A1
// BB2 相当于面向对象中的 A2 感觉不错看起来颇为妥当这种方式已经能在适合它的场景中工作也是模板方法的体现。对比下A、B都不是完整的父类实现A1、BB1才是真正的具体类型且它们都包含了父类的结构虽然BB1的写法有点不合常规。若子类还拥有自己的独立的扩展结构的话那Rust这种方式更优雅一些拆分的更原子、更合理。实践中往往不会这么完美的套用会复杂很多比如子类作为具体类型想访问父类的成员才能配合完成do_step2Rust又该怎么做面向对象的this指针则轻松支持。Rust不可能让B1再直接包含B那样循环包含了只能用引用或者指针来存在B1里面但这样的话岂不是太麻烦了循环引用/包含都是我们极力避免的东西麻烦到都想放弃模板方法了 为何会有这种差异因为面向对象的子类this指针其实指向的是整体子类的函数表是个本身就包含父类的整体而上述为B1实现DoStep2 trait的时候self指向的仅仅是B1并不知道B的存在。那怎么办得让self指向整体BB1那为BB1实现DoStep2行不行像下面这样 impl DoStep2 for BB1 {fn do_step2_maybe_different(mut self) {// 这里self可以访问“父类”B的成员了}
} 但回过头来B::do_all_steps(mut self)就没法在“父类”B中统一实现了因为BT在BB1具象化之前还不知道哪来的do_step2因此要在impl BB1中实现每个不同的具像化的子类都得单独实现相同的do_all_steps!你能接受不 也许你能接受为每个BB1、BB2...重复拷贝一遍各自的do_all_steps本文基于专业探讨还是要寻找一下编写通用的do_all_steps方法的有没有当然是有的前提是你得把do_step1_commondo_step3_common也得trait化然后在用一个trait组合限定搞定如下 trait DoStep1 {fn do_step1_common(mut self);
}trait DoStep3 {fn do_step2_common(mut self);
}// 因为BT是泛型只需为泛型编码实现一次DoStep1、DoStep3就行
implT DoStep1 for BT { ... }
implT DoStep3 for BT { ... }// 最后实现通用的do_all_steps还得靠泛型。
// 此时BB1已经满足T会为其实现下面的函数
// 可以这样读为所有实现了DoStep1/DoStep2/DoStep3特质的类型T实现do_all_steps
implT T
whereT: DoStep1 DoStep2 DoStep3
{pub fn do_all_steps(mut self) {self.do_step1_common();self.do_step2_maybe_different();self.do_step3_common();}
} 如何这样应该能接受了吧。Rust通过把问题解构的更细粒度完成了任务。客观对比下面向对象的实现还是简单些父类的do_step1和do_step3函数永远指向了同一个实现而Rust靠泛型应该是指向了3个不同的实现不知道编译期有没有优化盲猜应该有。可以说语法如此Rust只能做到如此了。与面向对象的模板方法相比最后一点小瑕疵就是要多定义DoStep1、DoStep2 2个trait并用一个T: DoStep1 DoStep2 DoStep3通用类型包含同样实现了DoStep1 DoStep2 DoStep3的BT进而代表它。可我们想仅仅为BT类型实现其他类型也不太可能这样实现了一个T则把范围不必要地扩大了。要是能按照我们想要的就仅为BT且实现了DoStep2的BT来实现do_all_steps就完美了。要做到此种程度必须能对自身Self进行限定如下 /// 可以这样读为所有自身实现了DoStep2的BT实现do_all_steps
implT BT
whereSelf: DoStep2
{pub fn do_all_steps(mut self) {self.do_step1_common();self.do_step2_maybe_different();self.do_step3_common();}
} 这种写法还真可以也不用额外定义DoStep1、DoStep3了因为本身BT已经有do_step1_common/do_step3_common的实现了Rust最新的稳定版就支持这样写 一段完整的Rust代码可以参考这里https://play.rust-lang.org/?versionstablemodedebugedition2021gistb80de6d4e6d75bf59bb37db386264fed 一个小小的模板方法Rust分离出2种不同的方式这是模板方法设计模式都没提到的2种方式还各有韵味。从定义的顺序上C的模板方法是 “子类后续扩展父类” Rust的模板方法则是 “父类提前包含子类泛型” 写法上还真是一开始不太好扭过来。可一旦扭转过来发现Rust挺强仍不失面向对象技巧。 反观面向对象一个模板方法案例让大家看到了些许面向对象的束缚其实也无伤大雅面向对象也能用纯组合的方式实现模板方法也不用继承如果需要组合的对象再通过构造动态传递进来那就跟策略模式很像了这种组合传递来的对象不止一个时就是策略模式然后让我想起了一个小争论子类应该严格不准访问父类的成员让父类的变化完全掌控在父类手中。面向对象的确可以做到全部private。但Rust的处理方式显示出了其对这些细节的语法表达更合乎逻辑。 总结 模板方法是面向对象虚函数继承的基本应用是面向对象很多设计模式的基础如装饰器模式。一篇讲解下来Rust从一开始别别扭扭到更好地支持模板方法其实能体会到Rust强迫你去拆解即便都是同一个模板方法但不同的细节要求子类是否需要访问父类都有不同的处理变化分出来的形式还更严格。写到最后Rust都感觉不到面向对象那味了那是什么味