【问题标题】:Use current class in constant declaration在常量声明中使用当前类
【发布时间】:2017-03-22 19:22:47
【问题描述】:
class Cat
  SUPERSTARS = [Cat.new('John'), Cat.new('Alfred')]

  attr_accessor :name

  def initialize(name)
    @name = name
  end
end

我收到一个错误

ArgumentError: 参数数量错误(1 代表 0)

因为initialize 没有再次定义。

如果我把定义放在最后:

class Cat      
  attr_accessor :name

  def initialize(name)
    @name = name
  end

  SUPERSTARS = [Cat.new('John'), Cat.new('Alfred')]
end

它有效。

是否可以在文件顶部保留常量声明?

【问题讨论】:

    标签: ruby


    【解决方案1】:

    我想,你可以使用这个技巧:

    class Cat
      def self.const_missing(name)
        [Cat.new('John'), Cat.new('Alfred')] if name == :SUPERSTARS
      end
    
      attr_accessor :name
    
      def initialize(name)
        @name = name
      end
    end
    

    【讨论】:

    • 一个可以做到这一点,但不应该这样做。 :)
    • @SergioTulentsev 这是另一个问题 ;)
    • 是的,绝对是:)
    • Cat.constants # => []
    • @ndn 我编辑它,现在Cat.constants 将返回常量
    【解决方案2】:

    Avdi Grimm 在名为 "Barewords" 的免费 ruby​​tapas 剧集中建议您不要直接使用常量。相反,将它们包装在一个方法中。当你有一个方法时,你甚至不需要常量(在 ruby​​ 中它并不是真正的 constant)。

    所以,你可以这样做:

    class Cat
      def self.superstars
        @superstars ||= [Cat.new('John'), Cat.new('Alfred')]
      end
    
      attr_accessor :name
    
      def initialize(name)
        @name = name
      end
    end
    

    唯一的区别是您现在将其称为Cat.superstars 而不是Cat::SUPERSTARS。您的代码现在可以工作并且看起来也更好了!我称之为双赢。 :)

    【讨论】:

    • 你也可以使用不太受欢迎的来电者::,就像:Cat::superstars :)))
    • @AlexGolubenko:是的,出于同样的原因,我不会使用这种语法:代码的统一性。
    • 当然,这只是个玩笑)
    【解决方案3】:

    问题在于类定义只是在达到定义时执行的代码。这就像在询问您是否可以访问尚未定义的局部变量的值。

    一种可能的解决方法是延迟对常量的求值,直到类主体被执行。请注意,这并不值得,它显示了一个不应该在实践中使用的有效解决方案:

    # BasicObject for minimal amount of methods
    class Retarded < BasicObject
      def initialize(&value)
        @value = value
      end
    
      # Make sure we remove as many methods as we can to
      #  make the proxy more transparent.
      (instance_methods - %i(__id__ __send__ __binding__)).each do |name|
        undef_method name
      end
    
      def method_missing(*args, &block)
        # Get the actual value
        value = @value.call
        # Suppress warnings while we traverse constants
        warn_level, $VERBOSE = $VERBOSE, nil
    
        traversed_modules = []
        constant_finder = -> (root) do
          constant_name = root.constants.find do |constant|
            # We undefed ==, so we need a different way to compare.
            # Given that this class should be used for constant values in place,
            #  comparing object ids does the job.
            root.const_get(constant).__id__ == __id__
          end
    
          if constant_name
            # Just set the constant to the actual value
            root.const_set(constant_name, value)
          else
            # Recursively search for the containing module of the constant
            root.constants.each do |child_name|
              child_constant = root.const_get(child_name)
              if child_constant.is_a?(::Module) &&
                   !traversed_modules.include?(child_constant)
                # Handle circular references
                traversed_modules.push child_constant
                constant_finder.(child_constant) 
              end
            end
          end
        end
    
        # ::Object is the root module where constants are contained
        constant_finder.(::Object)
    
        # Bring back warnings
        $VERBOSE = warn_level
    
        # We have already found the constant and set its value to whatever was
        #  passed in the constructor. However, this method_missing was called
        #  in an attempt to call a method on the value. We now should make that
        #  invocation.
        value.public_send(*args, &block)
      end
    end
    
    # I know, the example is mononic.
    class EdwardKing
      DEAD = Retarded.new { [new('I'), new('II'), new('III')] }
    
      def initialize(number)
        @number = number
      end
    
      def whoami
        "Edward #{@number} of England"
      end
    end
    
    p EdwardKing::DEAD
      # [#<EdwardKing:0x007f90fc26fd10 @number="I">, #<EdwardKing:0x007f90fc26fc70 @number="II">, #<EdwardKing:0x007f90fc26fc20 @number="III">
    p EdwardKing::DEAD.map(&:whoami)
      # ["Edward I of England", "Edward II of England", "Edward III of England"]
    

    【讨论】:

      猜你喜欢
      • 2010-11-07
      • 1970-01-01
      • 2015-12-15
      • 1970-01-01
      • 1970-01-01
      • 2019-06-02
      • 1970-01-01
      相关资源
      最近更新 更多