簡體   English   中英

我怎樣才能使這個 Rust 代碼更慣用

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

最近我開始學習 Rust,我的主要困難之一是將多年的 Object 面向的思維轉換為程序代碼。

我正在嘗試解析一個 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.

我的主要問題是:

  • 這感覺就像是面向 Object 的方法;
  • is_recursive 需要重新實現到每個結構,因為它們的特征不能有字段成員,我以后必須添加更多字段,這意味着每個新字段都有更多樣板;
  • 我可以為 Handler 使用一種類型並將 function 指針傳遞給它,但這種方法似乎很臟。 例如: => Handler::new(my_other_params, phone_handler_func)
  • 這感覺就像是面向 Object 的方法

事實上,我不這么認為。 該代碼明顯違反了告訴-不詢問原則,該原則脫離了面向對象編程的中心思想:將數據和相關行為封裝到對象中。 對象( NameHandlerPhoneHandler等)沒有足夠的知識來了解它們自己要做什么,因此get_data_from必須向它們查詢信息並決定要做什么,而不是簡單地發送消息並讓object 弄清楚如何處理它。

因此,讓我們首先將有關如何處理每種標簽的知識轉移到處理程序本身:

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

這樣,您就可以在每種Handler上調用foreach_child ,並讓處理程序本身決定正確的操作是否是遞歸。 畢竟,這就是他們有不同類型的原因——對吧?

為了擺脫不必要的dyn部分,讓我們編寫一個小通用幫助程序 function ,它使用XmlTagHandler處理一種特定類型的標簽,並修改get_data_from以便它只是分派到正確的參數化版本。 (我假設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),
    }
}

如果你不喜歡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,所有代碼都必須是某些 class 的一部分——因此您會發現自己編寫的類由於任何其他原因不存在,而不是將相關事物組合在一起。 在 Rust 中,您不需要這樣做,因此請確保任何添加的復雜性(例如XmlTagHandler )實際上都在發揮作用。

  • is_recursive 需要重新實現到每個結構,因為它們的特征不能有字段成員,我以后必須添加更多字段,這意味着每個新字段都有更多樣板

如果沒有有關這些領域的更多信息,就不可能真正理解您在這里面臨的問題; 然而,一般來說,如果有一個struct家族有一些共同的數據,你可能想要創建一個通用的struct而不是一個 trait。 請參閱如何重用二叉搜索樹、紅黑樹和 AVL 樹的代碼? 更多建議。

  • 我可以為 Handler 使用一種類型並將 function 指針傳遞給它,但這種方法似乎很臟

優雅有時是有用的東西,但它是主觀的。 我會推薦關閉而不是 function 指針,但這個建議對我來說似乎並不“臟”。 制作閉包並將它們放入數據結構中是編寫 Rust 代碼的一種非常正常的方式。 如果您可以詳細說明您不喜歡它的哪些方面,也許有人可以指出改進它的方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM