我有一个如下所示的配置结构:
struct Conf {
list: Vec<String>,
}
实现是在内部填充list
成员,但现在我已经决定我要将该任务委托给另一个对象。所以我有:
trait ListBuilder {
fn build(&self, list: &mut Vec<String>);
}
struct Conf<T: Sized + ListBuilder> {
list: Vec<String>,
builder: T,
}
impl<T> Conf<T>
where
T: Sized + ListBuilder,
{
fn init(&mut self) {
self.builder.build(&mut self.list);
}
}
impl<T> Conf<T>
where
T: Sized + ListBuilder,
{
pub fn new(lb: T) -> Self {
let mut c = Conf {
list: vec![],
builder: lb,
};
c.init();
c
}
}
这似乎工作正常,但现在我使用Conf
的所有地方,我必须改变它:
fn do_something(c: &Conf) {
// ...
}
变
fn do_something<T>(c: &Conf<T>)
where
T: ListBuilder,
{
// ...
}
由于我有很多这样的功能,这种转换很痛苦,特别是因为Conf
类的大多数用法都不关心ListBuilder
- 这是一个实现细节。我担心如果我向Conf
添加另一个泛型类型,现在我必须返回并在任何地方添加另一个泛型参数。有什么方法可以避免这种情况吗?
我知道我可以使用闭包代替列表构建器,但是我有一个额外的约束,我的Conf
结构需要是Clone
,而实际的构建器实现更复杂,并且在构建器中有一些函数和一些状态,这使得封闭方法笨拙。
您可以使用trait object Box<dyn ListBuilder>
隐藏构建器的类型。一些后果是动态调度(调用build
方法将通过虚函数表),额外的内存分配(盒装特征对象)和一些restrictions on the trait ListBuilder
。
trait ListBuilder {
fn build(&self, list: &mut Vec<String>);
}
struct Conf {
list: Vec<String>,
builder: Box<dyn ListBuilder>,
}
impl Conf {
fn init(&mut self) {
self.builder.build(&mut self.list);
}
}
impl Conf {
pub fn new<T: ListBuilder + 'static>(lb: T) -> Self {
let mut c = Conf {
list: vec![],
builder: Box::new(lb),
};
c.init();
c
}
}
虽然泛型类似乎可以“感染”其余的代码,但这正是它们有益的原因!编译器知道有多大,特别是使用什么类型,可以让它做出更好的优化决策。
话虽如此,它可能很烦人!如果您有少量实现特征的类型,您还可以构造这些类型的枚举并委托给子实现:
struct FromUser;
impl ListBuilder for FromUser { /**/ }
struct FromFile;
impl ListBuilder for FromFile { /**/ }
enum MyBuilders {
User(FromUser),
File(FromFile),
}
impl ListBuilder for MyBuilders {
fn build(&self, list: &mut Vec<String>) {
use MyBuilders::*;
match *self {
User(ref u) => u.build(list),
File(ref f) => f.build(list),
}
}
}
现在具体类型将是Conf<MyBuilders>
,您可以使用类型别名来隐藏。
当我希望能够在测试期间将测试实现注入代码时,我已经使用了这个效果,但是在生产代码中使用了一组固定的实现。