2

取自之前的一篇文章,对sepp2k关于命名空间的评论进行了一些修改,我已经实现了 String#to_class 方法。我在这里分享代码,我相信它可以以某种方式特别是“i”计数器进行重构。感谢您的意见。

 class String
   def to_class
     chain = self.split "::"
     i=0
     res = chain.inject(Module) do |ans,obj|
       break if ans.nil?
       i+=1
       klass = ans.const_get(obj)
       # Make sure the current obj is a valid class 
       # Or it's a module but not the last element, 
       # as the last element should be a class
       klass.is_a?(Class) || (klass.is_a?(Module) and i != chain.length) ? klass : nil
     end
   rescue NameError
     nil
   end
 end

 #Tests that should be passed.
 assert_equal(Fixnum,"Fixnum".to_class)
 assert_equal(M::C,"M::C".to_class)
 assert_nil "Math".to_class
 assert_nil "Math::PI".to_class
 assert_nil "Something".to_class
4

3 回答 3

6

我会具体看一下ActiveSupport::CoreExtensions::String::Inflections它的constantize方法:

def constantize(camel_cased_word)
  names = camel_cased_word.split('::')
  names.shift if names.empty? || names.first.empty?

  constant = Object
  names.each do |name|
    constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
  end
  constant
end
于 2009-09-19T15:11:40.503 回答
4

我出于好奇运行了一些基准测试,但我的解决方案非常慢!这是一个带有基准的重构解决方案,希望有所帮助。

require "benchmark"

class String
  def to_class_recursive
    chain = self.split "::"
    klass = parent.const_get chain.shift
    return chain.size < 1 ? (klass.is_a?(Class) ? klass : nil) : chain.join("::").to_class(klass)
  rescue
    nil
  end

  def to_class_original
    chain = self.split "::"
    i=0
    res = chain.inject(Module) do |ans,obj|
      break if ans.nil?
      i+=1
      klass = ans.const_get(obj)
      # Make sure the current obj is a valid class 
      # Or it's a module but not the last element, 
      # as the last element should be a class
      klass.is_a?(Class) || (klass.is_a?(Module) and i != chain.length) ? klass : nil
    end
  rescue NameError
    nil
  end

  def to_class_refactored
    chain = self.split "::"
    klass = Kernel
    chain.each do |klass_string|
      klass = klass.const_get klass_string
    end
    klass.is_a?(Class) ? klass : nil
  rescue NameError
    nil
  end
end

module M
  class C
  end
end

n = 100000
class_string = "M::C"
Benchmark.bm(20) do |x|
  x.report("to_class_recursive") { n.times { class_string.to_class_recursive } }
  x.report("to_class_original") { n.times { class_string.to_class_original } }
  x.report("to_class_refactored") { n.times { class_string.to_class_refactored } }
end

#                           user     system      total        real
# to_class_recursive    2.430000   0.170000   2.600000 (  2.701991)
# to_class_original     1.000000   0.010000   1.010000 (  1.049478)
# to_class_refactored   0.570000   0.000000   0.570000 (  0.587346)
于 2009-09-20T08:59:16.393 回答
2

您可以使用递归:

class String
  def to_class(parent = Kernel)
    chain = self.split "::"
    klass = parent.const_get chain.shift
    return chain.size < 1 ? (klass.is_a?(Class) ? klass : nil) : chain.join("::").to_class(klass)
    rescue
      nil
  end  
end
于 2009-09-19T21:25:17.410 回答