[Ruby-tut] Objects... objects everywhere!
Author |
Message |
wtd
|
Posted: Fri Jul 02, 2004 8:18 pm Post subject: [Ruby-tut] Objects... objects everywhere! |
|
|
So, knowing how to do simple input and output, test conditions, define methods, and construct loops, what's next?
At this point it becomes possible to understand classes and objects and all of that wonderful object-oriented stuff. Why bother? That's a good question, and the simple but correct answer is that it's because objects are at the heart of Ruby, and to really understand Ruby you need to understand classes and objects.
Of course, that's only the answer to "why bother if I care in the slightest about Ruby?" The answer to that question can only be realized by using it, and even then it's more a subconscious realization. You don't so much realize how much Ruby is doing for you as much as you come to appreciate the tedious little odds and ends that you aren't doing.
Classes are sort of a blueprint for objects. They define how an object is laid out and what it can do. Instance variables are the pieces of information stored inside an object, and methods are what an object can do.
In Ruby, all instance variables are private. They cannot be directly accessed by other objects. Also by default, methods in Ruby classes are public. They can be made private or protected, but the same cannot be done for instance variables.
The reasoning behind this is that methods define the interface of an object. Instance variables allow the object to maintain a certain state. Since other objects don't care what instance variables an object has, they are not predefined, and can be freely added, so long as the changes don't change what the object looks like from the outside.
Ruby makes this approach feel more natural by not requiring parentheses. It can feel like you're accessing an instance variable, but the reality is that you'd be accessing a method which in turn returns the instance variable.
As a simple example, let's create a simple class which defines a name.
code: | class Name
def initialize(first, last)
@first, @last = first, last
end
end |
Here "initialize" is somewhat like a constructor in other languages, except that it's a normal method which can be used at any time. It just happens that it's a method called by the "new" method of the class itself.
But I digress...
A "class method"? Remember, everything in Ruby is an object, and this extends even to classes. As a result classes can have variables and methods. The "new" class method simply takes a variable number of arguments and then passes those onto the initialize method which does the work of setting up the initial condition of the object. Of course "new" also handles memory allocation and such, but there's no need to worry about that.
To pass on those arguments Ruby has another use for the * operator that's used for "slurping" up extra arguments to a method.
code: | def foo(*extra_args)
extra_args.each { |arg| puts arg }
end |
In this simple example, "extra_args" becomes an array holding all of the additional arguments. Now in Ruby, by default, an array is just a single object, and normally passing it to a method asan argument it's treated as a single argument. We can, however, force it to be evaluated in the context of a list. For example, we'll pass an array into foo as a single argument, and as a list.
code: | foo([1, 2, 3, 4])
foo(*[1, 2, 3, 4]) |
The latter is equivalent to:
That said...
Back to our regularly scheduled tutorial
code: | @first, @last = first, last |
The @ symbols look rather odd, but are a simple way of designating instance variables, as opposed to local variables. Class variables are indentified by @@ and global variables are prefixed with a $.
Consider instance variables as "global" to all of a particular object's methods. Class variables are essentially global to all objects of a given class. Global variables are global to... well... everything.
The use of the = operator looks a bit odd here, but this is standard Ruby behavior. The first variable on the right is assigned to the first variable/value on the left and so forth. If there are more variables/values on the right than the left, the excess will be ignored.
For instance, in the following, 3 is ignored.
However, we could use the same slurping behavior seen in methods here.
Here "a" becomes 1, but "b" is now an array with 2 and 3.
The other possibility is that there aren't enough values on the right hand side.
In this case "a" is 1, "b" is 2, and "c" is "nil".
Back on topic again
So now we can create a Name object with a first and last name. To create a new name object is an easy task.
code: | bobs_name = Name.new("Bob", "Smith") |
Now, as I mentioned earlier, the instance variables are always private, so we can't get at them at this stage. We'll likely want that access, though, so let's modify the class to add accessor methods for the two variables.
code: | class Name
def initialize(first, last)
@first, @last = first, last
end
def first
@first
end
def last
@last
end
end |
Pretty easy, wasn't it? Fortunately Ruby can make it easier. For automatically creating such simple accessor methods there are the attr* keywords. Since we're only reading the values of the first and last names, and not trying to change them we'll just use "attr_reader".
code: | class Name
attr_reader :first, :last
def initialize(first, last)
@first, @last = first, last
end
end |
Of course, now we want to output the name.
code: | bobs_name = Name.new("Bob", "Smith")
puts "#{bobs_name.first} #{bobs_name.last}" |
But maybe we should have a method which returns a string with the names in that format. And maybe one that returns something like "Smith, Bob". The former we expect to be more common, so we'll alias "to_s" to that method so that puts automatically uses that format.
code: | class Name
attr_reader :first, :last
def initialize(first, last)
@first, @last = first, last
end
def full_name
"#{first} #{last}"
end
alias :to_s :full_name
def reverse_full_name
"#{last}, #{first}"
end
end |
code: | bobs_name = Name.new("Bob", "Smith")
puts bobs_name
puts bobs_name.reverse_full_name |
One last quirky thing
Classes in Ruby, unlike most other languages, are not ever really finished. If you should decide that you want to add something later on, you can easily do so. For instance, were we to want a method that switches the first and last names in a Name object, we could easily do so. Why we would want to is another story entirely.
Assume the previous definition of the Name class.
code: | class Name
def switch_names!
@first, @last = @last, @first
end
end |
That's pretty much it for the simple stuff. |
|
|
|
|
|
Sponsor Sponsor
|
|
|
TokenHerbz
|
Posted: Sat Mar 25, 2006 8:23 pm Post subject: Re: [Ruby-tut] Objects... objects everywhere! |
|
|
wtd wrote:
That's pretty much it for the simple stuff.
I'd hate to see the hard stuff :S |
|
|
|
|
|
rdrake
|
Posted: Sun Mar 26, 2006 9:41 pm Post subject: Re: [Ruby-tut] Objects... objects everywhere! |
|
|
TokenHerbz wrote: I'd hate to see the hard stuff :S Remember that if you have any questions, just ask here, the IRC channel, or wherever wtd/Minsc/myself/Hikaru79/others are. We'll be happy to help you out. |
|
|
|
|
|
wtd
|
Posted: Mon Mar 27, 2006 1:27 pm Post subject: (No subject) |
|
|
Ask as soon as you run into problems. It really makes offering help hard when someone asks for help with "chapter 8" and only reveals sometime later that they didn't make it through "chapter 3."
|
|
|
|
|
|
|
|