rust - 我怎样才能让这个 Rust 代码更地道
问题描述
最近我开始学习 Rust,我的主要困难之一是将多年的面向对象思维转化为过程代码。
我正在尝试解析具有由特定处理程序处理的标记的 XML,该处理程序可以处理从子项获取的数据。
此外,我还有一些它们之间共有的字段成员,我不希望将相同的字段写入所有处理程序。
我试过了,我的代码是这样的:
use roxmltree::Node; // roxmltree = "0.14.0"
fn get_data_from(node: &Node) -> String {
let tag_name = get_node_name(node);
let tag_handler: dyn XMLTagHandler = match tag_name {
"name" => NameHandler::new(),
"phone" => PhoneHandler::new(),
_ => DefaultHandler::new()
}
if tag_handler.is_recursive() {
for child in node.children() {
let child_value = get_data_from(&child);
// do something with child value
}
}
let value: String = tag_handler.value()
value
}
// consider that handlers are on my project and can be adapted to my needs, and that XMLTagHandler is the trait that they share in common.
我的主要问题是:
- 这感觉像是一种面向对象的方法。
- is_recursive 需要重新实现到每个结构,因为它们的特征不能有字段成员,我以后必须添加更多字段,这意味着每个新字段都有更多样板;
- 我可以为 Handler 使用一种类型并将函数指针传递给它,但这种方法似乎很脏。例如:
=> Handler::new(my_other_params, phone_handler_func)
解决方案
- 这感觉像是一种面向对象的方法
事实上,我不这么认为。此代码明显违反了告诉-不询问原则,该原则脱离了面向对象编程的中心思想:将数据和相关行为封装到对象中。对象(NameHandler
、PhoneHandler
等)没有足够的知识来了解它们自己要做什么,因此get_data_from
必须向它们查询信息并决定要做什么,而不是简单地发送消息并让对象弄清楚如何处理它。
因此,让我们首先将有关如何处理每种标签的知识转移到处理程序本身:
trait XmlTagHandler {
fn foreach_child<F: FnMut(&Node)>(&self, node: &Node, callback: F);
}
impl XmlTagHandler for NameHandler {
fn foreach_child<F: FnMut(&Node)>(&self, _node: &Node, _callback: F) {
// "name" is not a recursive tag, so do nothing
}
}
impl XmlTagHandler for DefaultHandler {
fn foreach_child<F: FnMut(&Node)>(&self, node: &Node, callback: F) {
// all other tags may be recursive
for child in node.children() {
callback(child);
}
}
}
这样你foreach_child
就可以调用每一种Handler
,并让处理程序自己决定正确的操作是否是递归。毕竟,这就是他们有不同类型的原因——对吧?
为了摆脱dyn
不必要的部分,让我们编写一个小通用帮助函数,用于XmlTagHandler
处理一种特定类型的标签,并进行修改get_data_from
,使其仅调度到正确的参数化版本。(我假设它XmlTagHandler
也有一个new
功能,以便您可以通用地创建一个。)
fn handle_tag<H: XmlTagHandler>(node: &Node) -> String {
let handler = H::new();
handler.foreach_child(node, |child| {
// do something with child value
});
handler.value()
}
fn get_data_from(node: &Node) -> String {
let tag_name = get_node_name(node);
match tag_name {
"name" => handle_tag::<NameHandler>(node),
"phone" => handle_tag::<PhoneHandler>(node),
_ => handle_tag::<DefaultHandler>(node),
}
}
如果你不喜欢handle_tag::<SomeHandler>(node)
,也可以考虑制作handle_tag
一个提供的方法XmlTagHandler
,这样你就可以改写SomeHandler::handle(node)
。
请注意,我并没有真正改变任何数据结构。XmlTagHandler
您对特征和各种实现者的假设Handler
是组织代码的一种非常正常的方式。但是,在这种情况下,与仅编写三个单独的函数相比,它并没有提供任何真正的改进:
fn get_data_from(node: &Node) -> String {
let tag_name = get_node_name(node);
match tag_name {
"name" => get_name_from(node),
"phone" => get_phone_from(node),
_ => get_other_from(node),
}
}
在某些语言中,例如 Java,所有代码都必须是某个类的一部分——因此您会发现自己编写的类由于任何其他原因不存在,而只是将相关事物组合在一起。在 Rust 中,您不需要这样做,因此请确保任何增加的复杂性,例如XmlTagHandler
实际上是在拉扯它的重量。
- is_recursive 需要重新实现到每个结构,因为它们的特征不能有字段成员,我以后必须添加更多字段,这意味着每个新字段都有更多样板
如果没有有关这些领域的更多信息,就不可能真正理解您在这里面临的问题;然而,一般来说,如果有一个struct
s 族有一些共同的数据,你可能想要创建一个泛型struct
而不是一个特征。请参阅如何重用二叉搜索树、红黑树和 AVL 树的代码?更多建议。
- 我可以为 Handler 使用一种类型并将函数指针传递给它,但这种方法似乎很脏
优雅有时是有用的东西,但它是主观的。我会推荐闭包而不是函数指针,但这个建议对我来说似乎并不“脏”。制作闭包并将它们放入数据结构中是编写 Rust 代码的一种非常正常的方式。如果您可以详细说明您不喜欢它的哪些方面,也许有人可以指出改进它的方法。
推荐阅读
- wicket - 单击ajax按钮时Jquery不起作用
- monitoring - 如何计算grafana中2个数据点之间的差异
- mysql - MySQL 在存储过程中使用 WHERE IN ()
- c++ - 提供的参数数量少于函数所需的函数调用是否可能?
- jquery - 无法从连接应用程序发出发布请求
- python - Issue with variable defining
- android - 我的按钮没有采用背景颜色
- ruby-on-rails - Rails 用守卫替换 if/elseif 块
- javascript - Vue.js - 使用外部脚本而不是导入的脚本
- google-apps-script - 谷歌脚本将没有 URL 的图像插入到谷歌表格