如何避免将具体结构更改为通用的连锁反应?

问题描述 投票:2回答:2

我有一个如下所示的配置结构:

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,而实际的构建器实现更复杂,并且在构建器中有一些函数和一些状态,这使得封闭方法笨拙。

generics rust traits
2个回答
4
投票

您可以使用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
    }
}

5
投票

虽然泛型类似乎可以“感染”其余的代码,但这正是它们有益的原因!编译器知道有多大,特别是使用什么类型,可以让它做出更好的优化决策。

话虽如此,它可能很烦人!如果您有少量实现特征的类型,您还可以构造这些类型的枚举并委托给子实现:

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>,您可以使用类型别名来隐藏。

当我希望能够在测试期间将测试实现注入代码时,我已经使用了这个效果,但是在生产代码中使用了一组固定的实现。

© www.soinside.com 2019 - 2024. All rights reserved.