2009-10-05 35 views
6

嘿,那裏,我已經閱讀了幾篇關於何時/如何使用訪問者模式的文章,以及關於它的一些文章/章節,如果您正在遍歷AST並且它是高度結構化的,並且您希望將邏輯封裝到一個單獨的「訪問者」對象中,等等。但是對於Ruby來說,它看起來像是過度殺傷,因爲你可以使用塊來做幾乎相同的事情。Ruby中的訪問者模式,還是隻使用Block?

我想pretty_print xml使用Nokogiri。作者建議我使用訪問者模式,這需要我創建一個FormatVisitor或類似的東西,所以我可以說「node.accept(FormatVisitor.new)」。

問題是,如果我想開始自定義FormatVisitor中的所有東西(假設它允許您指定節點如何選項卡,屬性如何排序,屬性如何間隔等)。

  • 一次我想的節點具有用於每個窩1個標籤,屬性以任何順序
  • 下一次,我希望節點具有2位,並且在英文字母的屬性order
  • 下一次,我希望他們有3個空格,每行有兩個屬性。

我有幾個選項:

  • 在構造創建選項散列(FormatVisitor.new({:突片=> 2})
  • 我已經構建訪問者
  • 後的設定值
  • 子類的FormatVisitor每個新執行
  • 或者只是使用塊,而不是遊客

而不必構建FormatVisitor,設定值,並將其傳遞到node.accept方法,爲什麼不只是這樣做:


node.pretty_print do |format| format.tabs = 2 format.sort_attributes_by {...} end 

這是在對比什麼,我覺得像訪問者模式看起來像:


visitor = Class.new(FormatVisitor) do attr_accessor :format def pretty_print(node) # do something with the text @format.tabs = 2 # two tabs per nest level @format.sort_attributes_by {...} end end.new doc.children.each do |child| child.accept(visitor) end 

也許我已經得到了訪問者模式都是錯誤的,但是從我在Ruby中讀到它,它似乎有點小題大做。你怎麼看?無論哪種方式對我都很好,只是想知道你們對此感覺如何。

非常感謝, 蘭斯

回答

7

我會去與什麼是簡單的作品。我不知道細節,但是你寫的和訪問者模式相比,看起來更簡單。如果它也適用於你,我會用它。就我個人而言,我厭倦了所有這些要求您創建一個相互關聯的類的「網絡」的技術,只是爲了解決一個小問題。

有人會說,是的,但如果你使用模式,那麼你可以覆蓋許多未來的需求和等等等等。我說,現在做什麼可行,如果有需要,你可以在未來重構。在我的項目中,這種需求幾乎從不出現,但這是一個不同的故事。

+0

同意。如果您需要更強大的可維護性,請創建一個可以生成塊的方法,但我覺得訪問者模式可以使用本機Ruby代碼進行重構,就像Factory模式使用本機初始化程序幾乎可以構建一樣。 –

+6

實際上,模式的真正好處在於,他們通過使其他人瞭解您嘗試使用模式實現的內容,從而幫助維護。在執行過程中往往意圖喪失 - 如果你認識到這種模式,你就有更好的機會認識到這個意圖。 –

+0

非常感謝,這些都是很棒的一點。 –

12

實質上,一個Ruby塊訪客模式沒有額外的樣板。對於微不足道的情況,一個塊就足夠了。

例如,如果要對Array對象執行簡單操作,則只需使用塊調用#each方法,而不是實現單獨的Visitor類。

不過,也有在執行在某些情況下,具體的Visitor模式的優點:

  • 對於多個,類似,但複雜的操作,訪問者模式提供了繼承和塊沒有。
  • 清理者爲Visitor類編寫單獨的測試套件。
  • 將較小的笨類合併到較大的智能類中比將複雜的智能類分成較小的笨類要容易得多。

你的實現似乎婉轉複雜,並引入nokogiri預計impelment #visit方法,所以Visitor模式實際上是一個不錯的選擇在您的特定用例訪問者實例。這裏是訪問者模式的一類基於實現:

FormatVisitor實現#visit方法和使用Formatter子類取決於節點類型和其它條件來格式化每個節點。

# FormatVisitor implments the #visit method and uses formatter to format 
# each node recursively. 
class FormatVistor 

    attr_reader :io 

    # Set some initial conditions here. 
    # Notice that you can specify a class to format attributes here. 
    def initialize(io, tab: " ", depth: 0, attributes_formatter_class: AttributesFormatter) 
    @io = io 
    @tab = tab 
    @depth = depth 
    @attributes_formatter_class = attributes_formatter_class 
    end 

    # Visitor interface. This is called by Nokogiri node when Node#accept 
    # is invoked. 
    def visit(node) 
    NodeFormatter.format(node, @attributes_formatter_class, self) 
    end 

    # helper method to return a string with tabs calculated according to depth 
    def tabs 
    @tab * @depth 
    end 

    # creates and returns another visitor when going deeper in the AST 
    def descend 
    self.class.new(@io, { 
     tab: @tab, 
     depth: @depth + 1, 
     attributes_formatter_class: @attributes_formatter_class 
    }) 
    end 
end 

這裏執行AttributesFormatter上面使用。

# This is a very simple attribute formatter that writes all attributes 
# in one line in alphabetical order. It's easy to create another formatter 
# with the same #initialize and #format interface, and you can then 
# change the logic however you want. 
class AttributesFormatter 
    attr_reader :attributes, :io 

    def initialize(attributes, io) 
    @attributes, @io = attributes, io 
    end 

    def format 
    return if attributes.empty? 

    sorted_attribute_keys.each do |key| 
     io << ' ' << key << '="' << attributes[key] << '"' 
    end 
    end 

    private 

    def sorted_attribute_keys 
    attributes.keys.sort 
    end 
end 

NodeFormatter S使用工廠模式來實例化權格式化爲特定節點。在這種情況下,我區分了文本節點,葉元素節點,具有文本的元素節點和常規元素節點。每種類型都有不同的格式要求。還要注意,這並不完整,例如評論節點不被考慮在內。

class NodeFormatter 
    # convience method to create a formatter using #formatter_for 
    # factory method, and calls #format to do the formatting. 
    def self.format(node, attributes_formatter_class, visitor) 
    formatter_for(node, attributes_formatter_class, visitor).format 
    end 

    # This is the factory that creates different formatters 
    # and use it to format the node 
    def self.formatter_for(node, attributes_formatter_class, visitor) 
    formatter_class_for(node).new(node, attributes_formatter_class, visitor) 
    end 

    def self.formatter_class_for(node) 
    case 
    when text?(node) 
     Text 
    when leaf_element?(node) 
     LeafElement 
    when element_with_text?(node) 
     ElementWithText 
    else 
     Element 
    end 
    end 

    # Is the node a text node? In Nokogiri a text node contains plain text 
    def self.text?(node) 
    node.class == Nokogiri::XML::Text 
    end 

    # Is this node an Element node? In Nokogiri an element node is a node 
    # with a tag, e.g. <img src="foo.png" /> It can also contain a number 
    # of child nodes 
    def self.element?(node) 
    node.class == Nokogiri::XML::Element 
    end 

    # Is this node a leaf element node? e.g. <img src="foo.png" /> 
    # Leaf element nodes should be formatted in one line. 
    def self.leaf_element?(node) 
    element?(node) && node.children.size == 0 
    end 

    # Is this node an element node with a single child as a text node. 
    # e.g. <p>foobar</p>. We will format this in one line. 
    def self.element_with_text?(node) 
    element?(node) && node.children.size == 1 && text?(node.children.first) 
    end 

    attr_reader :node, :attributes_formatter_class, :visitor 

    def initialize(node, attributes_formatter_class, visitor) 
    @node = node 
    @visitor = visitor 
    @attributes_formatter_class = attributes_formatter_class 
    end 

    protected 

    def attribute_formatter 
    @attribute_formatter ||= @attributes_formatter_class.new(node.attributes, io) 
    end 

    def tabs 
    visitor.tabs 
    end 

    def io 
    visitor.io 
    end 

    def leaf? 
    node.children.empty? 
    end 

    def write_tabs 
    io << tabs 
    end 

    def write_children 
    v = visitor.descend 
    node.children.each { |child| child.accept(v) } 
    end 

    def write_attributes 
    attribute_formatter.format 
    end 

    def write_open_tag 
    io << '<' << node.name 
    write_attributes 
    if leaf? 
     io << '/>' 
    else 
     io << '>' 
    end 
    end 

    def write_close_tag 
    return if leaf? 
    io << '</' << node.name << '>' 
    end 

    def write_eol 
    io << "\n" 
    end 

    class Element < self 
    def format 
     write_tabs 
     write_open_tag 
     write_eol 
     write_children 
     write_tabs 
     write_close_tag 
     write_eol 
    end 
    end 

    class LeafElement < self 
    def format 
     write_tabs 
     write_open_tag 
     write_eol 
    end 
    end 

    class ElementWithText < self 
    def format 
     write_tabs 
     write_open_tag 
     io << text 
     write_close_tag 
     write_eol 
    end 

    private 

    def text 
     node.children.first.text 
    end 
    end 

    class Text < self 
    def format 
     write_tabs 
     io << node.text 
     write_eol 
    end 
    end 
end 

要使用這個類:

xml = "<root><aliens><alien><name foo=\"bar\">Alf<asdf/></name></alien></aliens></root>" 
doc = Nokogiri::XML(xml) 

# the FormatVisitor accepts an IO object and writes to it 
# as it visits each node, in this case, I pick STDOUT. 
# You can also use File IO, Network IO, StringIO, etc. 
# As long as it support the #puts method, it will work. 
# I'm using the defaults here. (two spaces, with starting depth at 0) 
visitor = FormatVisitor.new(STDOUT) 

# this will allow doc (the root node) to call visitor.visit with 
# itself. This triggers the visiting of each children recursively 
# and contents written to the IO object. (In this case, it will 
# print to STDOUT. 
doc.accept(visitor) 

# Prints: 
# <root> 
# <aliens> 
#  <alien> 
#  <name foo="bar"> 
#   Alf 
#   <asdf/> 
#  </name> 
#  </alien> 
# </aliens> 
# </root> 

與上面的代碼,你可以通過構建的NodeFromatter小號額外的子類改變節點格式的行爲,並將其插入工廠方法。您可以通過AttributesFromatter的各種實施控制屬性的格式。只要你堅持它的界面,你可以將其插入attributes_formatter_class參數,而無需修改任何東西。

的設計模式一覽表中所用:

  • 訪問者模式:處理節點遍歷的邏輯。 (另外,通過引入nokogiri接口要求。)
  • 工廠模式,使用了基於節點類型和其他格式的條件,以確定格式。請注意,如果您不喜歡NodeFormatter上的類方法,則可以將它們解壓縮爲NodeFormatterFactory以更加合適。
  • 依賴注入(DI/IOC),用於控制屬性的格式。

這演示瞭如何將幾個圖案組合在一起以實現您想要的靈活性。雖然,如果你需要那些靈活性是必須決定。

+0

您可以添加一個很好的示例來描述_visitor_模式。 –

+0

增加了一些具體的例子,盡情享受吧! –

+0

感謝您的支持。我有很多關於設計模式的問題和概念..如果你有時間,你能幫我每週教授那些......嗎?再次感謝這個答案的進一步解釋.. –