2011-07-18 22 views
3

給定一個複雜的對象層次結構,幸運的是它不包含循環引用,如何實現支持各種格式的序列化?我不是在這裏討論一個實際的實現。相反,我在尋找可能派上用場的設計模式暗示。是否有任何模式序列化和反序列化各種格式的對象層次結構?

爲了更精確一點:我使用Ruby,我想解析XML和JSON數據以構建複雜的對象層次結構。此外,應該可以將此層次結構序列化爲JSON,XML和可能的HTML。

我可以利用Builder這個模式嗎?在上述任何一種情況下,我都有某種結構化數據 - 無論是在內存中還是在文本中 - 我想用建立別的東西。

我認爲將序列化邏輯與實際業務邏輯分開是很好的做法,以便稍後可以輕鬆地支持多種XML格式。

回答

7

我最終創建了一個基於Builder和Strategy模式的解決方案。我使用Builder模式提取解析並將邏輯構建到自己的類中。這使我可以輕鬆地分別添加新的分析器和構建器。我使用Strategy模式來實現個人解析和構建邏輯,因爲這個邏輯取決於我的輸入和輸出格式。

下圖顯示了我的解決方案的UML圖。下面

Parser/Builder Model

的清單顯示我的Ruby實現。實現有點微不足道,因爲我構建的對象非常簡單。對於那些認爲這段代碼臃腫和Java-ish的人,我認爲這實際上是一個很好的設計。我承認,在這樣一個微不足道的案例中,我可以直接將構建方法構建到我的業務對象中。然而,我並不是在我的其他應用程序中構建水果,而是相當複雜的對象,因此分離解析,構建和業務邏輯似乎是一個好主意。

require 'nokogiri' 
require 'json' 

class Fruit 

    attr_accessor :name 
    attr_accessor :size 
    attr_accessor :color 

    def initialize(attrs = {}) 
    self.name = attrs[:name] 
    self.size = attrs[:size] 
    self.color = attrs[:color] 
    end 

    def to_s 
    "#{size} #{color} #{name}" 
    end 

end 

class FruitBuilder 

    def self.build(opts = {}, &block) 
    builder = new(opts) 
    builder.instance_eval(&block) 
    builder.result 
    end 

    def self.delegate(method, target) 
    method = method.to_sym 
    target = target.to_sym 

    define_method(method) do |*attrs, &block| 
     send(target).send(method, *attrs, &block) 
    end 
    end 

end 

class FruitObjectBuilder < FruitBuilder 

    attr_reader :fruit 

    delegate :name=, :fruit 
    delegate :size=, :fruit 
    delegate :color=, :fruit 

    def initialize(opts = {}) 
    @fruit = Fruit.new 
    end 

    def result 
    @fruit 
    end 

end 

class FruitXMLBuilder < FruitBuilder 

    attr_reader :document 

    def initialize(opts = {}) 
    @document = Nokogiri::XML::Document.new 
    end 

    def name=(name) 
    add_text_node(root, 'name', name) 
    end 

    def size=(size) 
    add_text_node(root, 'size', size) 
    end 

    def color=(color) 
    add_text_node(root, 'color', color) 
    end 

    def result 
    document.to_s 
    end 

    private 

    def add_text_node(parent, name, content) 
    text = Nokogiri::XML::Text.new(content, document) 
    element = Nokogiri::XML::Element.new(name, document) 

    element.add_child(text) 
    parent.add_child(element) 
    end 

    def root 
    document.root ||= create_root 
    end 

    def create_root 
    document.add_child(Nokogiri::XML::Element.new('fruit', document)) 
    end 

end 

class FruitJSONBuilder < FruitBuilder 

    attr_reader :fruit 

    def initialize(opts = {}) 
    @fruit = Struct.new(:name, :size, :color).new 
    end 

    delegate :name=, :fruit 
    delegate :size=, :fruit 
    delegate :color=, :fruit 

    def result 
    Hash[*fruit.members.zip(fruit.values).flatten].to_json 
    end 

end 

class FruitParser 

    attr_reader :builder 

    def initialize(builder) 
    @builder = builder 
    end 

    def build(*attrs, &block) 
    builder.build(*attrs, &block) 
    end 

end 

class FruitObjectParser < FruitParser 

    def parse(other_fruit) 
    build do |fruit| 
     fruit.name = other_fruit.name 
     fruit.size = other_fruit.size 
     fruit.color = other_fruit.color 
    end 
    end 

end 

class FruitXMLParser < FruitParser 

    def parse(xml) 
    document = Nokogiri::XML(xml) 

    build do |fruit| 
     fruit.name = document.xpath('/fruit/name').first.text.strip 
     fruit.size = document.xpath('/fruit/size').first.text.strip 
     fruit.color = document.xpath('/fruit/color').first.text.strip 
    end 
    end 

end 

class FruitJSONParser < FruitParser 

    def parse(json) 
    attrs = JSON.parse(json) 

    build do |fruit| 
     fruit.name = attrs['name'] 
     fruit.size = attrs['size'] 
     fruit.color = attrs['color'] 
    end 
    end 

end 

# -- Main program ---------------------------------------------------------- 

p = FruitJSONParser.new(FruitXMLBuilder) 
puts p.parse('{"name":"Apple","size":"Big","color":"Red"}') 

p = FruitXMLParser.new(FruitObjectBuilder) 
puts p.parse('<fruit><name>Apple</name><size>Big</size><color>Red</color></fruit>') 

p = FruitObjectParser.new(FruitJSONBuilder) 
puts p.parse(Fruit.new(:name => 'Apple', :color => 'Red', :size => 'Big')) 
0

任何時候有人說他們想用不同的算法做同樣的操作,並選擇在運行時使用哪種算法,總是會想到Strategy pattern。不同類型的序列化(XML,JSON,二進制,無論)都是將對象轉換爲純數據,更便攜的結構的不同策略。似乎這可能適用於您的情況。

+0

好吧,我現在看看這個。也許,我找到了一些可以幫助我解決問題的靈感。然後,我會更新我的問題。非常感謝。 – t6d

0

看看面向模式的軟件體系結構,反射模式;我認爲它應該給出一些關於如何實施這樣一種結構的良好暗示。

相關問題