In this part of the Ruby tutorial, we will talk about object-oriented programming in Ruby.
There are three widely used programming paradigms there. Procedural programming, functional programming and object-oriented programming. Ruby is an object-oriented language. It has some functional and procedural features too.
Object-oriented programming (OOP) is a programming paradigm that uses objects and their interactions to design applications and computer programs. (Wikipedia)
There are some basic programming concepts in OOP:
There are two steps in creating an object. First, we define a class. A class is a template for an object. It is a blueprint, which describes the state and behavior that the objects of the class all share. A class can be used to create many objects. Objects created at runtime from a class are called instances of that particular class.
Constructors cannot be inherited. The constructor of a parent object is called with a
Object attributes is the data bundled in an instance of a class. The object attributes are called instance variables or member fields. An instance variable is a variable defined in a class, for which each object in the class has a separate copy.
In the next example, we initiate data members of the class. Initiation of variables is a typical job for constructors.
We can create an object without calling the constructor. Ruby has a special
Ruby has no constructor overloading that we know from other programming languages. This behaviour can be simulated to some extent with default parameter values in Ruby.
In Ruby, data is accessible only via methods.
Methods typically perform some action on the data of the object.
The
Note that unlike in other object-oriented programming languages, inheritance does not play role in Ruby access modifiers. Only two things are important. First, if we call the method inside or outside the class definition. Second, if we use or do not use the
Access modifiers protect data against accidental modifications. They make the programs more robust. The implementation of some methods is subject to change. These methods are good candidates for being private. The interface that is made public to the users should only change when really necessary. Over the years users are accustomed to using specific methods and breaking backward compatibility is generally frowned upon.
The next example looks at private methods.
Finally, we will work with protected methods. The distinction between the protected and the private methods in Ruby is subtle. Protected methods are like private. There is only one small difference. They can be called with the
An Object may be involved in comlicated relationships. A single object can have multiple ancestors. Ruby has a method
Each Ruby object is automatically a descendant of Object, BasicObject classes and Kernel Module. They are built-in the core of the Ruby language.
A more complex example follows.
Inheritance does not play role in the visibility of methods and data members. This is a notable difference from many common object-oriented programming languages.
In C# or Java, private data members and methods are not inherited. Only public and protected. In contrast to this, private data members and methods are inherited in Ruby as well. The visibility of data members and methods is not affected by inheritance in Ruby.
This was the first part of the description of OOP in Ruby.
There are three widely used programming paradigms there. Procedural programming, functional programming and object-oriented programming. Ruby is an object-oriented language. It has some functional and procedural features too.
Object-oriented programming (OOP) is a programming paradigm that uses objects and their interactions to design applications and computer programs. (Wikipedia)
There are some basic programming concepts in OOP:
- Abstraction
- Polymorphism
- Encapsulation
- Inheritance
Objects
Objects are basic building blocks of a Ruby OOP program. An object is a combination of data and methods. In a OOP program, we create objects. These objects communicate together through methods. Each object can receive messages, send messages and process data.There are two steps in creating an object. First, we define a class. A class is a template for an object. It is a blueprint, which describes the state and behavior that the objects of the class all share. A class can be used to create many objects. Objects created at runtime from a class are called instances of that particular class.
#!/usr/bin/rubyIn our first example, we create a simple object.
class Being
end
b = Being.new
puts b
class BeingThis is a simple class definition. The body of the template is empty. It does not have any data or methods.
end
b = Being.newWe create a new instance of the Being class. For this we have the
new
method. The b variable stores the newly created object. puts bWe print the object to the console to get some basic description of the object. What does it mean, to print an object? When we print an object, we in fact call its
to_s
method. But we have not defined any method yet. It is because every object created inherits from the base Object
. It has some elementary functionality, which is shared among all objects created. One of this is the to_s
method. $ ./simple.rbWe get the object class name.
#<Being:0x9f3c290>
The constructor
A constructor is a special kind of a method. It is automatically called, when the object is created. Constructors do not return values. The purpose of the constructor is to initiate the state of an object. The constructor in Ruby is calledinitialize
. Constructors do not return any values. Constructors cannot be inherited. The constructor of a parent object is called with a
super
method. They are called in the order of inheritance. #!/usr/bin/rubyWe have a Being class.
class Being
def initialize
puts "Being is created"
end
end
Being.new
class BeingThe Being class has a constructor method called
def initialize
puts "Being is created"
end
end
initialize
. It prints a message to the console. The definition of a Ruby method is placed between the def
and end
keywords. Being.newAn instance of the Being class is created. At the moment of the object creation, the constructor method is called.
$ ./constructor.rbThis is the output of the program.
Being is created
Object attributes is the data bundled in an instance of a class. The object attributes are called instance variables or member fields. An instance variable is a variable defined in a class, for which each object in the class has a separate copy.
In the next example, we initiate data members of the class. Initiation of variables is a typical job for constructors.
#!/usr/bin/rubyIn the above Ruby code, we have a Person class with one member field.
class Person
def initialize name
@name = name
end
def get_name
@name
end
end
p1 = Person.new "Jane"
p2 = Person.new "Beky"
puts p1.get_name
puts p2.get_name
class PersonIn the constructor of the Person class, we set a member field to a value name. The name parameter is passed to the constructor at creation. A constructor is a method called
def initialize name
@name = name
end
initialize
that is being called at the creation of an instance object. The @name
is an instance variable. Instance variables start with @ character in Ruby. def get_nameThe get_name method returns the member field. In Ruby member fields are accessible only via methods.
@name
end
p1 = Person.new "Jane"We create two objects of a Person class. A string parameter is passed to each object constructor. The names are stored inside instance variables that are unique to each object.
p2 = Person.new "Beky"
puts p1.get_nameWe print the member fields by calling the get_name on each of the objects.
puts p2.get_name
$ ./person.rbWe see the output of the program. Each instance of the Person class has its own name member field.
Jane
Beky
We can create an object without calling the constructor. Ruby has a special
allocate
method for this. The allocate
method allocates space for a new object of a class and does not call initialize on the new instance. #!/usr/bin/rubyIn the example, we create two objects. The first object using the
class Being
def initialize
puts "Being created"
end
end
b1 = Being.new
b2 = Being.allocate
puts b2
new
method, the second object using the allocate
method. b1 = Being.newHere we create an instance of the object with the
new
keyword. The constructor method initialize
is called and the message is printed to the console. b2 = Being.allocateIn case of the
puts b2
allocate
method, the constructor is not called. We call the to_s
method with the puts
keyword to show, that the object was created. $ ./allocate.rbOutput of the program.
Being created
#<Being:0x8ea0044>
Constructor overloading
Constructor overloading is the ability to have multiple types of constructors in a class. This way we can create an object with different number or different types of parameters.Ruby has no constructor overloading that we know from other programming languages. This behaviour can be simulated to some extent with default parameter values in Ruby.
#!/usr/bin/rubyThis example shows how we could simulate constructor overloading on a Person class that has two member fields. If we do not specify the name of the person, we have "unknown" string instead. For unspecified age we have 0.
class Person
def initialize name="unknown", age=0
@name = name
@age = age
end
def to_s
"Name: #{@name}, Age: #{@age}"
end
end
p1 = Person.new
p2 = Person.new "unknown", 17
p3 = Person.new "Becky", 19
p4 = Person.new "Robert"
p p1, p2, p3, p4
def initialize name="unknown", age=0The constructor takes two parameters. They have a default value. The default value is used, if we do not specify our own values at the object creation. Note that the order of parameters must be kept. First comes the name, then the age.
@name = name
@age = age
end
p1 = Person.newWe create four objects. The constructors take different number of parameters.
p2 = Person.new "unknown", 17
p3 = Person.new "Becky", 19
p4 = Person.new "Robert"
p p1, p2, p3, p4
$ ./consover.rbOutput of the example.
Name: unknown, Age: 0
Name: unknown, Age: 17
Name: Becky, Age: 19
Name: Robert, Age: 0
Methods
Methods are functions defined inside the body of a class. They are used to perform operations with the attributes of our objects. Methods are essential in encapsulation concept of the OOP paradigm. For example, we might have a connect method in our AccessDatabase class. We need not to be informed, how exactly the method connects to the database. We only have to know, that it is used to connect to a database. This is essential in dividing responsibilities in programming. Especially in large applications.In Ruby, data is accessible only via methods.
#!/usr/bin/rubyThe example shows two basic ways to call a method.
class Person
def initialize name
@name = name
end
def get_name
@name
end
end
per = Person.new "Jane"
puts per.get_name
puts per.send :get_name
puts per.get_nameThe common way is to use a dot operator on an object followed by a method name.
puts per.send :get_nameThe alternative is to use a built-in
send
method. It takes a symbol of the method to be called as a parameter. Methods typically perform some action on the data of the object.
#!/usr/bin/rubyIn the code example, we have a Circle class. We define two methods.
class Circle
@@PI = 3.141592
def initialize
@radius = 0
end
def set_radius radius
@radius = radius
end
def area
@radius * @radius * @@PI
end
end
c = Circle.new
c.set_radius 5
puts c.area
@@PI = 3.141592We have defined a @@PI variable in our Circle class. It is a class variable. Class variables start with @@ sigils in Ruby. Class variables belong to a class, not to an object. Each object has access to its class variables. We use the @@PI to compute the area of the circle.
def initializeWe have one member field. It is the radius of the circle. If we want to modify this variable from the outside, we must use the publicly available set_radius method. The data is protected.
@radius = 0
end
def set_radius radiusThis is the set_radius method. It gives the @radius instance variable a new value.
@radius = radius
end
def areaThe area method returns the area of a circle. This is a typical task for a method. It works with data and produces some value for us.
@radius * @radius * @@PI
end
c = Circle.newWe create an instance of the Circle class. And set its radius by calling the set_radius method on the object of the circle. We use the dot operator to call the method.
c.set_radius 5
puts c.area
$ ./circle.rbRunning the example.
78.5398
Access modifiers
Access modifiers set the visibility of methods and member fields. Ruby has three access modifiers:public
, protected
and private
. In Ruby, all data members are private. Access modifiers can be used only on methods. Ruby methods are public, unless we say otherwise. The
public
methods can be accessed from inside the definition of the class as well as from the outside of the class. The difference between the protected
and the private
methods is subtle. They both cannot be accessed outside the definition of the class. They can be accessed only within the class itself and by inherited or parent classes. Note that unlike in other object-oriented programming languages, inheritance does not play role in Ruby access modifiers. Only two things are important. First, if we call the method inside or outside the class definition. Second, if we use or do not use the
self
keyword which points to the current receiver. Access modifiers protect data against accidental modifications. They make the programs more robust. The implementation of some methods is subject to change. These methods are good candidates for being private. The interface that is made public to the users should only change when really necessary. Over the years users are accustomed to using specific methods and breaking backward compatibility is generally frowned upon.
#!/usr/bin/rubyThe example explains the usage of public Ruby methods.
class Some
def method1
puts "public method1 called"
end
public
def method2
puts "public method2 called"
end
def method3
puts "public method3 called"
method1
self.method1
end
end
s = Some.new
s.method1
s.method2
s.method3
def method1The method1 is public, even if we did not specify the
puts "public method1 called"
end
public
access modifier. It is because methods are public by default, if not specified otherwise. publicMethods following the
def method2
puts "public method2 called"
end
...
public
keyword are public. def method3From inside the public method3, we call other public method. With and without the
puts "public method3 called"
method1
self.method1
end
self
keyword. s = Some.newPublic methods are the only methods that can be called outside the definition of a class as shown here.
s.method1
s.method2
s.method3
$ ./public_methods.rbRunning the example.
public method1 called
public method2 called
public method3 called
public method1 called
public method1 called
The next example looks at private methods.
#!/usr/bin/rubyPrivate methods are tightest methods in Ruby. They can be called only inside a class definition and without the
class Some
def initialize
method1
# self.method1
end
private
def method1
puts "private method1 called"
end
end
s = Some.new
# s.method1
self
keyword. def initializeIn the constructor of the method, we call the private method1. Calling the method with the self is commented. Private methods cannot be specified with a receiver.
method1
# self.method1
end
privateMethods following the
def method1
puts "private method1 called"
end
private
keyword are private in Ruby. s = Some.newWe create an instance of the Some class. Calling the method outside the class definition is prohibited. If we uncomment the line, the Ruby interpreter gives an error.
# s.method1
$ ./private_methods.rbOutput of the example code.
private method called
Finally, we will work with protected methods. The distinction between the protected and the private methods in Ruby is subtle. Protected methods are like private. There is only one small difference. They can be called with the
self
keyword specified. #!/usr/bin/rubyThe above example shows protected method in usage.
class Some
def initialize
method1
self.method1
end
protected
def method1
puts "protected method1 called"
end
end
s = Some.new
# s.method1
def initializeProtected methods can be called with and without the self keyword.
method1
self.method1
end
protectedProtected methods are preceded by the
def method1
puts "protected method1 called"
end
protected
keyword. s = Some.newProtected methods cannot be called outside the class definition. Uncommenting the line would lead to an error.
# s.method1
Inheritance
The inheritance is a way to form new classes using classes that have already been defined. The newly formed classes are called derived classes, the classes that we derive from are called base classes. Important benefits of inheritance are code reuse and reduction of complexity of a program. The derived classes (descendants) override or extend the functionality of base classes (ancestors).#!/usr/bin/rubyIn this program, we have two classes. A base Being class and a derived Human class. The derived class inherits from the base class.
class Being
def initialize
puts "Being class created"
end
end
class Human < Being
def initialize
super
puts "Human class created"
end
end
Being.new
Human.new
class Human < BeingIn Ruby, we use the < operator to create inheritance relations. The Human class inherits from the Being class.
def initializeThe
super
puts "Human class created"
end
super
method calls the constructor of the parent class. Being.newWe instantiate the Being and the Human class.
Human.new
$ ./inheritance.rbFirst the Being class is created. The derived Human class also calls the costructor of its parent.
Being class created
Being class created
Human class created
An Object may be involved in comlicated relationships. A single object can have multiple ancestors. Ruby has a method
ancestors
which gives a list of ancestors for a specific class. Each Ruby object is automatically a descendant of Object, BasicObject classes and Kernel Module. They are built-in the core of the Ruby language.
#!/usr/bin/rubyWe have four classes in this example. A Human is a Mammal a Living and a Being.
class Being
end
class Living < Being
end
class Mammal < Living
end
class Human < Mammal
end
p Human.ancestors
p Human.ancestorsWe print the ancestors of a Human class.
$ ./ancestors.rbA Human class has three custom and three built-in ancestors.
[Human, Mammal, Living, Being, Object, Kernel, BasicObject]
A more complex example follows.
#!/usr/bin/rubyWe have four classes. The inheritance hierarchy is more complicated. The Human and the Animal classes inherit from the Being class. And the Dog class inherits directly from the Animal class and further from the Being class. We also use a class variable to count the number of beings created.
class Being
@@count = 0
def initialize
@@count += 1
puts "Being class created"
end
def show_count
"There are #{@@count} beings"
end
end
class Human < Being
def initialize
super
puts "Human is created"
end
end
class Animal < Being
def initialize
super
puts "Animal is created"
end
end
class Dog < Animal
def initialize
super
puts "Dog is created"
end
end
Human.new
d = Dog.new
puts d.show_count
@@count = 0We define a class variable. A class variable begins with @@ sigils and it belongs to the class, not to the instance of the class. We use it to count the number of beins created.
def initializeEach time the Being class is instantiated, we increase the @@count variable by one. This way we keep track of the number of instances created.
@@count += 1
puts "Being class created"
end
class Animal < BeingThe Animal inherits from the Being and the Dog inherits from the Animal. Further, the Dog inherits from the Being as well.
...
class Dog < Animal
...
Human.newWe create instances from the Human and from the Dog classes. We call the show_count method on the Dog object. The Dog class has no such method; the grandparent's (Being) method is called then.
d = Dog.new
puts d.show_count
$ ./inheritance2.rbThe Human object calls two constructors. The Dog object calls three constructors. There are two Beings instantiated.
Being class created
Human is created
Being class created
Animal is created
Dog is created
There are 2 beings
Inheritance does not play role in the visibility of methods and data members. This is a notable difference from many common object-oriented programming languages.
In C# or Java, private data members and methods are not inherited. Only public and protected. In contrast to this, private data members and methods are inherited in Ruby as well. The visibility of data members and methods is not affected by inheritance in Ruby.
#!/usr/bin/rubyIn the example we have two classes. The Derived class inherits from the Base class. It inherits all three methods and one data field.
class Base
def initialize
@name = "Base"
end
private
def private_method
puts "private method called"
end
protected
def protected_method
puts "protected_method called"
end
public
def get_name
return @name
end
end
class Derived < Base
def public_method
private_method
protected_method
end
end
d = Derived.new
d.public_method
puts d.get_name
def public_methodIn the public_method of the Derived class we call one private and one protected method. They were defined in the parent class.
private_method
protected_method
end
d = Derived.newWe create an instance of the Derived class. We call the public_method. And call the get_name method, which returns the private @name variable. Remember that all instance variables are private in Ruby. The get_name method returns the variable regardless of the fact, that it is private and defined in the parent class.
d.public_method
puts d.get_name
$ ./inheritance3.rbThe output of the example confirms that in Ruby language, public, protected, private methods and private member fields are inherited by child objects from their parents.
private method called
protected_method called
Base
The super method
Thesuper
method calls a method of the same name in the parent's class. If the method has no arguments it automatically passes all its arguments. If we write super()
no arguments are passed to parent's method. #!/usr/bin/rubyIn the example, we have two classes in a hierarchy. They both have a show method. The show method in the Derived class calls the show method in the Base class using the
class Base
def show x=0, y=0
p "Base class, x: #{x}, y: #{y}"
end
end
class Derived < Base
def show x, y
super
super x
super x, y
super()
end
end
d = Derived.new
d.show 3, 3
super
method. def show x, yThe
super
super x
super x, y
super()
end
super
method without any arguments calls the parent's show method with the arguments that were passed to the show method of the Derived class. In our case x=3, y=3. The super()
method passes no arguments to the parent's show method. $ ./super.rbOutput of the example.
"Base class, x: 3, y: 3"
"Base class, x: 3, y: 0"
"Base class, x: 3, y: 3"
"Base class, x: 0, y: 0"
This was the first part of the description of OOP in Ruby.
0 comments:
Post a Comment