4

ここで簡単な例を考えてみましょう。

class Base
  @tag = nil 

  def self.tag(v = nil) 
    return @tag unless v 
    @tag = v
  end 
end 

class A < Base 
  tag :A
end

class B < Base
  tag :B
end 

class C < Base; end

puts "A: #{A.tag}"
puts "B: #{B.tag}"
puts "A: #{A.tag}"
puts "C: #{C.tag}"

期待どおりに機能します

A: A
B: B 
A: A
C: 

ベースが拡張されて同じ機能を提供するが、クラスによって指定されたすべてのタグ情報を持つモジュールを作成したいと思います。例えば。

module Tester 
  def add_ident(v); ....; end
end

class Base 
  extend Tester 

  add_ident :tag
end 

私はまっすぐな評価でそれを行うことができることを発見したので、:

def add_ident(v)
  v = v.to_s 
  eval "def self.#{v}(t = nil); return @#{v} unless t; @#{v} = t; end"
end

しかし、私はどの言語でもeval文字列を使用するのは本当に嫌いです。

evalを使用せずにこの機能を取得する方法はありますか?私はdefine_methodとinstance_variable_get/setのすべての組み合わせを考えてきましたが、それを機能させることができません。

RailsなしのRuby1.9。

4

4 に答える 4

3

拡張するクラスのシングルトンクラスに動的メソッドを定義する必要があります。クラスのシングルトンクラスには、次のような式でアクセスできますclass << self; self end。クラスのクラスのスコープを開くには、を使用できますclass_eval。これらすべてをまとめると、次のように書くことができます。

module Identification

  def add_identifier(identifier)
    (class << self; self end).class_eval do
      define_method(identifier) do |*args|
        value = args.first
        if value
          instance_variable_set("@#{identifier}", value)
        else
          instance_variable_get("@#{identifier}")
        end
      end
    end
  end

end

class A
  extend Identification

  add_identifier :tag
end

最近のバージョンのRubyを使用している場合、このアプローチはModule#define_singleton_methodに置き換えることができます。

module Identification

  def add_identifier(identifier)
    define_singleton_method(identifier) do |value = nil|
      if value
        instance_variable_set("@#{identifier}", value)
      else
        instance_variable_get("@#{identifier}")
      end
    end
  end

end

self.class.send(:define_method)ここにある別の回答に示されているように、あなたが使用したいとは思わない。これには、のすべての子クラスに動的メソッドを追加するという意図しない副作用が あります。私の例では、self.classです。AClass

于 2012-10-09T03:08:25.023 に答える
1
module Tester
  def add_ident(var)
    self.class.send(:define_method, var) do |val=nil|
        return instance_variable_get("@#{var}") unless val
        instance_variable_set "@#{var}", val
      end
    end
end
于 2012-10-09T02:41:22.667 に答える
1

私のお気に入りのルビーの本MetaprogrammingRubyは、次のようにこれらの質問を解決しました。

module AddIdent 
  def self.included(base)
    base.extend ClassMethods    # hook method
  end

  module ClassMethods
    def add_ident(tag)
      define_method "#{tag}=" do |value=nil|
        instance_variable_set("@#{tag}", value)
      end

      define_method tag do 
        instance_variable_get "@#{tag}"
      end 
    end
  end
end 

# And use it like this
class Base
  include AddIdent

  add_ident :tag
end
于 2012-10-09T03:00:20.003 に答える
0

投稿するのに十分な欲求不満になったら、答えを見つけるのはいつもの方法ではありません:)

ローカルスコープを破壊せずにクラスインスタンスを提供するための秘訣は(class << self; self; end)にあるようです。参照:define_methodを使用してクラスメソッドを作成するにはどうすればよいですか?

def add_ident(v) 
  var_name = ('@' + v.to_s).to_sym 
  (class << self; self; end).send(:define_method, v) do |t = nil|
    return instance_variable_get(var_name) unless t
    instance_variable_set(var_name, t)
  end 
end 

でも、一緒に来ればもっと良い答えを受け入れます。

于 2012-10-09T02:38:52.543 に答える