Книга: Metaprogramming Ruby 2
Назад: Quiz: Class Taboo
Дальше: Singleton Classes

Singleton Methods

Where it’s your turn to teach Bill a few tricks.

It’s late morning, and you and Bill are deep in the flow. You’re zipping through the Bookworm source, deleting a useless line here, changing a confusing name there, and generally polishing the code…until you bump into a particularly troublesome bit of refactoring.

The Paragraph class wraps a string and then delegates all calls to the wrapped string—all of them, that is, except for one method, Paragraph#title?, which returns true if a Paragraph is all uppercase.

 
class​ Paragraph
 
def​ initialize(text)
 
@text = text
 
end
 
 
def​ title?; @text.upcase == @text; ​end
 
def​ reverse; @text.reverse; ​end
 
def​ upcase; @text.upcase; ​end
 
# ...

Paragraph objects are created in a single place in the Bookworm source code. Also, Paragraph#title? is called only once in the whole application, from a method named index:

 
def​ index(paragraph)
 
add_to_index(paragraph) ​if​ paragraph.title?
 
end

Bill frowns. “The stupid Paragraph class really doesn’t hold its own weight. We could scrap it entirely and just use regular Strings, if it weren’t for the title? method.”

“Why don’t we Monkeypatch () the String class and add the title? method right there?” you offer. “I’m not convinced,” Bill says. “A method with that name would make sense only on strings that represent a paragraph, not on each and every string.”

While Bill is pondering the idea of patching the String class with a Refinement (), you decide to Google for a solution.

Introducing Singleton Methods

As it turns out, Ruby allows you to add a method to a single object. For example, here’s how you can add title? to a specific string:

 
str = ​"just a regular string"
 
 
def​ str.title?
 
self.upcase == self
 
end
 
 
str.title? ​# => false
 
str.methods.grep(/title?/) ​# => [:title?]
 
str.singleton_methods ​# => [:title?]

The previous code adds a method named title? to str. No other object gets the method—not even other Strings. A method like this one, which is specific to a single object, is called a Spell: . You can define a Singleton Method with either the syntax above or the Object#define_singleton_method method.

Thanks to Singleton Methods, you can now fix your problem with the Bookworm source. You can send any old String to index if you enhance that String with a title? Singleton Method:

 
paragraph = ​"any string can be a paragraph"
 
 
def​ paragraph.title?
 
self.upcase == self
 
end
 
 
index(paragraph)

Now you can use plain strings in Bookworm and delete the Paragraph class.

Bill is awestruck by your solution. “I knew about Singleton Methods, but I never realized you could use them this way.”

“Wait a minute,” you reply. “You knew about them? What did you think they were useful for?”

“Singleton Methods aren’t just useful for enhancing a specific object, like you just did.” Bill replies. “They’re also the basis for one of Ruby’s most common features. What if I told you that you’ve been using Singleton Methods all along, without ever knowing it?”

The Truth About Class Methods

Remember what you learned in ? Classes are just objects, and class names are just constants. If you remember this concept, then you’ll see that calling a method on a class is the same as calling a method on an object:

 
an_object.a_method
 
AClass.a_class_method

See? The first line calls a method on an object referenced by a variable, and the second line calls a method on an object (that also happens to be a class) referenced by a constant. It’s the same syntax.

But, wait—there’s more. Remember how Bill told you that you’ve been using Singleton Methods () all along? That’s really what class methods are: they’re Singleton Methods of a class. In fact, if you compare the definition of a Singleton Method and the definition of a class method, you’ll see that they’re the same:

 
def​ obj.a_singleton_method; ​end
 
def​ MyClass.another_class_method; ​end

So, the syntax for defining a Singleton Method with def is always the same:

 
def​ object.method
 
# Method body here
 
end

In the definition shown previously, object can be an object reference, a constant class name, or self. The syntax might look different in the three cases, but in truth the underlying mechanism is always the same. Nice design, don’t you think?

You’re not quite finished with class methods yet. There’s a very useful and common spell that relies on class methods exclusively, and it deserves its own discussion.

Class Macros

Look at this example, coming straight from the core of Ruby.

The attr_accessor() Example

Ruby objects don’t have attributes. If you want something that looks like an attribute, you have to define two Mimic Methods (), a reader and a writer:

 
class​ MyClass
 
def​ my_attribute=(value)
 
@my_attribute = value
 
end
 
 
 
def​ my_attribute
 
@my_attribute
 
end
 
end
 
 
obj = MyClass.new
 
obj.my_attribute = ​'x'
 
obj.my_attribute ​# => "x"

Writing methods like these (also called accessors) gets boring quickly. As an alternative, you can generate accessors by using one of the methods in the Module#attr_* family. Module#attr_reader generates the reader, Module#attr_writer generates the writer, and Module#attr_accessor generates both:

 
class​ MyClass
 
attr_accessor :my_attribute
 
end

All the attr_* methods are defined on class Module, so you can use them whenever self is a module or a class. A method such as attr_accessor is called a Spell: . Class Macros look like keywords, but they’re just regular class methods that are meant to be used in a class definition.

“Now that you know about Class Macros,” Bill says, “I think I know a place in Bookworm’s source code where we can make good use of them.”

Class Macros Applied

The Book class in the Bookworm source code has methods named GetTitle, title2, and LEND_TO_USER. By Ruby’s conventions, these methods should be named title, subtitle, and lend_to, respectively. However, there are other projects that use the Book class, and you have no control over these projects. If you just rename the methods, you will break the callers.

Bill has an idea to fix this situation: you can rename the methods if you invent a Class Macro () that deprecates the old names:

 
class​ Book
 
def​ title ​# ...
 
 
def​ subtitle ​# ...
 
 
def​ lend_to(user)
 
puts ​"Lending to ​#{user}​"
 
# ...
 
end
 
 
 
def​ self.deprecate(old_method, new_method)
 
define_method(old_method) ​do​ |*args, &block|
 
warn ​"Warning: ​#{old_method}​() is deprecated. Use ​#{new_method}​()."
 
send(new_method, *args, &block)
 
end
 
end
 
 
deprecate :GetTitle, :title
 
deprecate :LEND_TO_USER, :lend_to
 
deprecate :title2, :subtitle
 
end

The deprecate method takes the old name and the new name of a method and defines a Dynamic Method () that catches calls to the old name. The Dynamic Method forwards the calls to the renamed method—but first it prints a warning on the console to notify the callers that the old name has been deprecated:

 
b = Book.new
 
b.LEND_TO_USER(​"Bill"​)
<= 
Warning: LEND_TO_USER() is deprecated. Use lend_to().
 
Lending to Bill

That was an ingenious way to use a Class Macro. However, if you really want to understand Class Macros, as well as Singleton Methods in general, you have to fit one last missing piece in the Ruby object model.

Назад: Quiz: Class Taboo
Дальше: Singleton Classes