Есть ли способ создать методы только для экземпляра класса Ruby из этого экземпляра? - PullRequest
44 голосов
/ 12 июня 2010

Пусть будет class Example, определенное как:

class Example
  def initialize(test='hey')
    self.class.send(:define_method, :say_hello, lambda { test })
  end
end

При звонке Example.new; Example.new я получаю warning: method redefined; discarding old say_hello. Я пришел к выводу, что это должно быть потому, что он определяет метод в реальном классе (что имеет смысл из синтаксиса) И это, конечно, окажется катастрофическим, если будет несколько экземпляров Example с различными значениями в их методах.

Есть ли способ создать методы только для экземпляра класса внутри этого экземпляра?

Ответы [ 3 ]

69 голосов
/ 12 июня 2010

Вам нужно получить ссылку на одноэлементный класс экземпляра, класс, который содержит все специфичные для экземпляра вещи, и определить метод для него. В ruby ​​1.8 это выглядит немного грязно. (если вы найдете более чистое решение, дайте мне знать!)

Рубин 1,8

class Example
  def initialize(test='hey')
    singleton = class << self; self end
    singleton.send :define_method, :say_hello, lambda { test }
  end
end

Ruby 1.9, однако, обеспечивает гораздо более легкий путь.

Рубин 1,9

class Example
  def initialize(test='hey')
    define_singleton_method :say_hello, lambda { test }
  end
end
39 голосов
/ 12 июня 2010

Прежде всего, небольшой совет по стилю:

self.class.send(:define_method, :say_hello, lambda { test })

Вы можете сделать это немного лучше, используя новый литерал proc в Ruby 1.9:

self.class.send(:define_method, :say_hello, -> { test })

Но тебе это не нужно. В Ruby есть нечто, называемое blocks , которое в основном представляет собой фрагмент кода, который вы можете передать в качестве аргумента методу. Фактически, вы уже использовали блоки, поскольку lambda - это просто метод, который принимает блок в качестве аргумента и возвращает Proc. Однако, define_method уже все равно принимает блок, нет необходимости передавать блок в lambda, который преобразует его в Proc, который он передает в define_method, который затем преобразует его обратно в блок:

self.class.send(:define_method, :say_hello) { test }

Как вы уже заметили, вы определяете метод не для того класса. Вы определяете его в классе Example, поскольку внутри метода экземпляра, например initialize, self является текущим объектом (т. Е. ex1 или ex2 в примере @ mikej), что означает, что self.class Класс ex1, который Example. Итак, вы перезаписываете один и тот же метод снова и снова.

Это приводит к следующему нежелательному поведению:

ex1 = Example.new('ex1')
ex2 = Example.new('ex2') # warning: method redefined; discarding old say_hello
ex1.say_hello            # => ex2 # Huh?!?

Вместо этого, если вам нужен одноэлементный метод, вам нужно определить его для одноэлементного класса:

(class << self; self end).send(:define_method, :say_hello) { test }

Работает как задумано:

ex1 = Example.new('ex1')
ex2 = Example.new('ex2')
ex1.say_hello            # => ex1
ex2.say_hello            # => ex2

В Ruby 1.9 есть метод, который делает это:

define_singleton_method(:say_hello) { test }

Теперь, это работает так, как вы хотите, но здесь есть проблема более высокого уровня: это не код Ruby. Это Ruby синтаксис , но это не код Ruby, это схема.

Теперь, Scheme - это блестящий язык, и написание кода на Scheme в синтаксисе Ruby, безусловно, неплохая вещь. Он превосходно пишет код Java или PHP с синтаксисом Ruby или, как было вчера в вопросе StackOverflow, код Fortran-57 с синтаксисом Ruby. Но это не так хорошо, как писать Ruby код в синтаксисе Ruby.

Схема - это функциональный язык. Функциональные языки используют функции (точнее, замыкания функций) для инкапсуляции и состояния. Но Ruby не является функциональным языком, это объектно-ориентированный язык, и языки OO используют objects для инкапсуляции и состояния.

Итак, замыкания функций становятся объектами, а захваченные переменные становятся переменными экземпляра.

Мы также можем прийти к этому с совершенно другой точки зрения: то, что вы делаете, это то, что вы определяете singleton метод, который является методом, целью которого является определение поведения, характерного для один объект. Но вы определяете этот одноэлементный метод для каждого экземпляра класса, и вы определяете одинаковый одноэлементный метод для каждого экземпляра класса. У нас уже есть механизм определения поведения для каждого экземпляра класса: методы экземпляра.

Оба эти аргумента исходят из совершенно противоположных направлений, но они достигают одного и того же пункта назначения:

class Example
  def initialize(test='hey')
    @test = test
  end

  def say_hello
    @test
  end
end
8 голосов
/ 18 августа 2015

Я знаю, что об этом спросили два года назад, но я хотел бы добавить еще один ответ..instance_eval поможет добавить методы к объекту экземпляра

    string = "String"
    string.instance_eval do
      def new_method
        self.reverse
      end
    end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...