从未知模块访问值,委派给未知方法

时间:2014-03-05 17:20:40

标签: ruby

我正在为许多不同的协议编写network protocol decoder。此应用程序是一个命令行应用程序,不使用Rails。

示例代码以说明我的挑战(Live Demo):

#!/usr/bin/env ruby

# define the base protocol handler

module Base
  MSG_HASH = {0x30=>:One,0x31=>:Two,0x32=>:Three}

  class Decoder
    def decode(wire)
      # get the msg type
      msg_type = MSG_HASH[wire]
      # delegate to a handler for that msg type
      send("decode_#{msg_type}_message".to_sym)
    end

    def decode_One_message
      "This was a One message"
    end

    def decode_Two_message
      "This was a Two message"
    end
  end
end

# define a handler for a specialized protocol

module Special
  MSG_HASH = Base::MSG_HASH.merge({0x4a=>:Foo, 0x73=>:Bar})

  class Decoder < Base::Decoder
    def decode_Foo_msg
      "Specialized Foo message"
    end
    def decode_Bar_message
      "Specialized Bar message"
    end
  end
end

# define a handler for another specialized protocol
module AnotherSpecial
  MSG_HASH = Base::MSG_HASH.merge({0x4a=>:Zippity, 0x73=>:DooDaa})

  class Decoder < Base::Decoder
    def decode_Zippity_message
      "Zippity"
    end
    def decode_DooDaa_message
      "DooDaa"
    end
  end
end

# decode the first specialized protocol
puts "Decoding the first protocol"
[0x30, 0x31, 0x4A, 0x73].each do |wire|
  decoder = Special::Decoder.new
  puts "(#{wire}) decoded to '#{decoder.decode(wire)}'"
end

# decode the second specialized protocol
puts "Decoding the second protocol"
[0x30, 0x31, 0x4A, 0x73].each do |wire|
  decoder = AnotherSpecial::Decoder.new
  puts "(#{wire}) decoded to '#{decoder.decode(wire)}'"
end

incorect输出是:

Decoding the first protocol
(48) decoded to 'This was a One message'
(49) decoded to 'This was a Two message'
./hacks:13:in `decode': undefined method `decode__message' for #<Special::Decoder:0x00000001ccac58> (NoMethodError)
    from ./hacks:59:in `block in <main>'
    from ./hacks:57:in `each'
    from ./hacks:57:in `<main>'

但我希望它是:

Decoding the first protocol
(48) decoded to 'This was a One message'
(49) decoded to 'This was a Two message'
(74) decoded to 'Specialized Foo message'
(115) decoded to 'Specialized Bar message'
Decoding the second protocol
(48) decoded to 'This was a One message'
(49) decoded to 'This was a Two message'
(74) decoded to 'Zippity'
(115) decoded to 'DooDaa'

我认为这里的基本问题是Base::Decoder::decode不知道添加到Special::MSG_HASH的额外值。怎么可能,对吧?

我的基本约束是Base模块无法知道关于专用协议的任何,或者处理这些专用协议的模块或类中的任何内容。例如,将Base::decoder更改为提及SpecialAnotherSpecial模块或其中的任何内容都不适用于我。

我该如何解决这个问题?

不要害怕将我的代码撕成碎片。我完全不相信我的解决方案不是完全紊乱


如果您对我的约束感到疑惑,请解释一下。我正在为网络协议编写解码器。这些协议中的一些共享公共子协议,并且在这些子协议内是字段(如消息类型),其可以具有由子协议或专用协议定义的值。

一个例子是消息类型。消息类型字段出现在共享XMT子协议的所有消息上。消息类型标识的一些消息在XMT协议中定义,其他消息由专用协议定义。这些专用消息类型对于专用协议是唯一的,并且可能存在重叠。例如,在ABC专用协议中,消息类型0x77可能表示Quote,而在XYZ协议中,它可能表示Replay。因此专用消息类型只能由专用协议的处理程序定义。但是消息类型0x30由XMT协议定义,并且始终表示Heartbeat

3 个答案:

答案 0 :(得分:2)

使其工作的一种方法是将专用消息哈希移动到特殊类中,然后使用self.class::MSG_HASH访问它。

这是完整的代码;它给出了你想要的输出:

module Base
  MSG_HASH = {0x30=>:One, 0x31=>:Two, 0x32=>:Three}

  class Decoder
    def decode(wire)
      msg_type = self.class::MSG_HASH[wire]
      send("decode_#{msg_type}_message".to_sym)
    end
    def decode_One_message
      "This was a One message"
    end
    def decode_Two_message
      "This was a Two message"
    end
  end
end

module Special
  class Decoder < Base::Decoder
    MSG_HASH = Base::MSG_HASH.merge({0x4a => :Foo, 0x73 => :Bar})
    def decode_Foo_message
      "Specialized Foo message"
    end
    def decode_Bar_message
      "Specialized Bar message"
    end
  end
end

module AnotherSpecial
  class Decoder < Base::Decoder
    MSG_HASH = Base::MSG_HASH.merge({0x4a => :Zippity, 0x73 => :DooDaa})
    def decode_Zippity_message
      "Zippity"
    end
    def decode_DooDaa_message
      "DooDaa"
    end
  end
end

puts "Decoding the first protocol"
[0x30, 0x31, 0x4A, 0x73].each do |wire|
  decoder = Special::Decoder.new
  puts "(#{wire}) decoded to '#{decoder.decode(wire)}'"
end

puts "Decoding the second protocol"
[0x30, 0x31, 0x4A, 0x73].each do |wire|
  decoder = AnotherSpecial::Decoder.new
  puts "(#{wire}) decoded to '#{decoder.decode(wire)}'"
end

免责声明:我只在JRuby中测试了这个,而不是MRI。

答案 1 :(得分:2)

编辑:约翰,有一件事困扰我整个方法就是需要调用

decoder = XX::Decoder.new

其中XXSpecialAnotherSpecial等。根据我对问题的理解,在我看来,能够添加,删除或重命名协议会更好模块没有对其他代码进行任何更改,并且在解码协议时使用嵌套类。

我可以想到几种方法可以做到这一点。两者的核心思想是每个协议处理程序都有两个方法可以由Base::Decorder#decode调用,即:can_decode?(identifier)将返回一个truthy,指示它是否可以解码由{{1}标识的协议};和identifier的目的很明显。

方法1

创建一个模块decode(message, other_params),其中包含(可能是Protocols)所有协议处理程序模块/类。这些类不一定是include的子类。然后使用Module#included_modules和其他方法(例如,请参阅thisthis)来创建Base::Decoder中所有协议类的数组:

Base::Decoder

你要初始化

@protocol_classes

并通过包含

进行访问
@protocol_classes = []

要使用class << self attr_reader :protocol_classes end 标识的协议解码邮件,identifier会调用:

Base::Decoder:decode

方法2

当我看到你的问题时,我立即想到了Russ Olsen在他出色的着作 Eloquent Ruby (第20章)中描述的一种技术,它依赖于钩子Class#inherited。方法1的唯一区别是协议类将是decoder_class = @protocol_classes.find { |c| c.can_decode?(identifier) } if decoder_class decoder_class.decode(message, other_params) else ... 的子类,就像现在一样,您可以通过将以下内容添加到Base::Decoder来创建数组@protocol_classes:

Base::Decorder#decode

同样,这两种方法的优点在于可以添加,删除和重命名协议模块,而不需要对其余代码进行任何更改。

编辑结束。

以下是否适合您?你会看到我只是让def self.inherited(subclass) Decoder.protocol_classes << subclass end 要求调用类将哈希与Base::Decoder#decode合并。

MSG_HASH

定义专用协议的处理程序

module Base
  MSG_HASH = {0x30=>:One,0x31=>:Two,0x32=>:Three}

  class Decoder
    def decode(wire)
      h = MSG_HASH.merge(self.class.hash_ext)
      # get the msg type
      msg_type = h[wire]
      # delegate to a handler for that msg type
      send("decode_#{msg_type}_message".to_sym)
    end

    def decode_One_message
      "This was a One message"
    end

    def decode_Two_message
      "This was a Two message"
    end
  end
end

为另一个专门协议定义处理程序

module Special
  MSG_HASH = {0x4a=>:Foo, 0x73=>:Bar}

  class Decoder < Base::Decoder
    def self.hash_ext
      MSG_HASH
    end
    def decode_Foo_message
      "Specialized Foo message"
    end
    def decode_Bar_message
      "Specialized Bar message"
    end
  end
end

解码第一个专用协议

module AnotherSpecial
  MSG_HASH = {0x4a=>:Zippity, 0x73=>:DooDaa}

  class Decoder < Base::Decoder
    def self.hash_ext
      MSG_HASH
    end
    def decode_Zippity_message
      "Zippity"
    end
    def decode_DooDaa_message
      "DooDaa"
    end
  end
end

解码第二个专用协议

puts "Decoding the first protocol"
[0x30, 0x31, 0x4A, 0x73].each do |wire|
  decoder = Special::Decoder.new
  puts "(#{wire}) decoded to '#{decoder.decode(wire)}'"
end
(48) decoded to 'This was a One message'
(49) decoded to 'This was a Two message'
(74) decoded to 'Specialized Foo message'
(115) decoded to 'Specialized Bar message'
=> [48, 49, 74, 115]

或者,为了提供更大的灵活性,可以替换

puts "Decoding the second protocol"
[0x30, 0x31, 0x4A, 0x73].each do |wire|
  decoder = AnotherSpecial::Decoder.new
  puts "(#{wire}) decoded to '#{decoder.decode(wire)}'"
end

(48) decoded to 'This was a One message'
(49) decoded to 'This was a Two message'
(74) decoded to 'Zippity'
(115) decoded to 'DooDaa'
=> [48, 49, 74, 115]
<{1>}中的

h = MSG_HASH.merge(self.class.hash_ext)

并使用

替换Base::Decoder#decodeh = self.class.hash_ext(MSG_HASH) 模块中的类方法hash_ext
Special

或其他内容,具体取决于要求。

答案 2 :(得分:1)

您可以删除Special::MSG_HASH常量并将以下行添加到Special::Decoder类:

Base::MSG_HASH.merge!(0x4a => :Foo, 0x73 => :Bar)

但请注意,这将修改Base::MSG_HASH常量。

您还定义了decode_Foo_msg,您可能想要输入decode_Foo_message

无论如何,你应该看一下Forwardable模块。