[英]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.
我的主要問題是:
=> Handler::new(my_other_params, phone_handler_func)
- 這感覺就像是面向 Object 的方法
事實上,我不這么認為。 該代碼明顯違反了告訴-不詢問原則,該原則脫離了面向對象編程的中心思想:將數據和相關行為封裝到對象中。 對象( NameHandler
、 PhoneHandler
等)沒有足夠的知識來了解它們自己要做什么,因此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.