代码之家  ›  专栏  ›  技术社区  ›  seladb

具有通用方法特征的动态调度

  •  0
  • seladb  · 技术社区  · 6 年前

    我有一个与描述的类似的用例 here 但是有一点不同,我的解决方案不能用非泛型方法替换泛型方法。这是我的密码( Rust Playground ):

    use serde::{de::DeserializeOwned, Serialize};
    use serde_json;
    
    trait Serializer { 
        fn serialize_data<V>(&self, data: &V) -> Result<String, String> where V: Serialize;
    
        fn deserialize_data<V>(&self, ser_data: &str) -> Option<V> where V: DeserializeOwned;
    }
    
    struct JsonSerializer { 
        x: i32 // some member I need to store
    }
    
    impl JsonSerializer {
        fn new() -> JsonSerializer {
            JsonSerializer { x: 1 }
        }
    }
    
    impl Serializer for JsonSerializer {
        fn serialize_data<V>(&self, data: &V) -> Result<String, String> where V: Serialize {
            match serde_json::to_string(data) {
                Ok(ser_data) => Ok(ser_data),
                Err(err) => Err(err.to_string())        
            }
        }
    
        fn deserialize_data<V>(&self, ser_data: &str) -> Option<V> where V: DeserializeOwned {
            match serde_json::from_str(ser_data).unwrap() {
                Ok(val) => Some(val),
                Err(_) => None
            }
        }
    }
    
    
    
    // I may want to have more serializer objects like 
    // YamlSerizlier, BincodeSerializer and so on...
    // ...
    
    struct MyMainObject {
        serializer: Box<Serializer>
    }
    
    impl MyMainObject {
        fn new() -> MyMainObject {
            MyMainObject { serializer: Box::new(JsonSerializer::new()) }
        }
    
        fn do_something(&self) {
            println!("{}", self.serializer.serialize_data(&1));
            println!("{}", self.serializer.serialize_data(&String::from("MY STRING")));
        }
    }
    
    fn main() {
        let my_main_object = MyMainObject::new();
        my_main_object.do_something();
    }
    

    如前一个问题所述,编译此代码时,我会得到一个错误 the trait `Serializer` cannot be made into an object 因为它有通用方法:

       Compiling playground v0.0.1 (/playground)
    error[E0038]: the trait `Serializer` cannot be made into an object
      --> src/main.rs:42:5
       |
    42 |     serializer: Box<Serializer>
       |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Serializer` cannot be made into an object
       |
       = note: method `serialize_data` has generic type parameters
       = note: method `deserialize_data` has generic type parameters
    

    但是在我的例子中,我希望这些方法保持通用性,这样我就可以序列化/反序列化任何类型的数据。

    因此,我的问题是如何保持动态调度模式并使其继续工作,这意味着我希望 Serializer 性状成员 MyMainObject 我可以使用任何类型的序列化程序对象(JSON、YAML等)进行初始化,然后调用 serializer.serialize_data() serializer.deserialize_data() 里面 主要对象 .

    如果这是不可能的,你能建议什么是最好的选择?

    编辑:

    我需要一个适用于不同类型的序列化程序的解决方案,列出这些序列化程序:

    2 回复  |  直到 6 年前
        1
  •  1
  •   Michail    6 年前

    注释

    以下不是一个很好的长期解决方案,它只是一个权宜之计。做你想做的事情的一个正确方法是找出并实现一个方法来协调 bincode serde_yaml 具有 erased_serde . 但如果你现在需要它,这是

    要旨

    基本上,你可以用枚举来写一个穷人的动态调度。它或多或少像这样(我已经简化并省略了一些东西):

    struct JsonSerializer();
    struct YamlSerializer();
    
    trait Serializer {
        fn serialize<V>(&self, thing: &V) -> ();
    }
    
    impl Serializer for JsonSerializer {
        fn serialize<V>(&self, thing: &V) -> () {
            println!("json");
        }
    }
    
    impl Serializer for YamlSerializer {
        fn serialize<V>(&self, thing: &V) -> () {
            println!("yaml");
        }
    }
    
    // That's what we'll be using instead of Box<dyn Serializer>
    enum SomeSerializer {
        Json(JsonSerializer),
        Yaml(YamlSerializer),
    }
    
    impl SomeSerializer {
        pub fn serialize<V>(&self, thing: &V) -> () {
            match self {
                SomeSerializer::Json(ser) => ser.serialize(thing),
                SomeSerializer::Yaml(ser) => ser.serialize(thing),
            }
        }
    }
    

    下面是您使用它的方法(除了您可能希望在这里使用实际的构造函数函数):

    pub fn main() {
        let thing = 2;
        let json = SomeSerializer::Json(JsonSerializer());
        let yaml = SomeSerializer::Yaml(YamlSerializer());
        json.serialize(&thing);
        yaml.serialize(&yaml);
    }
    

    这有严重的缺点(见下文),但它允许您将具有通用方法的东西打包到统一的接口中。

    问题

    这种方法的主要问题是很难向设置中添加新的序列化程序。用 Box<dyn Serializer> 你所要做的就是 impl Serializer 为了某件事。在这里,您必须在枚举中添加一个变量,并在所有相关方法中对其进行模式匹配。这不方便放在板条箱里 SomeSerializer 定义,以及 不可能的 其他板条箱。此外,向公共枚举添加一个变量是一个突破性的变化,下游板条箱可能并不完全受欢迎。有一些方法可以在某种程度上改善这一点:

    隐藏 躯体化器

    这对 躯体化器 公开。在其上进行模式匹配的能力几乎没有好处,而且它是公共的,限制了您可以在不破坏下游事物的情况下对其进行什么操作。通常的解决方案是将其置于不透明结构中并导出,使枚举本身隐藏:

    pub struct VisibleSerializer(SomeSerializer);
    

    仍然使用特性

    你不能延长 躯体化器 其他板条箱中有额外的序列化程序。您可以继续在它上面安装更多的枚举层(这既不幸又丑陋),但是原始板条箱中的任何函数都不会接受这种构造。这是有帮助的:而不是 serialize 一种固有的方法 躯体化器 实现 Serializer 并使所有将要使用的函数 躯体化器 通用并接受 T: Serializer . 突然,所有下游板条箱都可以向设置中添加他们想要的序列化程序。

    仅特殊情况特殊情况

    四个序列化程序中有三个以上是这样包装的,这有点荒谬,更不用说尴尬了。但是,如果您要使用的大多数序列化程序实际上是 埃拉塞德塞尔德 -兼容,您可以在 躯体化器 ,并且只有不兼容的变体才有单独的变体:

    enum SomeSerializer {
        Whatever(Box<dyn erased_serde::Serializer>),
    }
    
        2
  •  2
  •   Sebastian Redl    6 年前

    不能在动态调度中使用非对象安全特性;对象安全规则专门针对阻止动态调度的对象。

    有时,对于特定的场景有解决方法。它们通常很复杂。但为了 serde 具体来说,有 erased_serde 板条箱,因为你不是第一个有这个问题的人。