OOP in Ruby

OOP in Ruby

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 Duck
def 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.

  1. What advantages does Ruby’s approach to polymorphism provide compared to Java’s approach? What disadvantages exist?
  2. 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