OOP in Ruby
Ruby is purely object oriented. Everything in Ruby is an object.
Even numbers and strings are objects.
Ruby’s inheritance hierarchy: Singly rooted hierarchy with the class Object being the superclass of all classes, similar to Java.
The following examples demonstrate the pure object-oriented nature of Ruby. Try this in the Interactive Ruby Browser:
15.modulo 4
> 3
3.14159.between? 3, 4
> true
[11, 12, 13, 14, 15].slice 2, 4
> [13, 14, 15]
Classes
Let us define a Person class with attributes name and height. It has a class variable count, which keeps track of the number of instances of the class created.
class Person
@@count = 0
def initialize(name, height)
@name = name
@height = height
@@count += 1
end
def self.info
puts "Number of instances = #{@@count}"
end
def to_s
"Name: #{@name} Height: #{@height}"
end
end
p = Person.new("Tom",170)
p2 = Person.new("Harry",165)
puts p
puts p2
Person.info
To instantiate an object of the new class, we write varname = classname.new.
This automatically calls the class’s initialize method. The arguments to the new method are passed to the initialize method. The initialize method may cause any component objects to be created.
The instance variables have names beginning with
Class variables have names beginning with
Class methods are declared beginning with
Whenever an object is printed its, to_s method is called.
Note: While printing, values of variables in double-quoted strings may be accessed with #{varname}.
Attributes
In languages that provide information hiding, one usually defines getter and setter methods. Why?
Ruby has shortcuts to define these. Since Ruby classes are open to extension, we can add accessors to the definition of the Person class we defined above.
class Person
attr_reader :name
attr_writer :height
attr_accessor :sex
end
p = Person.new("Tom",170)
p2 = Person.new("Harry",165)
p2.height = 190
puts p2
p.sex = "male"
puts p.sex
The above code adds to the definition of the class.
- attr_reader creates a getter method
- attr_writer creates a setter
- attr_accessor creates both a getter and a setter.
Why would one want to create only attr_writer?
Accessors can also be written the “long way.”
class Person
def weight
@weight
end
def weight=(w)
@weight=w
end
end
Note that this defines two methods—not a way of performing an assignment!
Let’s see if this is shorter than equivalent Java code … in terms of lines of code, or syntactic elements.
Inheritance
In Ruby, the < operator indicates inheritance. Let’s define class Employee to inherit from the Person class defined above.
class Employee < Person
attr_accessor :salary
def initialize(name, height, salary)
super(name, height)
@salary = salary
end
def to_s
super + " Salary: #{@salary}"
end
end
We add a new attribute salary to the Employee class. The initialize method first calls the initialize method of the parent class using super, and then sets the salary. Similarly the to_s method is overridden in the Employee class.
What does super do?
e = Employee.new("Rob", 180, 80000)
puts e
> Name: Rob Height: 180 Salary: 80000
Access Control
Ruby provides three levels of access control –
- Public methods can be called by anyone—no access control is enforced. Methods are public by default (except for , which is always private).
- Protected methods can be invoked only by objects of the defining class and its subclasses. Access is kept within the family.
- Private methods cannot be called with an explicit receiver—the receiver is always self. This means that private methods can be called only in the context of the current object; you can’t invoke another object’s private methods.
By contrast, if a method is protected, it may be called by any instance of the defining class or its subclasses.
Can you think of a case where a private method would need to be called by another object of the same class?
If a method is private, it may be called only within the context of the calling object—it is never possible to access another object’s private methods directly, even if the object is of the same class as the caller.
class MyClass
def method1 # default is “public”
#...
end
protected # subsequent methods will be “protected”
def method2 # will be “protected”
#...
end
private # subsequent methods will be “private”
def method3 # will be “private”
#...
end
public # subsequent methods will be “public”
def method4 # and this will be “public”
#...
end
end
Alternatively, you can set access levels of named methods by listing them as arguments to the access-control functions.
class MyClass
def method1
end
# ... and so on
public :method1, :method4
protected :method2
private :method3
end
[Exercise 1 comes here.]
How does this differ from Java? Update the wiki.
Abstract Methods
Ruby does not have abstract methods like Java or pure virtual functions like C++.
However the same functionality can be simulated as follows (similarly to subclassResponsibility in Smalltalk):
class Shape
def draw
raise NotImplementedError.new("Method not implemented")
end
end
What will happen if we execute …
s = Shape.new.draw
Subclasses, then, have to provide an implementation for the draw method.
Why do you think that Ruby and other dynamic o-o languages don’t have an official “abstract method” construct?
Duck Typing (Unbounded Polymorphism)
Ruby has dynamic type checking.
A method can be invoked on a variable whenever the type of object assigned to the variable has that method defined on it.
This means that if the parameter passed to a method supports the methods invoked on it by the called method, then the calls work.
This is unbounded polymorphism, which can only be found in dynamically typed languages.
What’s the typical rule in statically typed languages?
The dynamic approach is also called “duck typing,” after the expression, “If it walks like a duck and talks like a duck then it must be a duck”. Here is some code illustrating the concept:
class Duckdef quack
puts "Quack!"
end
def walk
puts "Waddle"
end
end
class Toy
def quack
puts "Kwack!"
end
def walk
puts "Waggle"
end
end / class Pig
def eat
puts "I just eat!"
end
end
class Test
def test_duck(duck)
duck.quack
duck.walk
end
end
t = Test.new
t.test_duck(Duck.new)
t.test_duck(Toy.new)
t.test_duck(Pig.new)
What is the result of executing this code?
Explore possible advantages/disadvantages of using duck typing over interface and inheritance.
The test_duck method duck does not check the object type of the input argument that is passed in.
So any object which has the methods quack and walk can be passed to the method and the methods called on it will be successfully executed.
When we try to pass in an object lacking those methods, a NoMethodError is thrown at run time.
Duck typing trades off safety to save lines of code. Any adverse impact on safety can be mitigated by carefully checking the code.
Unbounded polymorphism allegedly makes it possible to add more functionality without affecting the existing design making one’s programs more flexible.
Discuss the following questions with classmates around you for 5 minutes:
For distance education students, please write your answers down on paper. You may also correspond with the TAs or other students.
- What advantages does Ruby’s approach to polymorphism provide compared to Java’s approach? What disadvantages exist?
- What examples do you think exist for duck typing being useful?
In the lecture notes, it was mentioned that a NoMethodError is thrown if an object is passed a message it does not recognize. How else could this be handed?
Regular expressions
Ruby was invented as “a better Perl,” and hence has excellent built-in support for regular expressions.
This is why it is useful as a scripting language. Here’s an example:
"faculty".sub(/c/, "")
"cheese".gsub(/e/, "o")
There are many facets to regular expressions. A Ruby regular expression is an object, and can be created by a call to new.
e = Regexp.new('\d\w*')
It can also be specified like this:
e = /\d\w*/
The \d and \w are character-class abbreviations.
We can, of course, use regular-expression objects in pattern matching:
'a0a0a0'.sub(e, "")
Any PCRE (Perl-compatible regular expression) can go between the two slashes.
Making substitutions couldn't be easier than this:
str = str.gsub(/Steel/,'Titanium')
puts str
>Titanium is the toughest material
We can constrain matching to occur at the start (^) or end ($) of a string.
"cheese".sub(/^e/, "o")
A character class is a set of characters between brackets. Any character within the class matches a character class.
"anecdote".sub(/[aeio]/, "u")
A hyphen may be used to signify a range:
"now is the time".sub(/[a-y]/, "z")
Repetition can be specified by several special characters.
"choose".sub(/o+/, "e")
"choose".sub(/o?/, "e")
Note: Merely calling sub creates a new string object which cannot be modified until it has been assigned to a variable (str). This is similar to Java where all variables are references.
[Exercise 3 comes here]
Ruby