如何在 Rust 中抽象远程或本地的可序列化对象?
How to abstract over serializable objects being either remote or local in Rust?
我有一个枚举来表示节点是本地的(指的是当前节点)还是远程的,在这种情况下,通过 RPC 调用进行通信:
pub enum Node {
Local,
Remote(SocketAddr),
}
我有一个代表本地数据库的结构 DB
。我需要序列化此 Node
枚举,因为它用作元数据以确定某些键值对的存储位置(本地或远程)。我将此元数据存储在与其余数据相同的数据库中,因此 Node::Local
指的是存储它的数据库。
我希望反序列化的节点枚举包含对从中取出的数据库的引用,以便我可以将 Local
变体转换为 Local(DB)
。然后我会对 Local
和 Remote
变体具有相同的语义,因为它们都包含足够的信息来执行 Node.get(key)
和 Node.insert(key, value)
操作。
对于远程变体,我只需要打开一个到 SocketAddr
的连接并发出 RPC 请求,但是对于 Local
变体,我必须在我拥有的地方进行模式匹配对本地数据库的引用,并应用特殊逻辑而不是通常说 Node.get(key)
。根本问题是 Node::Local
没有包含足够的信息来对本地数据库执行请求,而 Node::Remote
中的 SocketAddr
足以执行 RPC 调用。
我可以通过创建一个自定义反序列化方法来解决这个问题,我将当前 DB
作为对该反序列化方法的引用传递,但我想知道是否还有其他好的方法来解决这个问题。
我最终将这种区别表示为枚举。对于序列化和实际工作使用单独的枚举,因为我无法序列化对本地数据库的引用,如果您不需要序列化,您当然可以避开 NodeRepr 枚举:
#[derive(Serialize, Deserialize)]
pub enum NodeRepr {
Local,
Remote(NodeRef),
}
pub enum Node<'a> {
Local(&'a LocalDB),
Remote(NodeRef),
}
节点枚举然后具有 impl 方法,通过匹配语句将必要的方法调用委托给 localdb 或 NodeRef 方法。
impl Node {
pub async fn get(&self, key: String) -> Option<String> {
match self {
Node::Local(db) => db.get(key),
Node::Remote(noderef) => noderef.get(key).await,
}
}
}
这种处理方式允许我们仍然保留节点是本地节点还是远程节点的信息,因此我们可以在必要时在代码中的任何地方使用此信息。这很有用,因为您通常不想总是隐藏有关某物是本地还是远程的信息。
例如,如果您总是想隐藏节点是远程节点还是本地节点以加强代码清洁度,您可以使用特征对象,其中 LocalDB 和 NodeRef 都实现了相同的特征。
特征特别有用,因为它允许添加新节点类型(本地和远程之外)而无需更改任何旧代码。对于枚举,预计只有两个不同枚举情况的代码将在添加新节点类型时中断。特征对象的局限性也可以通过应用经典的 OOP 模式(如访问者模式)来克服。
我有一个枚举来表示节点是本地的(指的是当前节点)还是远程的,在这种情况下,通过 RPC 调用进行通信:
pub enum Node {
Local,
Remote(SocketAddr),
}
我有一个代表本地数据库的结构 DB
。我需要序列化此 Node
枚举,因为它用作元数据以确定某些键值对的存储位置(本地或远程)。我将此元数据存储在与其余数据相同的数据库中,因此 Node::Local
指的是存储它的数据库。
我希望反序列化的节点枚举包含对从中取出的数据库的引用,以便我可以将 Local
变体转换为 Local(DB)
。然后我会对 Local
和 Remote
变体具有相同的语义,因为它们都包含足够的信息来执行 Node.get(key)
和 Node.insert(key, value)
操作。
对于远程变体,我只需要打开一个到 SocketAddr
的连接并发出 RPC 请求,但是对于 Local
变体,我必须在我拥有的地方进行模式匹配对本地数据库的引用,并应用特殊逻辑而不是通常说 Node.get(key)
。根本问题是 Node::Local
没有包含足够的信息来对本地数据库执行请求,而 Node::Remote
中的 SocketAddr
足以执行 RPC 调用。
我可以通过创建一个自定义反序列化方法来解决这个问题,我将当前 DB
作为对该反序列化方法的引用传递,但我想知道是否还有其他好的方法来解决这个问题。
我最终将这种区别表示为枚举。对于序列化和实际工作使用单独的枚举,因为我无法序列化对本地数据库的引用,如果您不需要序列化,您当然可以避开 NodeRepr 枚举:
#[derive(Serialize, Deserialize)]
pub enum NodeRepr {
Local,
Remote(NodeRef),
}
pub enum Node<'a> {
Local(&'a LocalDB),
Remote(NodeRef),
}
节点枚举然后具有 impl 方法,通过匹配语句将必要的方法调用委托给 localdb 或 NodeRef 方法。
impl Node {
pub async fn get(&self, key: String) -> Option<String> {
match self {
Node::Local(db) => db.get(key),
Node::Remote(noderef) => noderef.get(key).await,
}
}
}
这种处理方式允许我们仍然保留节点是本地节点还是远程节点的信息,因此我们可以在必要时在代码中的任何地方使用此信息。这很有用,因为您通常不想总是隐藏有关某物是本地还是远程的信息。
例如,如果您总是想隐藏节点是远程节点还是本地节点以加强代码清洁度,您可以使用特征对象,其中 LocalDB 和 NodeRef 都实现了相同的特征。
特征特别有用,因为它允许添加新节点类型(本地和远程之外)而无需更改任何旧代码。对于枚举,预计只有两个不同枚举情况的代码将在添加新节点类型时中断。特征对象的局限性也可以通过应用经典的 OOP 模式(如访问者模式)来克服。