Method Arguments
Up to this point, it is assumed that the number of arguments passed to a method is known. While unthinkable in many other languages, Ruby allows you to pass in a variable number of arguments and capture them with a single parameter. To create an argument of a variable length, simply place an asterisk (
*) in front of the last argument. In this way, you could write the definition of a polygon in Ruby.
class Polygon
def initialize (s1, s2, s3, *others)
@sideOne = s1
@sideTwo = s2
@sideThree = s3
@otherSides = others
end
end
As shown below, you can use this definition to create a triangle or a hexagon. In Part 2 of this series, you will learn more about arrays and how to work with the other "sides," or arguments passed in, of the Polygon instances.
irb(main):009:0> poly1=Polygon.new(2,4,5)
=> #<Polygon:0x594db10 @otherSides=[], @sideThree=5, @sideTwo=4, @sideOne=2>
irb(main):010:0> poly2=Polygon.new(2,18,4,5,7,9)
=> #<Polygon:0x5948d58 @otherSides=[5, 7, 9], @sideThree=4, @sideTwo=18, @sideOne=2>
On the flipside of accepting variable length arguments, Ruby also lets you define a default value for a method argument when the caller does not provide one. For example, here's a better initializer for the
Rectangle class.
def initialize (hgt = 1, wdth = 1)
@height = hgt
@width = wdth
end
As seen here, the assignment operator next to the parameters listed in the definition serves as a default assigner if parameters are left off. Now, when creating a new
Rectangle, if the width is left off in the call, an appropriate width is provided by default:
irb(main):090:0> rect=Rectangle.new(2)
=> #<Rectangle:0x5873f68 @width=1, @height=2>
Class Variables and Class Methods
Like most object-oriented languages, Ruby classes also allow for class variables and methods. A class variable allows a single variable to be shared amongst all instances of a class. In Ruby, a double at sign (@@) is used to signify class variables. For example, if you wanted all instances of a BankAccount class to share the same interest rate, then the class might be defined as follows:
class BankAccount
@@interestRate = 6.5
def BankAccount.getInterestRate()
@@interestRate
end
attr_accessor :balance
def initialize (bal)
@balance = bal
end
end
As you can see, class variables must be initialized before they are used, and like instance variables, you need to write accessor methods if you want to make class variables accessible. In this case, I have defined a class method to return the interest rate. Note the class name and period in front of
getInterestRate denoting a class method. A class method works the same regardless of any instancein this case, returning the common interest rate to all
BankAccount instances. To call on class methods, you need to use the class name as it is also used in the class method definition:
irb(main):045:0> BankAccount.getInterestRate
=> 6.5
In fact, the "new" method used to create instances of classes is actually a class method. Thus, when you type
Rectangle.new in your program, you are actually calling on the
new class method which Ruby provides by default.
Inheritance
One of the tenets of object-oriented programming is support for a class hierarchy. As in the classification of things in nature, classes are allowed to inherit characteristics from more generic classes. Characteristics in object-oriented programming are embodied in methods and variables. For example, a Square class inherits from the Rectangle class some characteristics or methods and variables. A Square is a more specific type of Rectangle (an instance of Rectangle with equal sized height and width) but it still has a height and width as well as area (also computed in the same way as a Rectangle's by multiplying one side by the other side). In Ruby, the Square class could be created with the following definition:
class Square < Rectangle
end
The
< Rectangle signifies that
Square is a subclass of
Rectangle or, conversely, that
Rectangle is the superclass of
Square. By default, an instance of
Square automatically has all the same attributes and methods that a
Rectangle has to include height, width, and the area method. In order to insure the sides of
Square instances are of equal length, you may want to override the existing initialize method of
Square:
class Square < Rectangle
def initialize (size)
@height = size
@width = size
end
end
Again, everything in Ruby is an object. Quite literally, this means that everything descends from the
Object class. While it is not explicit in all class definitions (you won't see
< Object in the definition), all classes descend from the Ruby base class
Object. Knowing this fact makes this next point a little easier to understand.
When writing your application, you can define methods outside of a class definition. At the start of this article, you were shown a Celsius to Fahrenheit converter method that was not part of any class. As an additional example, here's a method outside of any class:
def feel?
return "I feel fine."
end
To execute this method, just enter the method nameno class or instance is needed:
irb(main):042:0> feel?
=> "I feel fine."
These methods look like functions or procedures in another language like C. In fact, while the methods look like they do not belong to any class, these methods are actually methods you have added to the
Object class, which (because
Object is "the" superclass of all classes) in turn also adds the method to your classes. Therefore, you can now call this method on any object (like instances of
Squares and
Rectangles) or even a class (like the
Rectangle class).
irb(main):043:0> sq1=Square.new(4)
=> #<Square:0x5a18b50 @width=4, @height=4>
irb(main):044:0> rect1=Rectangle.new(5,7)
=> #<Rectangle:0x5a139a8 @width=7, @height=5>
irb(main):045:0> sq1.feel?
=> "I feel fine."
irb(main):046:0> rect1.feel?
=> "I feel fine."
irb(main):047:0> Rectangle.feel?
=> "I feel fine."
Method Access Control
There is a small catch to the top-level methods. They are considered "private" methods. In many cases, as you design your applications, you want methods to be used internally by an object, but not by other objects. Ruby provides three keywords to limit access to methods.
- Private: Methods that can only be accessed by the object.
- Protected: Methods that can be accessed by the object and by instances of the class and direct inheritance descendants.
- Public: Methods can be accessed by any object (Public is the default setting for all methods).
The keywords are inserted in the code between methods. All methods defined from the keyword
private are private until another access control keyword is put in the code. For example, in the code below, the
accessors and
area method are all public by default while the
grow method is private. The
doubleSize method is explicitly made public. The
initialize method of a class is automatically private.
class Rectangle
attr_accessor :height, :width
def initialize (hgt, wdth)
@height = hgt
@width = wdth
end
def area ()
@height*@width
end
private #start making private methods
def grow (heightMultiple, widthMultiple)
@height = @height * heightMultiple
@width = @width * widthMultiple
return "New area:" + area().to_s
end
public #back to public methods again
def doubleSize ()
grow(2,2)
end
end
As shown below,
doubleSize can be executed on the object, but any calls to grow directly are rebuffed and return an error.
irb(main):075:0> rect2=Rectangle.new(3,4)
=> #<Rectangle:0x59a3088 @width=4, @height=3>
irb(main):076:0> rect2.doubleSize()
=> "New area: 48"
irb(main):077:0> rect2.grow()
NoMethodError: private method 'grow' called for #<Rectangle:0x59a3088 @width=8, @height=6>
from (irb):77
from :0
Instance and class variables are private by default in Ruby unless attribute accessors and mutators are provided.