1. Trang chủ >
  2. Công Nghệ Thông Tin >
  3. Kỹ thuật lập trình >

5 What you can’t do in argument lists

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 )


52



CHAPTER 2



Objects, methods, and local variables



As you can see from table 2.2, the arguments you send to methods are assigned to variables—specifically, local variables, visible and usable for the duration of the method.

Assignment of local variables through method argument binding is just one case of the

general process of local variable assignment, a process that we’ll look at in detail next.



2.6



Local variables and variable assignment

Local variable names start with a lowercase letter or an underscore and are made up

of alphanumeric characters and underscores. All of these are valid local variable

names, including the lone underscore:

x

_x

name

first_name

plan9

user_ID

_



The local in local variables pertains to the fact that they have limited scope: a local variable is only visible in a limited part of a program, such as a method definition. Local

variable names can be reused in different scopes. You can use, say, the variable name x

in more than one place, and as long as those places have different scopes, the two x

variables are treated as completely separate. (Remember that conventional Ruby style

prefers under_score names over camelCase names for local variables.)

Scope is an important topic in its own right, and we’ll get deeply into it in

chapter 5. You can start getting familiar with some key aspects of it now, though, as

you examine how local variables come and go. The classic case of local scope is a

method definition. Watch what happens with x in this example:

def say_goodbye

x = "Goodbye"

puts x

end



B



def start_here

x = "Hello"

puts x

say_goodbye

puts "Let's check whether x remained the same:"

puts x

end



C

D



E



start_here



F



The output from this program is as follows:

Hello

Goodbye

Let's check whether x remained the same:

Hello



Licensed to sam kaplan



Local variables and variable assignment



53



When you call start_here F, the method start_here is executed. Inside that

method, the string “Hello” is assigned to x C—that is, to this x, the x in scope inside

the method.

start_here prints out its x (“Hello”) and then calls the method say_goodbye D.

In say_goodbye, something similar happens: a string (“Goodbye”) is assigned to x B.

But this is a different x—as you see when the call to say_goodbye is finished and control returns to start_here: Ruby prints out this x, and the value is still “Hello” E.

Using x as a local variable name in the scope of one method didn’t affect its value in

the scope of the other.

The local variables in this last example are created through explicit assignment.

(Local variables can come into being, as you’ve seen, through the binding of method

arguments to method parameters.) But what happens when the assignment or initialization takes place? What exactly is the relation between a variable and the object that

it represents?



2.6.1



Variables, objects, and references

Variable assignments give the appearance, and have the apparent effect, of causing

the variable on the left to be set equal to the object on the right. After this assignment,

for example:

str = "Hello"



statements like puts str will deliver the string “Hello” for printing and processing.

Now, look at this example:

str = "Hello"

abc = str

puts abc



This, too, prints “Hello”. Apparently the variable abc also contains “Hello”, thanks to

having had str assigned to it.

But there’s more to it. The next example involves a method called replace, which

does an in-place replacement of a string’s content with new text:

str = "Hello"

abc = str

str.replace("Goodbye")

puts str

puts abc



Look closely at the output:

Goodbye

Goodbye



The first “Goodbye” is str; the second is abc. But we only replaced str. How did the

string in abc get replaced?



Licensed to sam kaplan



54



CHAPTER 2



Objects, methods, and local variables



ENTER REFERENCES



The answer is that variables in Ruby (with a few exceptions, most notably variables

bound to integers) don’t hold object values. str doesn’t contain “Hello”. Rather, str

contains a reference to a string object. It’s the string object that has the characteristic of

containing the letters that make up “Hello”.

In an assignment with a variable name on the left and an object on the right, the

variable receives a reference to the object. In an assignment from one variable to

another (abc = str), the variable on the left receives a copy of the reference stored in

the variable on the right, with the result that both variables now contain references to

the same object.

The fact that variables hold references to objects has implications for operations

that change objects. The string-replace operation

str.replace("Goodbye")



replaces the characters of the string to which str is a reference with the text “Goodbye”. The variable abc contains another reference to the same string object. Even

though the replace message goes to str, it causes a change to the object to which the

reference in abc refers. When you print out abc, you see the result: the contents of the

string have changed.



The un-reference: immediate values

Some objects in Ruby are stored in variables as immediate values. These include integers, symbols (which look like :this), and the special objects true, false, and

nil. When you assign one of these values to a variable (x = 1), the variable holds

the value itself, rather than a reference to it.

In practical terms, this doesn’t matter (and it will often be left as implied, rather than

spelled out repeatedly, in discussions of references and related topics in this book).

Ruby handles the dereferencing of object references automatically; you don’t have to

do any extra work to send a message to an object that contains, say, a reference to

a string, as opposed to an object that contains an immediate integer value.

But the immediate-value representation rule has a couple of interesting ramifications, especially when it comes to integers. For one thing, any object that’s represented as an immediate value is always exactly the same object, no matter how many

variables it’s assigned to. There’s only one object 100, only one object false, and

so forth.

The immediate, unique nature of integer-bound variables is behind Ruby’s lack of preand post-increment operators—which is to say, you can’t do this in Ruby:

x = 1

x++

# No such operator



The reason is that, due to the immediate presence of 1 in x, x++ would be like 1++,

which means you’d be changing the number 1 to the number 2—and that makes no

sense. (We’ll return to the topic of the absence of these operators in Ruby in the sidebar in section 7.2.)



Licensed to sam kaplan



Local variables and variable assignment



55



For every object in Ruby, there can and must be one or more references to that

object. If there are no references, the object is considered defunct, and its memory

space is released and reused.

If you have two or more variables containing references to a single object, you can

use any of them, on an equal basis, to send messages to the object. References have a

many-to-one relationship to their objects. But if you assign a completely new object to

a variable that’s already referring to an object, things change.



2.6.2



References in variable assignment and reassignment

Every time you assign to a variable—every time you put a variable name to the left of

an equal sign and something else on the right—you start from scratch: the variable is

wiped clean, and a new assignment is made.

Here’s a new version of our earlier example, illustrating this point:

str = "Hello"

abc = str

str = "Goodbye"

puts str

puts abc



This time the output is as follows:

Goodbye

Hello



The second assignment to str gives str a reference to a different string object. str

and abc part company at that point. abc still refers to the old string (the one whose

contents are “Hello”), but str now refers to a different string (a string whose contents

are “Goodbye”).

The first version of the program changed a single string; but the second version

has two separate strings. After it’s reused, the variable str has nothing further to do

with the object it referred to previously. But reusing str has no effect on abc, which

still contains a reference to the original string.

NOTE



The examples use local variables to demonstrate what does and doesn’t

happen when you assign to a variable that’s already been assigned to.

But the rules and behaviors you’re seeing here aren’t just for local variables. Class, global, and instance variables follow the same rules. (So do

so-called constants, which you can assign to more than once, oddly

enough!) All of these categories of identifier are l-values: they can serve

as the left-hand side, or target, of an assignment. (Compare with, say,

100 = 10, which fails because 100 isn’t an l-value.) And they all behave

the same with respect to how they bind to their right-hand side and what

happens when you use a given one more than once.



Ruby variables are often described as labels or names for objects. It’s a useful comparison. Say you have two names for your dog. “I’m taking Fido to the vet” and “I’m taking Rover to the vet” refer to the same animal. But if you get a new dog and transfer



Licensed to sam kaplan



56



CHAPTER 2



Objects, methods, and local variables



the name Fido to him, then the name-to-dog bindings have changed. Fido and Rover

no longer refer to the same animal, and the name Fido has no further connection

with the first dog. And the new Fido doesn’t even have to be a dog; you could stop calling your dog Fido and start using the name for your car instead. It’s the same when

you do x = 1 followed by x = "A string". You’re reusing the identifier x.

The semantics of references and (re)assignment have important implications for

how things play out when you call a method with arguments. What does the method

receive? And what can the method do with it?



2.6.3



References and method arguments

Let’s stick with a string-based example, because strings are easy to change and track.

Here’s a method that takes one argument:

def change_string(str)

str.replace("New string content!")

end



Next, create a string and send it to change_string:

s = "Original string content!"

change_string(s)



Now, examine s:

puts s



The examination reveals that the contents of the string to which s refers have

changed:

New string content!



This tells you that inside the change_string method, the variable str is assigned a reference to the string also referred to by s. When you call a method with arguments,

you’re really trafficking in object references. And once the method has hold of a reference, any changes it makes to the object through the reference are visible when you

examine the object through any of its references.

Ruby gives you some techniques for protecting objects from being changed,

should you wish or need to do so.

DUPING AND FREEZING OBJECTS



If you want to protect objects from being changed inside methods to which you send

them, you can use the dup method, which duplicates an object:

s = "Original string content!"

change_string(s.dup)

puts s

Original string content!



You can also freeze an object, which prevents it from undergoing further change:

s = "Original string content!"

s.freeze

change_string(s)

RuntimeError: can’t modify frozen string



Licensed to sam kaplan



Local variables and variable assignment



57



To complete the picture, there’s also a method called clone. It’s a lot like dup. The difference is that if you clone a frozen object, the clone is also frozen—whereas if you

dup a frozen object, the duplicate isn’t frozen.

With these tools in hand—dup, clone, and freeze—you can protect your objects

against most rogue change operations. Some dangers still lurk, though. Even if you

freeze an array, it’s still possible to change the objects inside the array (assuming

they’re not frozen).

>> numbers = ["one", "two", "three"]

=> ["one", "two", "three"]

>> numbers.freeze

=> ["one", "two", "three"]

>> numbers[2] = "four"

RuntimeError: can't modify frozen array

from (irb):24:in `[]='

from (irb):24

from /usr/local/bin/irb19:12:in `

'

>> numbers[2].replace("four")

=> "four"

>> numbers

=> ["one", "two", "four"]



B



C



D



In this example, the fact that the numbers array is frozen means you can’t change the

array B. But the strings inside the array aren’t frozen. If you do a replace operation

on the string “three”, mischievously turning it into “four” C, the new contents of the

string are revealed when you reexamine the (still frozen!) array D.

Be careful with references, and remember that a reference to an object inside a

collection isn’t the same as a reference to the collection. (You’ll get a strong feel for

collections as objects in their own right when we look at them in detail in chapter 9.)

A final point about variables—local variables in particular—involves their physical

resemblance to method calls, and how Ruby figures out what you mean when you

throw a plain, unadorned identifier at it.



2.6.4



Local variables and the things that look like them

When Ruby sees a plain word sitting there—a bareword identifier, like s, ticket,

puts, or user_name—it interprets it as one of three things:









A local variable

A keyword

A method call



Keywords are special reserved words that you can’t use as variable names. def is a keyword; the only thing you can use it for is to start a method definition. (Strictly speaking, you can trick Ruby into naming a method def. But … well … don’t.) if is also a

keyword; lots of Ruby code involves conditional clauses that start with if, so it would

be confusing to also allow the use of if as a variable name. A sequence like if = 3

would be difficult for Ruby to parse.



Licensed to sam kaplan



58



CHAPTER 2



Objects, methods, and local variables



Like local variables, method calls can be plain words. You’ve seen several examples,

including puts and print. If the method call includes arguments in parentheses, then

it’s clear that it’s not a local variable. In other cases, there may be some ambiguity—

and Ruby has to figure it out.

Here’s how Ruby decides what it’s seeing when it encounters a plain identifier:

1



2



3



If the identifier is a keyword, it’s a keyword (Ruby has an internal list of these

and recognizes them).

If there’s an equal sign (=) to the right of the identifier, it’s a local variable

undergoing an assignment.

Otherwise, the identifier is assumed to be a method call.



If you use an identifier that isn’t any of these three things, then Ruby will complain

and halt execution with a fatal error. The error message you get when this happens is

instructive:

$ ruby -e "x"

-e:1: undefined local variable or method 'x' for

main:Object (NameError)



Note that Ruby can’t tell whether you thought x was a variable or a method. It knows

that x isn’t a keyword, but it could be either of the other two. So the error message

includes both.

At this point you’ve got a large, and growing, store of knowledge about objects and

variables and how they’re related. We’ll turn next to the topic of how to create objects

in a structured, scalable way with classes.



2.7



Summary

We’ve covered a lot of ground in this chapter. You’ve learned about creating a new

object and defining methods for it. You’ve learned about the message-sending mechanism by which you send requests to objects for information or action. You also learned

how to use some of the important built-in methods that every Ruby object comes with:

object_id, respond_to?, and send. And we looked in some detail at the syntax for

method argument lists, including the use of required, optional, and default-valued

arguments.

Finally, we examined local variables and variable assignment. You saw that keywords and method calls can look like local variables, and Ruby has ways of figuring out

what it’s seeing. You also learned that variables receive references to objects, and more

than one variable can refer to the same object.

The chapter included some comments about object orientation and how objects

do or don’t represent a modeling of some sector or quadrant of the real world; and

we’ll end with a couple of follow-up comments in that vein. Writing a Ruby program

can involve thinking about how you might map elements of a domain (even a modest

one-entity domain like “a ticket to an event”) onto a system of objects, such that those

objects can store information and perform tasks. At the same time, it’s important not



Licensed to sam kaplan



Summary



59



to think too rigidly about the relation between objects and the real world. Objectoriented languages certainly offer a strong component of real-world modeling; but

Ruby, at the same time, is extremely elastic in its modeling facilities—as you can see

from how easy it is to enhance a given object’s behavior. The chief goal in designing a

program and the objects inside it is to come up with a system that works and that has

internal consistency.

And, of course, the language offers lots of facilities for developing program structure. Creating objects one by one, as we’ve been doing in this chapter, isn’t much more

than the tip of the iceberg. We’ll expand the discussion exponentially next, by looking

at how to create objects on a multiple, more automated basis using Ruby classes.



Licensed to sam kaplan



Organizing objects

with classes



In this chapter





Creating multiple objects with classes







Setting and reading object state







Automating creation of attribute read and write

methods







Class inheritance mechanics







Syntax and semantics of Ruby constants



Creating a new object with Object.new—and equipping that object with its own

methods, one method at a time—is a great way to get a feel for the objectcenteredness of Ruby programming. But this approach doesn’t exactly scale; if

you’re running an online box office and your database has to process records for

tickets by the hundreds, you’ve got to find another way to create and manipulate

ticket-like objects in your Ruby programs.

Sure enough, Ruby gives you a full suite of programming techniques for creating objects on a batch basis. You don’t have to define a separate price method for



60



Licensed to sam kaplan



Classes and instances



61



every ticket. Instead, you can define a ticket class, engineered in such a way that every

individual ticket object automatically has the price method.

Defining a class lets you group behaviors (methods) into convenient bundles, so

that you can quickly create many objects that behave essentially the same way. You can

also add methods to individual objects, if that’s appropriate for what you’re trying to

do in your program. But you don’t have to do that with every object, if you model your

domain into classes.

Everything you handle in Ruby is an object; and every object is an instance of some

class. This fact holds true even where it might at first seem a little odd. Integers are

instances of a class, and classes themselves are objects. You’ll learn in this chapter how

this pervasive aspect of the design of Ruby operates.

It’s important to remember that talking about classes doesn’t mean not talking

about objects; that’s why this chapter has the title it has, rather than, say, “Ruby

Classes.” Much of what we’ll look at here pertains to objects and methods—but that’s

because classes are, at heart, a way to organize objects and methods. We’ll be looking

at the kinds of things you can and will do inside classes, as well as looking at what

classes themselves are.



3.1



Classes and instances

In most cases, a class consists chiefly of a collection of method definitions. The class

exists (also in most cases) for the purpose of being instantiated: that is, of having

objects created that are instances of the class.

You’ve already seen instantiation in action. It’s our old signature tune:

obj = Object.new



Object is a built-in Ruby class. When you use the dot notation on a class, you send a



message to the class. Classes can respond to messages, just like objects; in fact, as you’ll

have reason to be aware of in any number of situations, classes are objects. The new

method is a constructor: a method whose purpose is to manufacture and return to you a

new instance of the class, a newly minted object.

You define a class with the class keyword. Classes are named with constants; a constant is a special type of identifier, recognizable by the fact that it begins with a capital

letter. Constants are used to store information and values that don’t change over the

course of a program run.

WARNING



Constants can change: they’re not as constant as their name implies. But

if you assign a new value to a constant, Ruby prints a warning. The best

practice is to avoid assigning new values to constants that you've already

assigned a value to. (See section 3.7.2 for more information about reassignment to constants.)



Let’s define a Ticket class. Inside the class definition, we define a single, simple

method:

class Ticket

def event



Licensed to sam kaplan



62



CHAPTER 3



Organizing objects with classes



"Can't really be specified yet..."

end

end



Now we can create a new ticket object and ask it (pointlessly, but to see the process)

to describe its event:

ticket = Ticket.new

puts ticket.event



The method call ticket.event results in the execution of our event method and,

consequently, the printing out of the (rather uninformative) string specified inside

that method:

Can't really be specified yet...



The information is vague, but the process is fully operational: we have written and

executed an instance method.

Meaning what, exactly?



3.1.1



Instance methods

The examples of method definitions in chapter 2 involved defining methods directly

on individual objects:

def ticket.event



The event method in the previous example, however, is defined in a general way,

inside the Ticket class:

def event



That’s because this event method will be shared by all tickets—that is, by all instances

of Ticket. Methods of this kind, defined inside a class and intended for use by all

instances of the class, are called instance methods. They don’t belong only to one object.

Instead, any instance of the class can call them.

NOTE



Methods that you define for one particular object—as in def

ticket.price—are called singleton methods. You’ve already seen examples, and we’ll look in more depth at how singleton methods work in

chapter 13. An object that has a price method doesn’t much care

whether it’s calling a singleton method or an instance method of its class.

But the distinction is important from the programmer perspective.



Once you’ve defined an instance method in a class, nothing stops you from defining it

again—that is, overriding the first definition with a new one.



3.1.2



Overriding methods

Here’s an example of defining the same method twice in one class:

class C

def m

puts "First definition of method m"



Licensed to sam kaplan



Xem Thêm
Tải bản đầy đủ (.pdf) (519 trang)

×