Let there be class Example
defined as:
class Example
def initialize(test='hey')
self.class.send(:define_method, :say_hello, lambda { test })
end
end
On calling Example.new; Example.new
I get a warning: method redefi开发者_JAVA技巧ned; discarding old say_hello
. This, I conclude, must be because it defines a method in the actual class (which makes sense, from the syntax). And that, of course, would prove disastrous should there be multiple instances of Example
with different values in their methods.
Is there a way to create methods just for the instance of a class from inside that instance?
You need to grab a reference to the instance's singleton class, the class that holds all the instance specific stuff, and define the method on it. In ruby 1.8, it looks a little messy. (if you find a cleaner solution let me know!)
Ruby 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 however, provides a much easier way in.
Ruby 1.9
class Example
def initialize(test='hey')
define_singleton_method :say_hello, lambda { test }
end
end
First off, a small style tip:
self.class.send(:define_method, :say_hello, lambda { test })
You can make this look a little bit nicer by using the new proc literal in Ruby 1.9:
self.class.send(:define_method, :say_hello, -> { test })
But you don't need that. Ruby has something called blocks, which are basically a piece of code that you can pass as an argument to a method. In fact, you already used blocks, since lambda
is just a method which takes a block as an argument and returns a Proc
. However, define_method
already takes a block anyway, there is no need to pass a block to lambda
which converts it to a Proc
which it passes to define_method
which then converts it back into a block:
self.class.send(:define_method, :say_hello) { test }
As you already noticed, you are defining the method on the wrong class. You are defining it on the Example
class, since inside an instance method like initialize
, self
is the current object (i.e. ex1
or ex2
in @mikej's example), which means that self.class
is ex1
's class, which is Example
. So, you are overwriting the same method over and over again.
This leads to the following unwanted behavior:
ex1 = Example.new('ex1')
ex2 = Example.new('ex2') # warning: method redefined; discarding old say_hello
ex1.say_hello # => ex2 # Huh?!?
Instead, if you want a singleton method, you need to define it on the singleton class:
(class << self; self end).send(:define_method, :say_hello) { test }
This works as intended:
ex1 = Example.new('ex1')
ex2 = Example.new('ex2')
ex1.say_hello # => ex1
ex2.say_hello # => ex2
In Ruby 1.9, there's a method that does that:
define_singleton_method(:say_hello) { test }
Now, this works the way you want it to, but there's a higher-level problem here: this is not Ruby code. It is Ruby syntax, but it's not Ruby code, it's Scheme.
Now, Scheme is a brilliant language and writing Scheme code in Ruby syntax is certainly not a bad thing to do. It beats the hell out of writing Java or PHP code in Ruby syntax, or, as was the case in a StackOverflow question yesterday, Fortran-57 code in Ruby syntax. But it's not as good as writing Ruby code in Ruby syntax.
Scheme is a functional language. Functional languages use functions (more precisely, function closures) for encapsulation and state. But Ruby is not a functional language, it is an object-oriented language and OO languages use objects for encapsulation and state.
So, function closures become objects and captured variables become instance variables.
We can also come at this from a completely different angle: what you are doing is that you are defining a singleton method, which is a method whose purpose it is to define behavior which is specific to one object. But you are defining that singleton method for every instance of the class, and you are defining the same singleton method for every instance of the class. We already have a mechanism for defining behavior for every instance of a class: instance methods.
Both of these arguments come from completely opposite directions, but they arrive at the same destination:
class Example
def initialize(test='hey')
@test = test
end
def say_hello
@test
end
end
I know it was asked two years back, but I would like to add one more answer. .instance_eval
will help to add methods to instance object
string = "String"
string.instance_eval do
def new_method
self.reverse
end
end
Define instance method from outside:
example = Example.new
def example.say_hello
puts 'hello'
end
From inside:
class Example
def initialize(word='hey')
@word = word
def self.say_hello
puts "example: #{@word}"
end
end
end
Tested on ruby 2.5
精彩评论