简体   繁体   English

我怎样才能使这个 Rust 代码更惯用

[英]How can I make this Rust code more idiomatic

Recently I started to learn Rust and one of my main struggles is converting years of Object Oriented thinking into procedural code.最近我开始学习 Rust,我的主要困难之一是将多年的 Object 面向的思维转换为程序代码。

I'm trying to parse a XML that have tags that are processed by an specific handler that can deal with the data it gets from the children.我正在尝试解析一个 XML 具有由特定处理程序处理的标签,该处理程序可以处理它从孩子那里获得的数据。

Further more I have some field members that are common between them and I would prefer not to have to write the same fields to all the handlers.此外,我还有一些它们之间共有的字段成员,我不希望将相同的字段写入所有处理程序。

I tried my hand on it and my code came out like this:我试过了,我的代码是这样的:

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.

My main issues with this are:我的主要问题是:

  • This feels like a Object oriented approach to it;这感觉就像是面向 Object 的方法;
  • is_recursive needs to be reimplemented to each struct because they traits cannot have field members, and I will have to add more fields later, which means more boilerplate for each new field; is_recursive 需要重新实现到每个结构,因为它们的特征不能有字段成员,我以后必须添加更多字段,这意味着每个新字段都有更多样板;
  • I could use one type for a Handler and pass to it a function pointer, but this approach seems dirty.我可以为 Handler 使用一种类型并将 function 指针传递给它,但这种方法似乎很脏。 eg: => Handler::new(my_other_params, phone_handler_func)例如: => Handler::new(my_other_params, phone_handler_func)
  • This feels like a Object oriented approach to it这感觉就像是面向 Object 的方法

Actually, I don't think so.事实上,我不这么认为。 This code is in clear violation of the Tell-Don't-Ask principle , which falls out from the central idea of object-oriented programming: the encapsulation of data and related behavior into objects.该代码明显违反了告诉-不询问原则,该原则脱离了面向对象编程的中心思想:将数据和相关行为封装到对象中。 The objects ( NameHandler , PhoneHandler , etc.) don't have enough knowledge about what they are to do things on their own, so get_data_from has to query them for information and decide what to do, rather than simply sending a message and letting the object figure out how to deal with it.对象( NameHandlerPhoneHandler等)没有足够的知识来了解它们自己要做什么,因此get_data_from必须向它们查询信息并决定要做什么,而不是简单地发送消息并让object 弄清楚如何处理它。

So let's start by moving the knowledge about what to do with each kind of tag into the handler itself:因此,让我们首先将有关如何处理每种标签的知识转移到处理程序本身:

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);
        }
    }
}

This way you call foreach_child on every kind of Handler , and let the handler itself decide whether the right action is to recurse or not.这样,您就可以在每种Handler上调用foreach_child ,并让处理程序本身决定正确的操作是否是递归。 After all, that's why they have different types -- right?毕竟,这就是他们有不同类型的原因——对吧?

To get rid of the dyn part, which is unnecessary, let's write a little generic helper function that uses XmlTagHandler to handle one specific kind of tag, and modify get_data_from so it just dispatches to the correct parameterized version of it.为了摆脱不必要的dyn部分,让我们编写一个小通用帮助程序 function ,它使用XmlTagHandler处理一种特定类型的标签,并修改get_data_from以便它只是分派到正确的参数化版本。 (I'll suppose that XmlTagHandler also has a new function so that you can create one generically.) (我假设XmlTagHandler也有一个new的 function 以便您可以通用地创建一个。)

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),
    }
}

If you don't like handle_tag::<SomeHandler>(node) , also consider making handle_tag a provided method of XmlTagHandler , so you can instead write SomeHandler::handle(node) .如果你不喜欢handle_tag::<SomeHandler>(node) ,也可以考虑让handle_tag成为XmlTagHandler提供的方法,这样你就可以改写SomeHandler::handle(node)

Note that I have not really changed any of the data structures.请注意,我并没有真正改变任何数据结构。 Your presumption of an XmlTagHandler trait and various Handler implementors is a pretty normal way to organize code.您对XmlTagHandler特征和各种Handler实现者的假设是组织代码的一种非常正常的方式。 However, in this case, it doesn't offer any real improvement over just writing three separate functions:但是,在这种情况下,与仅编写三个单独的函数相比,它并没有提供任何真正的改进:

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),
    }
}

In some languages, such as Java, all code has to be part of some class – so you can find yourself writing classes that don't exist for any other reason than to group related things together.在某些语言中,例如 Java,所有代码都必须是某些 class 的一部分——因此您会发现自己编写的类由于任何其他原因不存在,而不是将相关事物组合在一起。 In Rust you don't need to do this, so make sure that any added complication such as XmlTagHandler is actually pulling its weight.在 Rust 中,您不需要这样做,因此请确保任何添加的复杂性(例如XmlTagHandler )实际上都在发挥作用。

  • is_recursive needs to be reimplemented to each struct because they traits cannot have field members, and I will have to add more fields later, which means more boilerplate for each new field is_recursive 需要重新实现到每个结构,因为它们的特征不能有字段成员,我以后必须添加更多字段,这意味着每个新字段都有更多样板

Without more information about the fields, it's impossible to really understand what problem you're facing here;如果没有有关这些领域的更多信息,就不可能真正理解您在这里面临的问题; however, in general, if there is a family of struct s that have some data in common, you may want to make a generic struct instead of a trait.然而,一般来说,如果有一个struct家族有一些共同的数据,你可能想要创建一个通用的struct而不是一个 trait。 See the answers to How to reuse codes for Binary Search Tree, Red-Black Tree, and AVL Tree?请参阅如何重用二叉搜索树、红黑树和 AVL 树的代码? for more suggestions.更多建议。

  • I could use one type for a Handler and pass to it a function pointer, but this approach seems dirty我可以为 Handler 使用一种类型并将 function 指针传递给它,但这种方法似乎很脏

Elegance is sometimes a useful thing, but it is subjective.优雅有时是有用的东西,但它是主观的。 I would recommend closures rather than function pointers, but this suggestion doesn't seem "dirty" to me.我会推荐关闭而不是 function 指针,但这个建议对我来说似乎并不“脏”。 Making closures and putting them in data structures is a very normal way to write Rust code.制作闭包并将它们放入数据结构中是编写 Rust 代码的一种非常正常的方式。 If you can elaborate on what you don't like about it, perhaps someone could point out ways to improve it.如果您可以详细说明您不喜欢它的哪些方面,也许有人可以指出改进它的方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM