如何使用实例变量值动态创建类方法

时间:2019-06-07 19:52:44

标签: ruby

让我们先看看代码,这将对我想要实现的目标有所帮助:

class PostalInfo
  attr_reader :name, :code

  def initialize (id, name, code)
    @id = id
    @name = name
    @code = code
  end

  def method_missing(method, *args, &blk)
    if method.to_s == "#{name}"
      return code
    else
      super
    end
  end
end

pi1 = PostalInfo.new(1, 'united_states', 'US')
pi2 = PostalInfo.new(2, 'united_kingdom', 'UK')

因此,当我运行以下代码时,其输出为:

pi1.united_states => 'US'
pi2.united_kingdom => 'UK'

到现在为止还不错,但我也想做类似的事情

PostalInfo.united_states => 'US'
PostalInfo.united_kingdom => 'UK'

该如何做,请先谢谢

3 个答案:

答案 0 :(得分:0)

这将设置一个类属性来保存数据,并且每当实例被初始化时,它就会添加到该数据结构中,并使用类似的类级别method_missing

class PostalInfo
  attr_reader :name, :code
  @@postal_info = {}

  def self.method_missing(method, *args, &blk)
    name = method.to_s
    if @@postal_info[name]
      @@postal_info[name]
    else
      super
    end
  end

  def initialize (id, name, code)
    @id = id
    @name = name
    @code = code
    @@postal_info[@name] = @code
  end

  def method_missing(method, *args, &blk)
    if method.to_s == "#{name}"
      return code
    else
      super
    end
  end
end
pi1 = PostalInfo.new(1, 'united_states', 'US')
pi2 = PostalInfo.new(2, 'united_kingdom', 'UK')
PostalInfo.united_states #=> 'US'
PostalInfo.united_kingdom #=> 'UK'

我会说,这似乎是一个怪异的设计,我通常建议避免在类方法中使用可变状态,并尽可能避免使用method_missing

答案 1 :(得分:0)

您可以这样写:

class PostalInfo
  POSTAL_HASH = {
    united_states: 'US',
    united_kingdom: 'UK',
  }.freeze

  def self.method_missing(method, *args, &blk)
    POSTAL_HASH[method] || super
  end
end

跳过缺少的方法可能会导致更好的性能:

class PostalInfo
  POSTAL_HASH = {
    united_states: 'US',
    united_kingdom: 'UK',
  }.freeze

  class << self
    POSTAL_HASH.each do |name, code|
      define_method(name) do
        code
      end
    end
  end
end

答案 2 :(得分:0)

除了一个例外,您需要在类的单例类中模拟答案的第一部分中的代码。区别在于实例变量的初始化。无需使用PostalInfo::newPostalInfo#initialize,而是需要创建一个用于执行此操作的类方法(我将其称为add_country_data)。请注意,由于未使用类的实例变量id,因此我没有将其包含在代码中。

class PostalInfo
  class << self
    attr_reader :country_data

    def add_country_data(name, code)
      (@country_data ||= {})[name] = code
    end

    def add_country_data(name, code)
      @country_data[name] = code
    end

    def method_missing(method, *args, &blk)
      return country_data[method.to_s] if country_data.key?(method.to_s)
      super
    end
  end
end

PostalInfo.add_country_data('united_states', 'US')
PostalInfo.add_country_data('united_kingdom', 'UK')
PostalInfo.united_states
  #=> "US" 
PostalInfo.united_kingdom
  #=> "UK" 
PostalInfo.france
  #=> NoMethodError (undefined method `france' for PostalInfo:Class)

尽管这符合您的要求,但我倾向于以更常规的方式构造该类:

class PostalInfo
  attr_reader :name, :code
  @instances = []

  def initialize(name, code)
    @name = name
    @code = code
    self.class.instances << self
  end

  singleton_class.public_send(:attr_reader, :instances)
end

us = PostalInfo.new('united_states', 'US')
uk = PostalInfo.new('united_kingdom', 'UK')
us.code
  #=> "US" 
uk.code
  #=> "UK"
PostalInfo.instances
  #=> [#<PostalInfo:0x00005c1f24c5ccf0 @name="united_states", @code="US">,
  #    #<PostalInfo:0x00005c1f24c71858 @name="united_kingdom", @code="UK">] 
相关问题