In this part of the Ruby tutorial, we will continue talking about object-oriented programming in Ruby.
We start with attribute accessors. We will cover class constants, class methods and operator overloading. We will define polymorphism and will show how it is used in Ruby. We will also mention modules and exceptions.
The
As we already stated above, the
In Ruby there is only a slight distinction between an operator and a method.
Class methods cannot access instance variables.
There are three ways to create a class method in Ruby.
In general, polymorphism is the ability to appear in different forms. Technically, it is the ability to redefine methods for derived classes. Polymorphism is concerned with the application of specific implementations to an interface or a more generic base class.
Note that there is some difference in the definition of the polymorphism in statically typed languages like C++, Java or C# and dynamically typed languages like Python or Ruby. In statically typed languages it is important when the compilers determine the method definition. At compile time or at run time. In dynamically typed languages we concentrate on the fact, that methods with the same name do different things.
Modules serve two basic purposes. They are used to organize code. Classes, methods and constants which have something in common can be put into separate modules. This also prevents name clashes, because all objects are unique by their module. In this context, Ruby modules are similar to C# namespaces and Java packages.
The second purpose of a module is to create mixins. A mixin is a Ruby facility to create multiple inheritance. If a class inherits functionality from more than one class, we speak of multiple inheritance.
In the following example, we show how modules can be used to organize code.
The final code example of this section will demonstrate multiple inheritance using Ruby modules. In this context the modules are called mixins.
During the execution of our application, many things might go wrong. A disk might get full and we cannot save our file. An Internet connection might go down and our application tries to connect to a site. All these might result in a crash of our application. To prevent happening this, we must cope with all possible errors that might occur. For this, we can use the exception handling.
Exceptions are objects. They are descendants of a built-in
A programmer may raise exceptions himself using the
With the Ruby
We can create our own custom exceptions if we want. Custom exceptions in Ruby should inherit from the
In this chapter we finished talking about object-oriented programming in Ruby language.
We start with attribute accessors. We will cover class constants, class methods and operator overloading. We will define polymorphism and will show how it is used in Ruby. We will also mention modules and exceptions.
Attribute accessors
All Ruby variables are private. It is possible to access them only via methods. These methods are often called setters and getters. Creating a setter and a getter method is a very common task. Therefore Ruby has convenient methods to create both types of methods. They areattr_reader
, attr_writer
and attr_accessor
. The
attr_reader
creates getter methods. The attr_writer
method creates setter methods and instance variables for this setters. The attr_accessor
method creates both getter, setter methods and their instance variables. #!/usr/bin/rubyWe have a Car class. In the definition of the class, we use the
class Car
attr_reader :name, :price
attr_writer :name, :price
def to_s
"#{@name}: #{@price}"
end
end
c1 = Car.new
c2 = Car.new
c1.name = "Porsche"
c1.price = 23500
c2.name = "Volkswagen"
c2.price = 9500
puts "The #{c1.name} costs #{c1.price}"
p c1
p c2
attr_reader
and attr_writer
to create two getter and setter methods for the Car class. attr_reader :name, :priceHere we create two instance methods named name and price. Note that the
attr_reader
takes symbols of methods as parameters. attr_writer :name, :priceThe
attr_writer
creates two setter methods named name and price and two instance variables @name and @price. c1.name = "Porsche"In this context, two setter methods are called to fill instance variables with some data.
c1.price = 23500
puts "The #{c1.name} costs #{c1.price}"Here two getter methods are called to get data from the instance variables of the c1 object.
$ ./arw.rbOutput of the example.
The Porsche costs 23500
Porsche: 23500
Volkswagen: 9500
As we already stated above, the
attr_accessor
method creates both getter, setter methods and their instance variables. #!/usr/bin/rubyWe have a Book class in which the
class Book
attr_accessor :title, :pages
end
b1 = Book.new
b1.title = "Hidden motives"
b1.pages = 255
p "The book #{b1.title} has #{b1.pages} pages"
attr_accessor
creates two pairs of methods and two instance variables. class BookThe
attr_accessor :title, :pages
end
attr_accessor
method that sets up title and pages methods and @title and @pages instance variables. b1 = Book.newAn object of a Book class is created. Two setter methods fill the instance variables of the object.
b1.title = "Hidden motives"
b1.pages = 255
p "The book #{b1.title} has #{b1.pages} pages"In this code line we use two getter methods to read the values of the instance variables.
$ ./accessor.rbExample output.
"The book Hidden motives has 255 pages"
Class constants
Ruby enables to create class constants. These constants do not belong to a concrete object. They belong to the class. By convention, constants are written in uppercase letters.#!/usr/bin/rubyWe have a MMath class with a PI constant.
class MMath
PI = 3.141592
end
puts MMath::PI
PI = 3.141592We create a PI constant. Remember that constants in Ruby are not enforced.
puts MMath::PIWe access the PI constant using the :: operator.
$ ./classconstant.rbRunning the example.
3.141592
The to_s method
Each object has ato_s
method. It returns a string representation of the object. Note that when the puts
method takes an object as a parameter, the to_s
of the object is being called. #!/usr/bin/rubyWe have a Being class in which we override the default implementation of the
class Being
def to_s
"This is Being class"
end
end
b = Being.new
puts b.to_s
puts b
to_s
method. def to_sEach class created inherits from the base
"This is Being class"
end
Object
. The to_s
method belongs to this class. We overwrite the to_s method and create a new implementation. We provide a human-readable description of our object. b = Being.newWe create a Being class and call the
puts b.to_s
puts b
to_s
method twice. The first time explicitly, the second time implicitly. $ ./tostring.rbThis is what we get, when we run the example.
This is Being class
This is Being class
Operator overloading
Operator overloading is a situation where different operators have different implementations depending on their arguments.In Ruby there is only a slight distinction between an operator and a method.
#!/usr/bin/rubyIn the example, we have a Circle class. We overload the + operator in the class. We use it to add two circle objects.
class Circle
attr_accessor :radius
def initialize r
@radius = r
end
def +(other)
Circle.new @radius + other.radius
end
def to_s
"Circle with radius: #{@radius}"
end
end
c1 = Circle.new 5
c2 = Circle.new 6
c3 = c1 + c2
p c3
def +(other)We define a method with a + name. The method adds the radiuses of two circle objects.
Circle.new @radius + other.radius
end
c1 = Circle.new 5We create two circle objects. In the third line, we add these two objects to create a new one.
c2 = Circle.new 6
c3 = c1 + c2
$ ./operatoroverloading.rbBy adding two circle objects we have created a third one, which radius is 11.
Circle with radius: 11
Class methods
From a specific point of view, Ruby has two kinds of methods. Class methods and instance methods. Class methods are called on a class. They cannot be called on an instance of a class; on a created object.Class methods cannot access instance variables.
#!/usr/bin/rubyThe above code example presents a Circle class. Apart from a constructor method, it has one class and one instance method.
class Circle
def initialize x
@r = x
end
def self.info
"This is a Circle class"
end
def area
@r * @r * 3.141592
end
end
p Circle.info
c = Circle.new 3
p c.area
def self.infoMethods that start with a
"This is a Circle class"
end
self
keyword are class methods. def areaInstance methods do not start with the self keyword.
"Circle, radius: #{@r}"
end
p Circle.infoWe call a class method. Note that we call the method on a class name.
c = Circle.new 3To call an instance method, we must first create an object. Instance methods are always called on an object. In our case, the c variable holds the object and we call the area method on the circle object. We utilize a dot operator.
p c.area
$ ./classmethods.rbOutput of the code example describing class methods in Ruby.
"This is a Circle class"
28.274328
There are three ways to create a class method in Ruby.
#!/usr/bin/rubyThe example has three classes. Each of them has one class method.
class Wood
def self.info
"This is a Wood class"
end
end
class Brick
class << self
def info
"This is a Brick class"
end
end
end
class Rock
end
def Rock.info
"This is a Rock class"
end
p Wood.info
p Brick.info
p Rock.info
def self.infoClass methods may start with a
"This is a Wood class"
end
self
keyword. class << selfAnother way is to put a method definition after the class << self construct.
def info
"This is a Brick class"
end
end
def Rock.infoThis is the third way to define a class method in Ruby.
"This is a Rock class"
end
$ ./classmethods2.rbWe see the output of calling all three class methods on a Wood, Brick and Rock classes.
"This is a Wood class"
"This is a Brick class"
"This is a Rock class"
Three ways to create an instance method
Ruby has three basic ways to create instance methods. Instance methods belong to an instance of an object. They are called on an object using a dot operator.#!/usr/bin/rubyIn the example we create three instance objects from a Wood, a Brick and a Rock class. Each object has one instance method defined.
class Wood
def info
"This is a wood object"
end
end
wood = Wood.new
p wood.info
class Brick
attr_accessor :info
end
brick = Brick.new
brick.info = "This is a brick object"
p brick.info
class Rock
end
rock = Rock.new
def rock.info
"This is a rock object"
end
p rock.info
class WoodThis is probably the most common way to define and call an instance method. The info method is defined inside the Wood class. Later, the object is created and we call the info method on the object instance.
def info
"This is a wood object"
end
end
wood = Wood.new
p wood.info
class BrickAnother way is to create a method using the attribute accessors. This is a convenient way which saves some typing for the programmer. The
attr_accessor :info
end
brick = Brick.new
brick.info = "This is a brick object"
p brick.info
attr_accessor
creates two methods, the getter and the setter method and it also creates an instance variable which stores the data. The brick object is created and the data is stored in the @info variable using the info setter method. Finally, the message is read by the info getter method. class RockIn the third way we create an empty Rock class. The object is instantiated. Later, a method is dynamically created and placed into the object.
end
rock = Rock.new
def rock.info
"This is a rock object"
end
p rock.info
$ ./threeways.rbExample output.
"This is a wood object"
"This is a brick object"
"This is a rock object"
Polymorphism
The polymorphism is the process of using an operator or function in different ways for different data input. In practical terms, polymorphism means that if class B inherits from class A, it doesn't have to inherit everything about class A; it can do some of the things that class A does differently. (Wikipedia)In general, polymorphism is the ability to appear in different forms. Technically, it is the ability to redefine methods for derived classes. Polymorphism is concerned with the application of specific implementations to an interface or a more generic base class.
Note that there is some difference in the definition of the polymorphism in statically typed languages like C++, Java or C# and dynamically typed languages like Python or Ruby. In statically typed languages it is important when the compilers determine the method definition. At compile time or at run time. In dynamically typed languages we concentrate on the fact, that methods with the same name do different things.
#!/usr/bin/rubyWe have a simple inheritance hierarchy. There is an Animal base class and two descendants, a Cat and a Dog. Each of these three classes has its own implementation of the make_noise method. The implementation of the method of the descendants replaces the definition of a method in the Animal class.
class Animal
def make_noise
"Some noise"
end
def sleep
puts "#{self.class.name} is sleeping."
end
end
class Dog < Animal
def make_noise
'Woof!'
end
end
class Cat < Animal
def make_noise
'Meow!'
end
end
[Animal.new, Dog.new, Cat.new].each do |animal|
puts animal.make_noise
animal.sleep
end
class Dog < AnimalThe implementation of the make_noise method in the Dog class replaces the implementation of the make_noise of the Animal class.
def make_noise
'Woof!'
end
end
[Animal.new, Dog.new, Cat.new].each do |animal|We create an instance of ech of the classes. We call make_noise and sleep methods on the objects.
puts animal.make_noise
animal.sleep
end
$ ./polymorhism.rbOutput of the polymorhism.rb script.
Some noise
Animal is sleeping.
Woof!
Dog is sleeping.
Meow!
Cat is sleeping.
Modules
A RubyModule
is a collection of methods, classes and constants. Modules are similar to classes with a few differences. Modules cannot have instances. They have no subclasses. Modules serve two basic purposes. They are used to organize code. Classes, methods and constants which have something in common can be put into separate modules. This also prevents name clashes, because all objects are unique by their module. In this context, Ruby modules are similar to C# namespaces and Java packages.
The second purpose of a module is to create mixins. A mixin is a Ruby facility to create multiple inheritance. If a class inherits functionality from more than one class, we speak of multiple inheritance.
#!/usr/bin/rubyRuby has a built-in
puts Math::PI
puts Math.sin 2
Math
module. It has multiple methods and a constant. We access the PI constant by using the :: operator. Methods are accessed by a dot operator as in classes. #!/usr/bin/rubyIf we include a module in our script, we can refer to the Math objects directly, omitting the Math name. Modules are added to a script using the
include Math
puts PI
puts sin 2
include
keyword. $ ./modules.rbOutput of the program.
3.141592653589793
0.9092974268256817
In the following example, we show how modules can be used to organize code.
#!/usr/bin/rubyRuby code can be grouped semantically. Rocks, trees belong to a forest. And pools, cinemas, squares belong to a town. By using modules our code has some order. Plus there is another thing. Animals can be in a forest and in a town too. In a single script, we cannot define two animal classes. They would clash. Putting them in different modules we solve the issue.
module Forest
class Rock ; end
class Tree ; end
class Animal ; end
end
module Town
class Pool ; end
class Cinema ; end
class Square ; end
class Animal ; end
end
p Forest::Tree.new
p Forest::Rock.new
p Town::Cinema.new
p Forest::Animal.new
p Town::Animal.new
p Forest::Tree.newWe are creating objects that belong to a forest and to a town. To access an object in a module, we use the :: operator.
p Forest::Rock.new
p Town::Cinema.new
p Forest::Animal.newTwo different animal objects are created. The Ruby interpreter can tell between them. It identifies them by their module name.
p Town::Animal.new
$ ./modules3.rb
#<Forest::Tree:0x97f35ec>
#<Forest::Rock:0x97f35b0>
#<Town::Cinema:0x97f3588>
#<Forest::Animal:0x97f3560>
#<Town::Animal:0x97f3538>
The final code example of this section will demonstrate multiple inheritance using Ruby modules. In this context the modules are called mixins.
#!/usr/bin/rubyWe have three modules and one class. The modules represent some functionality. A device can be swiched on and off. Many objects can share this functionality. Televisions, mobile phones, computers or refrigerators. Rather than creating this ability to be swiched on/off for each object class, we separate it in one module, which can be included in each object if necessary. This way the code is better organized and more compact.
module Device
def switch_on ; puts "on" end
def switch_off ; puts "off" end
end
module Volume
def volume_up ; puts "volume up" end
def vodule_down ; puts "volume down" end
end
module Pluggable
def plug_in ; puts "plug in" end
def plug_out ; puts "plug out" end
end
class CellPhone
include Device, Volume, Pluggable
def ring
puts "ringing"
end
end
cph = CellPhone.new
cph.switch_on
cph.volume_up
cph.ring
module VolumeA Volume module organizes methods, than are responsible for controlling the volume level. If a device needs these methods, it simply includes the module to its class.
def volume_up ; puts "volume up" end
def vodule_down ; puts "volume down" end
end
class CellPhoneA cell phone adds all three modules with the
include Device, Volume, Pluggable
def ring
puts "ringing"
end
end
include
method. The methods of the modules are mixed in the CellPhone class. And are available for the instances of the class. The CellPhone class has also its own ring method that is specific to it. cph = CellPhone.newA CellPhone object is created and we call three methods upon the object.
cph.switch_on
cph.volume_up
cph.ring
$ ./mixins.rbRunning the example.
on
volume up
ringing
Exceptions
Exceptions are designed to handle the occurrence of exceptions, special conditions that change the normal flow of program execution. Exceptions are raised, thrown or initiated.During the execution of our application, many things might go wrong. A disk might get full and we cannot save our file. An Internet connection might go down and our application tries to connect to a site. All these might result in a crash of our application. To prevent happening this, we must cope with all possible errors that might occur. For this, we can use the exception handling.
Exceptions are objects. They are descendants of a built-in
Exception
class. Exception objects carry information about the exception. Its type (the exception’s class name), an optional descriptive string, and optional traceback information. Programs may subclass Exception
, or more often StandardError
to provide custom classes and add additional information. #!/usr/bin/rubyIn the above program, we intentionally divide a number by zero. This leads to an error.
x = 35
y = 0
begin
z = x / y
puts z
rescue => e
puts e
p e
end
beginStatements that are error prone are placed after the
z = x / y
puts z
begin
keyword. rescue => eIn the code following the
puts e
p e
end
rescue
keyword, we deal with an exception. In this case, we print the error message to the console. The e is an exception object that is created when the error occurs. $ ./zerodivision.rbIn the output of the example, we see the message of the exception. The last line shows the exception object called ZeroDivisionError.
divided by 0
#<ZeroDivisionError: divided by 0>
A programmer may raise exceptions himself using the
raise
keyword. #!/usr/bin/rubyThe entrance to a club is not allowed for people younger than 18 years. We simulate this situation in our Ruby script.
age = 17
begin
if age < 18
raise "Person is minor"
end
puts "Entry allowed"
rescue => e
puts e
p e
exit 1
end
beginIf the person is minor, an exception is raised. If the
if age < 18
raise "Person is minor"
end
puts "Entry allowed"
raise
keyword does not have a specific exception as a parameter, a RuntimeError exception is raised setting its message to the given string. The code does not reach the puts "Entry allowed"
line. The execution of the code is interrupted and it continues at the rescue block. rescue => eIn the rescue block, we print the error message and the string representation of the
puts e
p e
exit 1
end
RuntimeError
object. We also call the exit
method to inform the environment that the execution of the script ended in error. $ ./raise_exception.rbThe person was minor and he or she was not allowed to enter the club. The bash $? variable is set to the exit error of the script.
Person is minor
#<RuntimeError: Person is minor>
$ echo $?
1
With the Ruby
ensure
clause we create a block of code that is always executed. Whether there is an exception or not. #!/usr/bin/rubyIn the code example, we try to open and read the stones file. I/O operations are error prone. We could easily have an exception.
begin
f = File.open("stones", "r")
while line = f.gets do
puts line
end
rescue => e
puts e
p e
ensure
f.close if f
end
ensureIn the ensure block we close the file handler. We check if the handler exists because it might not have been created. Allocated resources are often placed in the ensure block.
f.close if f
end
We can create our own custom exceptions if we want. Custom exceptions in Ruby should inherit from the
StandardError
class. #!/usr/bin/rubyLet's say, we have a situation in which we cannot deal with big numbers.
class BigValueError < StandardError ; end
LIMIT = 333
x = 3_432_453
begin
if x > LIMIT
raise BigValueError, "Exceeded the maximum value"
end
puts "Script continues"
rescue => e
puts e
p e
exit 1
end
class BigValueError < StandardError ; endWe have a BigValueError class. This class derives from the built-in
StandardError
class. LIMIT = 333Numbers which exceed this constant are considered to be "big" by our program.
if x > LIMITIf the value is bigger than the limit, we throw our custom exception. We give the exception a message "Exceeded the maximum value".
raise BigValueError, "Exceeded the maximum value"
end
$ ./custom_exception.rbRunning the program.
Exceeded the maximum value
#<BigValueError: Exceeded the maximum value>
In this chapter we finished talking about object-oriented programming in Ruby language.
0 comments:
Post a Comment