Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (5.14 MB, 519 trang )
Crafting an object: the behavior of a ticket
39
def ticket.venue
"Town Hall"
end
def ticket.performer
"Mark Twain"
end
def ticket.event
"Author's reading"
end
def ticket.price
5.50
end
B
def ticket.seat
"Second Balcony, row J, seat 12"
end
def ticket.date
"01/02/03"
end
The majority of the methods defined here return string values. You can see this at a
glance: they hand back a value inside quotation marks. The price method B returns
a floating-point decimal number: 5.50.
The joy (or not) of floats
Floating-point numbers have more complexity and quirkiness than you may think.
Someday you’ll probably come across something similar to this frequently cited
example:
puts 0.5 - 0.4 - 0.1
-2.77555756156289e-17
Not zero!
The problem—or, more accurately, the inevitable consequence of the laws of mathematics and computers—is that decimal floating-point numbers of arbitrary length
can’t be stored and operated on in binary form with complete accuracy. Don’t be surprised if your “floats” don’t act as integer-like as you might wish or expect.
Now that the ticket object knows a little about itself, let’s ask it to share the information.
2.2.2
Querying the ticket object
Rather than produce a raw list of items, let’s generate a reader-friendly summary of
the details of the ticket. The use of print and puts can help get the information into
more or less narrative form:
print "This ticket is for: "
print ticket.event + ", at "
print ticket.venue + ", on "
Licensed to sam kaplan
40
CHAPTER 2
Objects, methods, and local variables
puts ticket.date + "."
print "The performer is "
puts ticket.performer + "."
print "The seat is "
print ticket.seat + ", "
print "and it costs $"
puts "%.2f." % ticket.price
Print floating-point number
to two decimal places
Save all the code, starting with ticket = Object.new, to a file called ticket.rb, and run
it. You’ll see the following:
This ticket is for: Author's reading, at Town Hall, on 01/02/03.
The performer is Mark Twain.
The seat is Second Balcony, row J, seat 12, and it costs $5.50.
The code for this example consists of a series of calls to the methods defined earlier:
ticket.event, ticket.venue, and so forth. The printing code embeds those calls—in
other words, embeds the return values of those methods (“Author’s reading”, “Town
Hall”, and so on)—in a succession of output commands, and adds connectors (“, at”,
“, on”, and so forth) to make the text read well and look nice.
The Twain ticket is a simple example, but it encompasses some vital Ruby procedures and principles. The most important lesson is that the knowledge necessary for
the program to do anything useful resides in the object. The ticket object has the knowledge; you tap into that knowledge by asking the ticket for it, via method calls. Nothing
is more central to Ruby programming than this. It’s all about asking objects to do
things and tell you things.
The ticket code works, and it embodies useful lessons; but it’s wordy. Ruby has a
reputation as a powerful, high-level language. You’re supposed to be able to get a lot
done with relatively little code. But the ticket example takes nine lines of print and
puts instructions to generate three lines of output.
Let’s improve that ratio a bit.
2.2.3
Shortening the ticket code via string interpolation
The goal of shortening the output of this little program gives us an excuse to dip into
one of the most useful programming techniques available in Ruby: string interpolation.
The string-interpolation operator gives you a way to drop variables, method-return values, or anything else into a string. This can save you a lot of back-and-forth between
print and puts.
Moreover, strings can be concatenated with the plus sign (+). Here’s how the printing code looks, using string interpolation to insert the values of expressions into the
string and using string “addition” to consolidate multiple puts calls into one:
puts "This ticket is for: #{ticket.event}, at #{ticket.venue}." +
"The performer is #{ticket.performer}." +
"The seat is #{ticket.seat}, " +
"and it costs $#{"%.2f." % ticket.price}"
Whatever’s inside the interpolation operator #{...} gets calculated separately, and
the results of the calculation are inserted into the string. When you run these lines,
Licensed to sam kaplan
Crafting an object: the behavior of a ticket
41
you won’t see the #{...} operator on your screen; instead, you’ll see the results of calculating or evaluating what was between the curly braces. Interpolation helped eliminate six of nine lines of code and also made the code look a lot more like the eventual
format of the output, rather than something that works but doesn’t convey much
visual information.
So far, we’ve been asking the ticket for information in the form of strings and numbers. Tickets also have some true/false—boolean—information about themselves.
2.2.4
Ticket availability: expressing boolean state in a method
By way of boolean information, consider the matter of whether a ticket has been sold
or is still available. One way to endow a ticket with knowledge of its own availability status is this:
def ticket.availability_status
"sold"
end
Another way is to ask the ticket whether it’s available and have it report back true or
false:
def ticket.available?
false
end
false is a special term in Ruby, as is the term true. true and false are objects.
Ruby uses them to represent results of, among other things, comparison operations
(like x > y), and you can use them to represent truth and falsehood in your own
methods. You may have noticed that the method name available? ends with a question mark. Ruby lets you do this so you can write methods that evaluate to true or
false and make the method calls look like questions:
if ticket.available?
puts "You're in luck!"
else
puts "Sorry--that seat has been sold."
end
But there’s more to truth and falsehood than the true and false objects. Every
expression in Ruby evaluates to an object; and every object in Ruby has a truth value.
The truth value of almost every object in Ruby is true. The only objects whose truth
value (or boolean value) is false are the objects false and the special non-entity
object nil. You’ll see booleans and nil in more detail in chapter 7. For the moment,
you can think of both false and nil as functionally equivalent indicators of a negative
test outcome.
Playing around with if expressions in irb is a good way to get a feel for how conditional logic plays out in Ruby. Try some examples like these:
>> if "abc"
>> puts "Strings are 'true' in Ruby!"
>> end
Licensed to sam kaplan
42
CHAPTER 2
Objects, methods, and local variables
Strings are 'true' in Ruby!
=> nil
>> if 123
>> puts "So are numbers!"
>> end
So are numbers!
=> nil
>> if 0
>> puts "Even 0 is true, which it isn't in some languages."
>> end
Even 0 is true, which it isn't in some languages.
=> nil
>> if 1 == 2
>> puts "One doesn't equal two, so this won't appear."
>> end
=> nil
(The indentation in this irb session isn’t mandatory, but indenting the bodies of if
statements, as you would in a program file, makes even an irb transcript easier to follow.)
Notice how irb not only obeys the puts method calls (when conditions are right)
but also, on its own initiative, outputs the value of the entire expression. In the cases
where the puts happens, the whole expression evaluates to nil—because the return
value of puts is always nil. In the last case, where the string isn’t printed (because the
condition fails), the value of the expression is also nil—because an if statement that
fails (and has no else branch to salvage it) also evaluates to nil.
Remembering that nil has a boolean value of false, you can, if you wish, get into
acrobatics with irb. A call to puts returns nil and is therefore false, even though the
string gets printed. If you put puts in an if clause, the clause will be false. But it will
still be evaluated. So…
>> if puts "You'll see this"; puts "but not this"; end
You'll see this
=> nil
The first puts is executed, but the value it returns, namely nil, isn’t true in the boolean sense—so the second puts isn’t executed.
This is, to use the popular phrase, a contrived example. But it’s a good idea to get
used to the fact that everything in Ruby has a boolean value, and sometimes it’s not
what you might expect. As is often the case, irb can be a great help in getting a handle
on this concept.
Now that the ticket object has some hand-crafted behaviors, let’s circle back
and consider the matter of what behaviors every object in Ruby is endowed with at
its creation.
2.3
The innate behaviors of an object
Even a newly created object isn’t a blank slate. As soon as an object comes into existence, it responds to a number of messages. Every object is “born” with certain innate
abilities.
Licensed to sam kaplan
The innate behaviors of an object
43
To see a list of innate methods, you can call the methods method (and throw in a
sort operation, to make it easier to browse visually):
p Object.new.methods.sort
The result is a list of all the messages (methods) this newly minted object comes bundled with. (Warning: the output looks cluttered. This is how Ruby displays arrays—
and the methods method gives you an array of method names. If you want a list of the
methods one per line, use puts instead of p in the command.)
[:!, :!=, :!~, :==, :===, :=~, :__id__, :__send__, :class, :clone,
:define_singleton_method, :display, :dup, :enum_for, :eql?, :equal?,
:extend, :freeze, :frozen?, :gem, :hash, :inspect, :instance_eval,
:instance_exec, :instance_of?, :instance_variable_defined?,
:instance_variable_get, :instance_variable_set, :instance_variables,
:is_a?, :kind_of?, :method, :methods, :nil?, :object_id,
:private_methods, :protected_methods, :public_method, :public_methods,
:public_send, :respond_to?, :send, :singleton_methods, :taint,
:tainted?, :tap, :to_enum, :to_s, :trust, :untaint, :untrust,
:untrusted?]
Don’t worry if most of these methods make no sense to you right now. You can try
them in irb, if you’re curious to see what they do (and if you’re not afraid of getting
some error messages).
But a few of these innate methods are common enough—and helpful enough,
even in the early phases of acquaintance with Ruby—that we’ll look at them in detail
here. The following methods fit this description:
■
■
■
object_id
respond_to?
send (synonym: __send__)
Adding these to your Ruby toolbox won’t be amiss, on account of what they do and
because they serve as examples of innate methods.
Generic objects vs. basic objects
Asking Ruby to create a new object for you, with the Object.new command, produces
what we’re calling here, informally, a generic object. Ruby also has basic objects—and
that’s a more formal name. If you call BasicObject.new, you get a kind of proto-object that can do very little. You can’t even ask a basic object to show you its methods,
because it has no methods method! In fact, it has only seven methods—enough for
the object to exist and be identifiable, and not much more. You’ll learn more about
these basic objects in chapters 3 and 13.
2.3.1
Identifying objects uniquely with the object_id method
Every object in Ruby has a unique id number associated with it. You can see an object’s
id by asking the object to show you its object_id, using this or similar code:
Licensed to sam kaplan
44
CHAPTER 2
Objects, methods, and local variables
obj = Object.new
puts "The id of obj is #{obj.object_id}."
str = "Strings are objects too, and this is a string!"
puts "The id of the string object str is #{str.object_id}."
puts "And the id of the integer 100 is #{100.object_id}."
Having a unique id number for every object can come in handy when you’re trying to
determine whether two objects are the same as each other. How can two objects be the
same? Well, the integer object 100 is the same as … the integer object 100. (Ask 100
for its object id twice; the result will be the same.) And here’s another case:
a = Object.new
b = a
puts "a's id is #{a.object_id} and b's id is #{b.object_id}."
Even though the variables a and b are different, the object they both refer to is the
same. (See section 2.6.1 for more on the concept of object references.) The opposite
scenario can happen too: sometimes two objects appear to be the same, but they’re
not. This happens a lot with strings. Consider the following example:
string_1 = "Hello"
string_2 = "Hello"
puts "string_1's id is #{string_1.object_id}."
puts "string_2's id is #{string_2.object_id}."
string_1 id: 287090
string_2 id: 279110
Even though these two strings contain the same text, they aren’t, technically, the same
object. If you printed them out, you’d see the same result both times (“Hello”). But
the string objects themselves are different. It’s like having two copies of the same
book: they contain the same text, but they aren’t the same thing as each other. You
could destroy one, and the other would be unaffected.
ID NUMBERS AND EQUALITY OF OBJECTS
As in the case of human institutions, one of the points of giving objects id numbers in
Ruby is to be able to make unique identifications—and, in particular, to be able to
determine when two objects are the same object.
Ruby provides a variety of ways to compare objects for different types of equality. If
you have two strings, you can test to see whether they contain the same characters. You
can also test to see whether they’re the same object (which, as you’ve just seen, isn’t
necessarily the case, even if they contain the same characters). The same holds true,
with slight variations, for other objects and other types of objects.
Comparing id numbers for equality is just one way of measuring object equality.
We’ll get into more detail about these comparisons a little later. Right now, we’ll turn
to the next innate method on our list: respond_to?.
2.3.2
Querying an object’s abilities with the respond_to? method
Ruby objects respond to messages. At different times during a program run, depending on the object and what sorts of methods have been defined for it, an object may
or may not respond to a given message. For example, the following code results in
an error:
Licensed to sam kaplan
The innate behaviors of an object
45
obj = Object.new
obj.talk
Ruby is only too glad to notify you of the problem:
NoMethodError: undefined method 'talk' for #
You can determine in advance (before you ask the object to do something) whether the
object knows how to handle the message you want to send it, by using the respond_to?
method. This method exists for all objects; you can ask any object whether it responds
to any message.
respond_to? usually appears in connection with conditional (if) logic:
obj = Object.new
if obj.respond_to?("talk")
obj.talk
else
puts "Sorry, the object doesn't understand the 'talk' message."
end
respond_to? is an example of introspection or reflection, two terms that refer to examin-
ing the state of a program while it’s running. Ruby offers a number of facilities for
introspection. Examining an object’s methods with the methods method, as we did
earlier, is another introspective or reflective technique. (You’ll see many more such
techniques in part 3 of the book.)
Up to now, we’ve been using the dot operator (.) to send messages to objects.
Nothing wrong with that. But what if you don’t know which message you want to send?
2.3.3
Sending messages to objects with the send method
Suppose you want to let a user get information from the ticket object by entering an
appropriate query term (venue, performer, and so on) at the keyboard. Here’s what
you’d add to the existing program:
print "Information desired: "
request = gets.chomp
The second line of code gets a line of keyboard input, “chomps” off the trailing newline character, and saves the resulting string in the variable request.
At this point, you could test the input for one value after another by using the double equal sign comparison operator (==), which compares strings based on their content, and calling the method whose value provides a match:
if request == "venue"
puts ticket.venue
elsif request == "performer"
puts ticket.performer
...
To be thorough, though, you’d have to continue through the whole list of ticket properties. That’s going to get lengthy.
Licensed to sam kaplan
46
CHAPTER 2
Objects, methods, and local variables
There’s an alternative: you can send the word directly to the ticket object. Instead
of the previous code, you’d do the following:
B
if ticket.respond_to?(request)
puts ticket.send(request)
else
puts "No such information available"
end
This version uses the send method as an all-purpose way of getting a message to the
ticket object. It relieves you of having to march through the whole list of possible
requests. Instead, having checked that the ticket object knows what to do B, you
hand the ticket the message and let it do its thing.
Using __send__ or public_send instead of send
Sending is a broad concept: email is sent, data gets sent to I/O sockets, and so
forth. It’s not uncommon for programs to define a method called send that conflicts
with Ruby’s built-in send method. Therefore, Ruby gives you an alternative way to call
send: __send__. By convention, no one ever writes a method with that name, so the
built-in Ruby version is always available and never comes into conflict with newly written methods. It looks strange, but it’s safer than the plain send version from the
point of view of method-name clashes.
In addition, there’s a safe—but in a different way—version of send (or __send__)
called public_send. The difference between plain send and public_send is that
plain send can call an object’s private methods, and public_send can’t. We don’t
cover private methods until later in the book, but in case you’re curious what
public_send was doing in the method list, that’s the gist.
Most of the time, you’ll use the dot operator to send messages to objects. But the send
alternative can be useful and powerful. It’s powerful enough, and error-prone
enough, that you normally wouldn’t use it to send messages to objects with no safety
checks or error handling, as in this example; but the example demonstrates the basic
mechanism of the explicit send operation.
Next, we’ll put method argument syntax and semantics under the microscope.
2.4
A close look at method arguments
Methods you write in Ruby can take more than one argument, or none at all. They
can also allow a variable number of arguments. We’ll look at several permutations
here, and you can see them summarized in table 2.2 at the end of this section.
We’ll start examining argument semantics by looking at the difference between
required and optional arguments. We’ll also look at how to assign default values to
arguments, and the rules governing the order in which you have to arrange the
parameters in the method signature so that Ruby can make sense of argument lists in
method calls and bind the parameters correctly.
Licensed to sam kaplan
A close look at method arguments
2.4.1
47
Required and optional arguments
When you call a Ruby method, you have to supply the correct number of arguments.
If you don’t, Ruby tells you there’s a problem. For example, calling a one-argument
method with three arguments
def obj.one_arg(x)
puts "I require one and only one argument!"
end
obj.one_arg(1,2,3)
results in
ArgumentError: wrong number of arguments (3 for 1)
It’s possible to write a method that allows any number of arguments. To do this, you
put a star (an asterisk: *) in front of a single argument name:
def obj.multi_args(*x)
puts "I can take zero or more arguments!"
end
The *x notation means that when you call the method, you can supply any number of
arguments (or none). In this case, the variable x is assigned an array of values corresponding to whatever arguments were sent. You can then examine the values one at a
time by traversing the array. (We’ll look more closely at arrays in chapter 9.)
You can fine-tune the number of arguments by mixing required and optional arguments:
def two_or_more(a,b,*c)
puts "I require two or more arguments!"
puts "And sure enough, I got: "
p a, b, c
end
In this example, a and b are required arguments. The final *c will sponge up any
other arguments that you may send and put them into an array in the variable c. If you
call two_or_more(1,2,3,4,5), you’ll get the following report of what got assigned to
a, b, and c:
I require two or more arguments!
And sure enough, I got:
1
2
[3, 4, 5]
(Using p rather than print or puts results in the array being printed out in array
notation. Otherwise, each array element would appear on a separate line, making it
harder to see that an array is involved at all.)
You can also make an argument optional by giving it a default value.
Licensed to sam kaplan
48
2.4.2
CHAPTER 2
Objects, methods, and local variables
Default values for arguments
When you supply a default value for an argument, the result is that if that argument
isn’t supplied, the variable corresponding to the argument receives the default value.
Default arguments are indicated with an equal sign and a value. Here’s an example:
def default_args(a,b,c=1)
puts "Values of variables: ",a,b,c
end
If you make a call this like this
default_args(3,2)
you’ll see this result:
Values of variables:
3
2
1
No value was supplied in the method call for c, so c was set to the default value provided for it in the parameter list: 1. If you do supply a third argument, that value overrides the default assignment of 1. The following call
default_args(4,5,6)
produces this result:
Values of variables:
4
5
6
The real fun starts when you mix and match the different elements of argument syntax and have to figure out what order to put everything in.
2.4.3
Order of parameters and arguments
What output would you expect from this code snippet?
def mixed_args(a,b,*c,d)
puts "Arguments:"
puts a,b,c,d
end
mixed_args(1,2,3,4,5)
You’ve seen that a starred parameter, like *c, sponges up the remaining arguments—
at least, it did so in the method two_or_more, where *c occurred last in the parameter
list. What happens when another argument follows it?
Basically, Ruby tries to assign values to as many variables as possible. And the
sponge parameters get the lowest priority: if the method runs out of arguments after
it’s performed the assignments of required arguments, then a catch-all parameter like
*c ends up as an empty array. The required arguments both before *c and after *c get
taken care of before *c does.
Licensed to sam kaplan
A close look at method arguments
49
The output of the previous snippet is this:
Arguments:
1
2
[3, 4]
5
The parameters a and b get the first two arguments, 1 and 2. Because the parameter at
the end of the list, d, represents a required argument, it grabs the first available value
from the right-hand end of the argument list—namely, 5. Whatever’s left in the middle (3, 4) gets sponged up by c.
If you only give enough arguments to match the required arguments of the
method, then the sponge array will be empty. This method call
mixed_args(1,2,3)
results in this output:
1
2
[]
3
In this example, c is out of luck; there’s nothing left.
You can get reasonably fancy with parameter syntax. Here’s a method that takes a
required argument; an optional argument that defaults to 1; two more required arguments taken from the right; and, somewhere in the middle, everything else:
def args_unleashed(a,b=1,*c,d,e)
puts "Arguments:"
p a,b,c,d,e
end
And here’s an irb session that puts this method through its paces. Note that the return
value of the method call, in every case, is an array consisting of all the values. That’s
the return value of the call to p. It’s an array representation of the same values that
you see printed out as individual values on separate lines:
>> args_unleashed(1,2,3,4,5)
1
2
[3]
4
5
=> [1, 2, [3], 4, 5]
>> args_unleashed(1,2,3,4)
1
2
[]
3
4
=> [1, 2, [], 3, 4]
>> args_unleashed(1,2,3)
Licensed to sam kaplan