Метапрограммирование: Как обнаружить реальный класс объекта? - PullRequest
6 голосов
/ 27 марта 2011

Я шутил с метапрограммированием в Ruby, и я сделал этот код:

class Class
  def ===(other)
    other.kind_of?(self)
  end
end
class FakeClass
  def initialize(object)
    methods.each {|m| eval "undef #{m}" if m.to_sym != :methods }
    define = proc do |m|
      eval(<<-END)
        def #{m}(*a, &b)
          @object.#{m}(*a, &b)
        rescue Object
          raise $!.class, $!.message.gsub("FakeClass", @object.class.to_s),
                $!.backtrace-[$!.backtrace[-caller.size-1]]
        end
      END
    end
    object.methods.each {|m| define[m] }
    def method_missing(name, *a, &b)
      if @object.methods.include?(name.to_s)
        define[name]
        eval "@object.#{name}(*a, &b)"
      elsif @object.methods.include?("method_missing")
        eval "@object.#{name}(*a, &b)"
      else
        super
      end
    rescue Object
      raise $!.class, $!.message.gsub("FakeClass", @object.class.to_s),
            $!.backtrace-[$!.backtrace[-caller.size-1]]
    end
    @object = object
  end
end

Это создает поддельный класс, который имитирует объект. Посмотрите:

a = FakeClass.new(1)  # => 1
a.class               # => Fixnum
a.methods             # => Return all Fixnum methods
a + 1                 # => 2 (is not a FakeClass)
Fixnum === a          # => true
a.something           # => NoMethodError:
                      #    undefined method `something' for 1:Fixnum
class Fixnum
  def foo
    true
  end
end

a.foo                 # => true

Проблема в том, что теперь я не знаю, как узнать, является ли объект реальным или фальшивым. Другими словами, если #class возвращает истинный класс объекта. Есть какой-нибудь чистый рубиновый способ дифференциации?

Подумайте о сценарии, в котором я не знаю, существует ли FakeClass, или я не знаю, как называется FakeClass. Это означает, что я не могу редактировать FakeClass, чтобы добавить метод, подобный #is_fake?.

PS: я знаю, что a.instance_eval {self} возвращает объект (не подделка). Но это не помогает проверить, является ли a подделкой.

Ответы [ 6 ]

4 голосов
/ 27 марта 2011

Вот еще один ответ:

a = FakeClass.new(1)
b = 1
da = Marshal.dump(a)
db = Marshal.dump(b)
puts da == db            #=> false
1 голос
/ 27 марта 2011

Шутливый ответ: поменяй Fixnum и посмотри, торчит ли он. Полагаю, он довольно специфичен для этого метода проксирования, но он подойдет.

class Fixnum
  def foo
    return true
  end
end

a.foo # NoMethodError

Я должен превратить это в приятную функцию, но ленивый:)

0 голосов
/ 10 июня 2011

Мне было интересно то же самое.И если вы думаете, что это просто игра, попробуйте покопаться в ассоциациях Rails ActiveRecord (в частности: AssociationProxy ), и вы обнаружите, что происходит именно этот вид магии (страшный вид!).1004 * Вот один из способов, с которым я столкнулся, чтобы хотя бы обнаружить подобные вещи:

a = FakeClass.new(5)
Fixnum.instance_method(:class).bind(a)

бросков:

TypeError: bind argument must be an instance of Fixnum

Мне кажется, что должен быть лучший способ.

0 голосов
/ 27 марта 2011

Вы можете определить любой метод в FakeClass и проверить его с помощью response_to?

a = FakeClass.new(1)

a.respond_to?( 'is_fake_class?' )

например.

0 голосов
/ 27 марта 2011

Для этого FakeClass, я знаю о @object, и я попробую:

a = FakeClass.new(5)

class << a
  def fake?
    self.instance_variable_defined? :@object
  end
end

a.fake?  #=> true

О неизвестном FakeClass, я бы сравнил количество переменных экземпляра:

fake = (a.instance_variables.length > 1.instance_variables.length)
0 голосов
/ 27 марта 2011

У вас может быть FakeClass, реализующий #class и возвращающий FakeClass

, например:

class FakeClass
  #code
  def class
    FakeClass
  end
end

Чтобы увидеть, какой класс является объектом, который вы проксируете, просто позвоните

a.object.class #=> Fixnum
...