Contents
Object-Oriented Programming in Ruby
When we refer to object-oriented programming, we mean that our code is organized around objects. These objects are tangible instances that belong to various categories.
To illustrate this concept, let’s consider a car as an object. The class representing the car would be vehicle. A class serves as a blueprint for an object and outlines its attributes and behaviors. For a vehicle, the attributes might include the make, model, and whether it has a sunroof. These features are defined in the class, and each object (or instance) of the class shares these attributes, although the specific values may differ between instances. For instance, a Toyota Camry might be red, while a Honda Accord could be blue. This encapsulation of real-life scenarios into code is the essence of object-oriented programming, allowing us to create various instances from a single concept.
Defining a Class in Ruby
Class Syntax:
OOP Concepts
class ClassName
end
Implementing OOP Concepts with Ruby
Ruby fully supports the object-oriented programming paradigm, enabling the creation of classes and their corresponding objects. As discussed, objects are instances of a class, and a class provides the framework for these objects. Each class specifies attributes and behaviors, while the objects represent real-world entities.
A class comprises two main components: data and methods. For instance, if we have a class called Vehicle, we can define the following attributes:
1. Make
2. Model
We need methods to access these attributes. To accomplish this, we use a constructor that takes values for the attributes and assigns them to the appropriate variables for each object.
Constructor Syntax:
def initialize(attribute1, attribute2)
@attribute1 = attribute1
@attribute2 = attribute2
end
The initialize
method is defined within the class, and its structure resembles a standard function. It can accept any number of arguments. The @
symbol denotes the object’s attributes.
Example:
class Vehicle
def initialize(make, model)
@make = make
@model = model
end
end
To create an instance of a class, we use a familiar method similar to creating hashes or arrays. We utilize the new
function.
Syntax:
object_name = ClassName.new()
If there are parameters, they are typically passed in parentheses with new
, similar to regular methods.
Example:
class Vehicle
def initialize(make, model)
@make = make
@model = model
end
end
car1 = Vehicle.new('Toyota', 'Camry')
Now we have an object named car1
. Calling Vehicle.new()
creates a new instance, invoking the initialize
method. Thus, car1.make
will be Toyota and car1.model
will be Camry.
Let’s create a second object as well.
Example:
class Vehicle
def initialize(make, model)
@make = make
@model = model
end
# Defining Methods
def get_make
@make
end
def get_model
@model
end
end
# Creating objects
car1 = Vehicle.new('Toyota', 'Camry')
car2 = Vehicle.new('Honda', 'Accord')
puts 'Make of car1: ' + car1.get_make
puts 'Model of car1: ' + car1.get_model
puts 'Make of car2: ' + car2.get_make
puts 'Model of car2: ' + car2.get_model
Output:
Make of car1: Toyota
Model of car1: Camry
Make of car2: Honda
Model of car2: Accord
In this example, calling car1.get_make
invokes the get_make
method for car1
, returning the result ‘Toyota’. Similarly, calling car2.get_model
invokes the get_model
method for car2
, yielding ‘Accord’.
Typically, methods in Ruby return the last evaluated result, so we don’t need to explicitly use the return
keyword.
Instead of the following code:
def get_make
return @make
end
We can simply write:
def get_make
@make
end
Variable Scope
Variable scope refers to the areas in which a variable can be utilized. It can be global or local. Global variables are accessible throughout the code. To define a global variable, we use the $
symbol.
Example:
class Vehicle
# Creating global variable
$global_var = 'GLOBAL'
def initialize(make, model)
@make = make
@model = model
end
# Defining Methods
def get_make
@make
end
def get_model
@model
end
end
# Creating objects
car1 = Vehicle.new('Toyota', 'Camry')
car2 = Vehicle.new('Honda', 'Accord')
puts 'Make of car1: ' + car1.get_make
puts 'Model of car1: ' + car1.get_model
puts 'Make of car2: ' + car2.get_make
puts 'Model of car2: ' + car2.get_model
# Printing global variable
puts 'Global variable: ' + $global_var
Output:
Make of car1: Toyota
Model of car1: Camry
Make of car2: Honda
Model of car2: Accord
Global variable: GLOBAL
In the example above, we declared a global variable $global_var
. Since it is global, it can be accessed anywhere in the program. The value of $global_var
is ‘GLOBAL’. If we attempted to access car1.make
, it would produce an error because that variable is locally scoped to the class.
Modifying Attributes
Let’s illustrate this concept with another example.
Example:
class Vehicle
def initialize(make, model)
@make = make
@model = model
end
# Defining Methods
def get_make
@make
end
def get_model
@model
end
def change_model(new_model)
@model = new_model
end
end
# Creating object
car = Vehicle.new('Toyota', 'Camry')
puts 'Make of car: ' + car.get_make
puts 'Model of car: ' + car.get_model
# Modifying attribute
car.change_model('Corolla')
puts 'Updated model of car: ' + car.get_model
Output:
Make of car: Toyota
Model of car: Camry
Updated model of car: Corolla
In this example, we introduce a new method, change_model
, which allows us to modify the value of the model
attribute. Since the attributes cannot be accessed directly outside the class, we use methods to manipulate them. As shown, we pass a new model as an argument to the change_model
method, which updates the object’s model attribute accordingly.
Class Variables
Class variables differ from instance variables in that they belong to the class itself rather than individual objects. For instance variables like make
and model
, each object maintains its own copy, while class variables share a single copy among all objects. Class variables can be accessed by the instances of the class and are denoted by @@
.
Example:
class Vehicle
@@count = 0
def initialize(make, model)
@make = make
@model = model
@@count += 1
end
# Defining Methods
def get_make
@make
end
def get_model
@model
end
def self.get_count
@@count
end
end
# Creating objects
car1 = Vehicle.new('Toyota', 'Camry')
car2 = Vehicle.new('Honda', 'Accord')
puts 'Make of car1: ' + car1.get_make
puts 'Model of car1: ' + car1.get_model
puts 'Make of car2: ' + car2.get_make
puts 'Model of car2: ' + car2.get_model
puts 'Total number of vehicles created: ' + Vehicle.get_count.to_s
Output:
Make of car1: Toyota
Model of car1: Camry
Make of car2: Honda
Model of car2: Accord
Total number of vehicles created: 2
In this example, we utilize a class variable @@count
to track the number of objects created. We initialize count
to 0, and each time an object is instantiated, the initialize
method increments the count. Regardless of whether we call get_count
using car1
or car2
, it returns the same value, indicating the total number of vehicle objects created.
Using Accessor Methods
Instead of manually creating methods to access attributes, Ruby provides a simpler approach using attr_reader
, attr_writer
, and attr_accessor
. Let’s see how these work in the following example.
Example:
class Vehicle
attr_reader :make
attr_writer :model
def initialize(make, model)
@make = make
@model = model
end
end
car = Vehicle.new('Toyota', 'Camry')
puts 'Make of car: ' + car.make
puts 'Model of car: ' + car.model
# Changing the model
car.model = 'Corolla'
puts 'Updated model of car: ' + car.model
Output:
Make of car: Toyota
Model of car: Camry
Updated model of car: Corolla
Explanation: The expression is evaluated as:
(10 * 5) + (8 / 2) = 50 + 4 = 54
Example 1: C Program to Calculate the Area and Perimeter of a Rectangle
#include
int main() {
// Declare dimensions of the rectangle
int length = 7;
int width = 3;
// Declare variables to store the area and perimeter
int area, perimeter;
// Calculate area
area = length * width;
// Calculate perimeter
perimeter = 2 * (length + width);
// Print the results
printf("Area = %d\n", area);
printf("Perimeter = %d\n", perimeter);
return 0;
}
Output:
Area = 21
Perimeter = 20
Inheritance
Inheritance is a key principle in object-oriented programming, allowing one class to inherit the properties and methods of another. The class being inherited from is known as the “base” or “parent” class, while the inheriting class is the “derived” or “child” class.
Syntax:
class BaseClass
# code for base class
end
class DerivedClass < BaseClass
# code for derived class
end
The <
symbol indicates inheritance. The derived class inherits all attributes and methods from the base class. Note that this relationship is one-way: the base class does not inherit anything from the derived class.
Example:
Suppose we have a base class Device
and two derived classes: Laptop
and Smartphone
.
# Ruby program demonstrating Inheritance
class Device
def initialize(device_name, device_color)
@device_name = device_name
@device_color = device_color
end
def description
puts 'This is a general device.'
end
end
class Laptop < Device
def description
puts 'This is a laptop.'
end
end
class Smartphone < Device
def details
puts 'This is a smartphone.'
end
end
# Creating objects
laptop = Laptop.new('Dell', 'Silver')
smartphone = Smartphone.new('iPhone', 'Black')
laptop.description
smartphone.description
smartphone.details
Output:
This is a laptop.
This is a general device.
This is a smartphone.
In the above code, the Laptop
and Smartphone
classes inherit from the Device
class. The Laptop
class overrides the description
method, while the Smartphone
class adds a new method called details
.
Using super
If a derived class has a method with the same name as a method in the base class but wants to access the base class’s method as well, it can use the super
keyword.
Example:
# Ruby program using super
class Device
def initialize(device_name, device_color)
@device_name = device_name
@device_color = device_color
end
def description
puts 'This is a general device.'
end
end
class Laptop < Device
def description
puts 'This is a laptop.'
super
end
end
laptop = Laptop.new('Dell', 'Silver')
laptop.description
Output:
This is a laptop.
This is a general device.
In this case, the Laptop
class calls super
to invoke the description
method from the Device
class, executing both the derived and base class methods.
Attributes in the Derived Class
If the derived class needs its own attributes in addition to those from the base class, you can pass the base class attributes using super
.
# Ruby program with derived class attributes
class Device
attr_accessor :device_name, :device_color
def initialize(device_name, device_color)
@device_name = device_name
@device_color = device_color
end
end
class Laptop < Device
attr_accessor :model
def initialize(device_name, device_color, model)
super(device_name, device_color)
@model = model
end
end
laptop = Laptop.new('Dell', 'Silver', 'XPS')
puts laptop.device_name
puts laptop.device_color
puts laptop.model
Output:
Dell
Silver
XPS
In this example, the Laptop
class has an additional attribute model
. When an object is created, super
is used to initialize the attributes of the Device
class before initializing the Laptop
-specific attributes.
Modules
Modules are containers for methods and constants that can be reused in multiple classes. Unlike classes, modules cannot create objects. They are often used to group related methods and constants and can be included in classes.
Example:
module Features
FEATURE_ONE = 'Wi-Fi'
FEATURE_TWO = 'Bluetooth'
def feature_list
puts 'This device supports Wi-Fi and Bluetooth.'
end
end
To use the data and methods in a module, include the module in a class using the include
keyword.
Mixins with include
Mixins allow classes to include modules, effectively adding the module’s methods to the class.
Example:
# Ruby program using mixins
module Features
def feature_list
puts 'This device supports Wi-Fi and Bluetooth.'
end
end
class Laptop
include Features
attr_reader :model
def initialize(model)
@model = model
end
end
laptop = Laptop.new('XPS')
laptop.feature_list
puts laptop.model
Output:
This device supports Wi-Fi and Bluetooth.
XPS
Mixins with extend
Using extend
incorporates the module’s methods at the class level, allowing the class itself to use the module’s methods instead of instances.
Example:
# Ruby program extending mixins
module Features
def feature_list
puts 'This class supports advanced features.'
end
end
class Laptop
extend Features
attr_reader :model
def initialize(model)
@model = model
end
end
Laptop.feature_list
laptop = Laptop.new('XPS')
puts laptop.model
Output:
This class supports advanced features.
XPS
In this case, extend
allows the Laptop
class itself to use the feature_list
method, rather than an instance of the class.
Public and Private Methods
By default, all methods in a class are public. You can explicitly set methods as private
to restrict their access.
Example:
# Ruby program demonstrating public and private methods
class Device
def initialize(device_name, device_color)
@device_name = device_name
@device_color = device_color
end
public
def display_info
greet
puts "Device name: #{@device_name}"
puts "Device color: #{@device_color}"
end
private
def greet
puts 'Hello, user!'
end
end
device = Device.new('Laptop', 'Silver')
device.display_info
Output:
Hello, user!
Device name: Laptop
Device color: Silver
Ruby Class & Object
Ruby is a powerful object-oriented programming language. Some of the key characteristics of object-oriented programming include encapsulation, inheritance, polymorphism, data abstraction, and operator overloading. In Ruby, classes and objects play a central role in structuring code.
A class can be viewed as a template or blueprint from which objects (instances of the class) are created. For instance, if we think of a class named “Vehicle,” specific types of vehicles such as cars, trucks, and bikes would be objects or instances of that class. Similarly, if “Employee” is a class, objects of that class might be a manager, developer, or assistant.
Defining a Class in Ruby:
In Ruby, defining a class is straightforward. You start with the class
keyword, followed by the name of the class. The name of the class should begin with a capital letter.
Syntax:
class ClassName
# class body
end
A class is encapsulated between the class
and end
keywords. All the data members (attributes) and methods are defined inside this block.
Example:
# Class named Animal
class Animal
# Class variables
@@num_legs = 4
@@num_eyes = 2
end
Creating Objects using the new
Method in Ruby:
Objects are instances of a class, and they are easily created using the new
method. Multiple objects can be created from a single class.
Syntax:
object_name = ClassName.new
Example:
# Class named Book
class Book
# Class variable
@@pages = 100
end
# Creating objects of the Book class
novel = Book.new
magazine = Book.new
In this example, Book
is the class, and @@pages
is a class variable. We create two objects, novel
and magazine
, using the new
method.
Defining Methods in Ruby:
Methods in Ruby are defined using the def
keyword followed by the method name. By convention, method names are in lowercase. A method is terminated with the end
keyword.
Syntax:
def method_name
# method body
end
Example:
# Ruby program to demonstrate methods
# Defining class Calculator
class Calculator
# Defining a method
def add
# Performing addition
puts 5 + 10
end
end
# Creating an object of Calculator
calc = Calculator.new
# Calling the method
calc.add
Output:
15
Passing Parameters to the new
Method:
You can pass parameters to the new
method, which are used to initialize instance variables within the class. To handle parameters, you need to define an initialize
method. The initialize
method is a special method in Ruby that is automatically invoked when the new
method is called with arguments.
Example:
# Ruby program to illustrate passing parameters
# to the new method
# Defining class Laptop
class Laptop
# initialize method
def initialize(brand, model, price)
# Instance variables
@laptop_brand = brand
@laptop_model = model
@laptop_price = price
# Displaying values
puts "Brand: #@laptop_brand"
puts "Model: #@laptop_model"
puts "Price: $#@laptop_price"
puts "\n"
end
end
# Creating objects and passing parameters
# to the new method
laptop1 = Laptop.new("Dell", "XPS 13", 1200)
laptop2 = Laptop.new("Apple", "MacBook Air", 999)
Output:
Brand: Dell
Model: XPS 13
Price: $1200
Brand: Apple
Model: MacBook Air
Price: $999
Private Classes in Ruby
Ruby has a unique approach to method visibility compared to other languages like Java. In Ruby, it’s more about which object is calling the method, as everything in Ruby, including classes, is an object.
Private Classes in Ruby
When a class or constant is declared private in Ruby, it means the class cannot be called with an explicit receiver. A private class or constant can only be accessed with an implicit receiver. In Ruby, private classes can be defined inside another class as nested sub-classes and declared as private constants. This makes the private class accessible only from within the outer class.
Syntax:
private_constant :ClassName
Example:
# Defining the outer class
class Universe
# Defining a public sub-class
class Heroes
def show
puts "Superman saves the day!"
end
end
# Another public sub-class
class Villains
def show
puts "Lex Luthor schemes again!"
end
end
end
# Calling the public methods
Universe::Heroes.new.show
Universe::Villains.new.show
Output:
Superman saves the day!
Lex Luthor schemes again!
In this example, both the Heroes
and Villains
classes are public, meaning they can be accessed directly using an explicit receiver.
Example of a Private Class:
# Defining the outer class
class Universe
# Defining a private sub-class
class Heroes
def show
puts "Superman saves the day!"
end
end
# Defining a public sub-class
class Villains
def show
puts "Lex Luthor schemes again!"
end
end
# Making the Heroes class private
private_constant :Heroes
end
# Calling the public method of Villains class
Universe::Villains.new.show
# Attempting to access the private class (will raise an error)
Universe::Heroes.new.show
Output:
Lex Luthor schemes again!
main.rb:18:in `': private constant Universe::Heroes referenced (NameError)
In this example, since the Heroes
class is declared private using private_constant
, it cannot be accessed directly by external code, while the Villains
class remains publicly accessible.
Accessing a Private Class from the Outer Class
Although private classes cannot be accessed explicitly from outside the outer class, they can still be accessed from within the outer class. Here’s an example demonstrating how this works:
# Defining the outer class
class Universe
# Defining a private sub-class
class Heroes
def show
puts "Superman saves the day!"
end
end
# Defining another private sub-class
class Villains
def show
puts "Lex Luthor schemes again!"
end
end
# Method inside outer class to access private classes
def battle
Heroes.new.show
Villains.new.show
end
# Making the sub-classes private
private_constant :Heroes
private_constant :Villains
end
# Accessing the private classes through the outer-class method
Universe.new.battle
# Attempting to access private classes explicitly (will raise an error)
Universe::Heroes.new.show
Universe::Villains.new.show
Output:
Superman saves the day!
Lex Luthor schemes again!
main.rb:25:in `': private constant Universe::Heroes referenced (NameError)
main.rb:26:in `': private constant Universe::Villains referenced (NameError)
Freezing Objects Ruby
In Ruby, any object can be frozen by invoking the Object#freeze
method. Once an object is frozen, it becomes immutable: its instance variables cannot be altered, new singleton methods cannot be defined, and if it’s a class or module, you cannot modify its methods. To check if an object is frozen, you can use the Object#frozen?
method, which returns true
if the object is frozen and false
otherwise.
The freeze
method essentially turns an object into a constant by preventing any further modifications. It’s important to note that once an object is frozen, it cannot be unfrozen.
Syntax:
ObjectName.freeze
Example:
# Ruby program to demonstrate freezing of an object
# Define a simple class for Product
class Product
# Constructor to initialize product details
def initialize(name, price)
@name = name
@price = price
end
# Accessor methods to retrieve values
def name
@name
end
def price
@price
end
# Setter methods to modify values
def name=(new_name)
@name = new_name
end
def price=(new_price)
@price = new_price
end
end
# Create a new Product object
item = Product.new("Laptop", 1000)
# Freeze the object to prevent modification
item.freeze
# Check if the object is frozen
if item.frozen?
puts "The product object is frozen and cannot be modified."
else
puts "The product object is still modifiable."
end
# Attempt to modify the frozen object
begin
item.name = "Tablet" # This will raise an error
item.price = 800
rescue => e
puts e.message # Outputs error message due to modification attempt
end
# Access and print the unchanged values
puts "Product Name: #{item.name}"
puts "Product Price: #{item.price}"
Output:
The product object is frozen and cannot be modified.
can't modify frozen Product
Product Name: Laptop
Product Price: 1000
Polymorphism in Ruby
In Ruby, variables do not have specific types like in other programming languages. Every variable is considered an object, and each object can be modified individually. You can also add methods or functions to every object dynamically. Object-Oriented Programming (OOP) plays a significant role in this flexibility. Among the core principles of OOP are Inheritance, Encapsulation, and Polymorphism.
Polymorphism is one such concept. The term comes from two Greek words: “Poly” meaning “many” and “Morph” meaning “forms.” Essentially, polymorphism allows one method to execute in different ways depending on the object it’s acting on. By passing different objects to the same method, we can achieve different outcomes, avoiding the need for lengthy if-else
statements.
In Ruby, polymorphism allows classes to have different functionalities while sharing a common interface. Polymorphism can be achieved in two main ways:
- Polymorphism using Inheritance
- Polymorphism using Duck-Typing
Polymorphism using Inheritance
Inheritance allows a child class to inherit properties and methods from a parent class. Ruby makes it easy to implement polymorphism using inheritance. Here’s an example:
Example:
# Ruby example of Polymorphism using Inheritance
class Instrument
def play
puts "Playing an instrument"
end
end
# Using inheritance
class Guitar < Instrument
def play
puts "Playing the guitar"
end
end
# Using inheritance
class Piano < Instrument
def play
puts "Playing the piano"
end
end
# Creating objects and calling the same method
instrument = Instrument.new
instrument.play
instrument = Guitar.new
instrument.play
instrument = Piano.new
instrument.play
Output:
Playing an instrument
Playing the guitar
Playing the piano
Polymorphism using Duck-Typing
In Ruby, we focus on what an object can do rather than its class. This is known as Duck-Typing, where an object’s abilities are more important than its type. The idea is: “If it looks like a duck and quacks like a duck, it’s probably a duck.” Here’s an example to illustrate this concept:
Example:
# Ruby example of Polymorphism using Duck Typing
# Defining a Store class
class Store
def order(customer)
customer.payment_type
customer.discount
end
end
# Defining two different customer classes
class RegularCustomer
def payment_type
puts "Payment with credit card"
end
def discount
puts "No discount available"
end
end
class PremiumCustomer
def payment_type
puts "Payment with loyalty points"
end
def discount
puts "10% discount applied"
end
end
# Polymorphism in action
store = Store.new
puts "Regular customer making a purchase:"
customer = RegularCustomer.new
store.order(customer)
puts "\nPremium customer making a purchase:"
customer = PremiumCustomer.new
store.order(customer)
Output:
Regular customer making a purchase:
Payment with credit card
No discount available
Premium customer making a purchase:
Payment with loyalty points
10% discount applied
Ruby Constructors
A constructor is a special method in a class that is automatically invoked when an instance of the class is created. Like regular methods, constructors can contain a set of instructions that are executed upon object creation.
Key Points about Constructors:
- A constructor is defined using the initialize and def keywords.
- It is treated as a special method in Ruby.
- Constructors cannot be overloaded in Ruby.
- Constructors are not inherited.
- They return an instance of the class.
- Note: Whenever an object of a class is created using the new method, the initialize method is called internally. Any arguments passed to new are automatically forwarded to the initialize method.
- Unlike other programming languages, constructors in Ruby have a unique name.
Syntax:
# Ruby program demonstrating public access control
class Greeting
# Public method without the public keyword
def hello
puts "Hello, world!"
end
# Using public keyword to define public methods
public
def good_morning
puts "Good morning!"
end
def good_night
puts "Good night!"
# Calling hello method
hello
# Calling hello using self
self.hello
end
end
# Creating an object
greet = Greeting.new
# Calling public methods
greet.hello
greet.good_morning
greet.good_night
Output:
Hello, world!
Good morning!
Good night!
Hello, world!
Hello, world!
Private Methods
Private methods cannot be accessed outside the class; they can only be called from within the class. Unlike public methods, you cannot use the self
keyword to call private methods. The initialize
method, which is used to initialize instance variables, is inherently private and cannot be made public.
Example:
# Ruby program demonstrating private access control
class Person
def initialize(name)
@name = name
puts "Person's name is #{@name}."
end
# Public method
def greet
puts "Hello!"
# Calling private method
private_greet
end
private
# Private method
def private_greet
puts "This is a private greeting."
end
end
# Creating an object
person = Person.new("John")
# Calling public method
person.greet
# Attempting to call private method (will raise an error)
person.private_greet
Output:
Person's name is John.
Hello!
This is a private greeting.
source_file.rb:27:in `': private method `private_greet' called for # (NoMethodError)
Protected Methods
Protected methods can be called only within the class and its subclasses. These methods are not accessible outside the class definition or by external objects. However, they can be accessed by any object of the same class or its subclass.
Example:
# Ruby program demonstrating protected access control
class Animal
def initialize(type)
@type = type
# Calling protected method
protected_sound
end
# Public method
def show_type
puts "This is a #{@type}."
end
protected
# Protected method
def protected_sound
puts "This animal makes a sound."
end
end
class Dog < Animal
def bark
puts "The dog barks!"
# Calling protected method from parent class
protected_sound
end
end
# Creating objects
animal = Animal.new("generic animal")
dog = Dog.new("dog")
# Calling public method
animal.show_type
dog.bark
# Attempting to call protected method from outside (will raise an error)
dog.protected_sound
Ruby Access Control
Access control is an essential part of object-oriented programming that restricts the visibility of methods and fields to protect data from accidental modification. Ruby, however, approaches access control somewhat differently than many other languages.
Key Points:
1. Instance and Class Variables: Always private in terms of visibility.
2. Applicability: Access controls in Ruby only apply to methods, not to instance or class variables.
3. Inheritance: Private methods in Ruby can be inherited, similar to public and protected methods.
4. Access Control Conditions: It depends on where the method is called (inside or outside the class definition) and whether the self keyword is used.
Public Methods
Public methods can be called by anyone. By default, all methods in Ruby are public unless explicitly declared otherwise.
Example:
# Ruby program demonstrating calling private and protected methods inside a public method
class ExampleClass
# Public method
def method_one
puts "Public method_one is called"
# Calling protected and private methods within the public method
method_two
method_three
end
# Defining a protected method
protected
def method_two
puts "Protected method_two is called"
end
# Defining a private method
private
def method_three
puts "Private method_three is called"
end
end
# Creating an object of the class
obj = ExampleClass.new
# Calling the public method
obj.method_one
Output:
Public method_one is called
Protected method_two is called
Private method_three is called
Inheritance of Private Methods
Unlike many other object-oriented programming languages, Ruby allows private methods to be inherited by subclasses.
Example:
# Ruby program demonstrating calling private and protected methods inside a public method
class ExampleClass
# Public method
def method_one
puts "Public method_one is called"
# Calling protected and private methods within the public method
method_two
method_three
end
# Defining a protected method
protected
def method_two
puts "Protected method_two is called"
end
# Defining a private method
private
def method_three
puts "Private method_three is called"
end
end
# Creating an object of the class
obj = ExampleClass.new
# Calling the public method
obj.method_one
Output:
Public method_one is called
Protected method_two is called
Private method_three is called
Ruby Encapsulation
Encapsulation is the concept of wrapping data and the methods that operate on that data into a single unit, typically a class. This mechanism binds code and the data it manipulates, providing a protective shield that restricts access to the data directly from outside the class.
In technical terms, encapsulation in Ruby involves hiding the internal state of an object and requiring all interactions to be performed through methods. The data (or variables) of a class are typically made private and are accessible only through public methods within the class.
Encapsulation is achieved in Ruby by defining instance variables as private and using public methods to get and set the values of those variables.
Example:
# Ruby program demonstrating encapsulation
class Customer
def initialize(id, name, address)
# Instance variables
@customer_id = id
@customer_name = name
@customer_address = address
end
# Method to display customer details
def display_details
puts "Customer ID: #{@customer_id}"
puts "Customer Name: #{@customer_name}"
puts "Customer Address: #{@customer_address}"
end
end
# Creating objects of the Customer class
customer1 = Customer.new("1", "Alice", "123 Maple Street")
customer2 = Customer.new("2", "Bob", "456 Oak Avenue")
# Calling methods to display details
customer1.display_details
customer2.display_details
Output:
Customer ID: 1
Customer Name: Alice
Customer Address: 123 Maple Street
Customer ID: 2
Customer Name: Bob
Customer Address: 456 Oak Avenue
Ruby Mixins
Before diving into Ruby Mixins, it’s helpful to understand the basics of Object-Oriented Programming. If you’re not familiar with these concepts, it might be good to review them first. In object-oriented languages, a class can sometimes inherit features from more than one parent class. This is known as multiple inheritance. However, Ruby doesn’t support multiple inheritance directly; instead, it uses a feature called mixins.
Mixins in Ruby allow modules to “mixin” their methods into a class using the include
method. This provides a controlled way to add functionalities to classes. When a class includes a module, the module’s methods become part of that class. In Ruby, a module that is included in a class is referred to as a mixin.
Example:
# Ruby program demonstrating mixins
# Module containing two methods
module ModuleA
def method1
puts 'This is method1 from ModuleA.'
end
def method2
puts 'This is method2 from ModuleA.'
end
end
# Another module containing two methods
module ModuleB
def method3
puts 'This is method3 from ModuleB.'
end
def method4
puts 'This is method4 from ModuleB.'
end
end
# Creating a class that includes both modules
class SampleClass
include ModuleA
include ModuleB
def class_method
puts 'This is a method from SampleClass.'
end
end
# Creating an object of the class
sample_object = SampleClass.new
# Calling methods from the included modules
sample_object.method1
sample_object.method2
sample_object.method3
sample_object.method4
sample_object.class_method
Output:
This is method1 from ModuleA.
This is method2 from ModuleA.
This is method3 from ModuleB.
This is method4 from ModuleB.
This is a method from SampleClass.
Instance Variables in Ruby
Ruby has four different types of variables: Local variables, Instance variables, Class variables, and Global variables. An instance variable in Ruby starts with the @
symbol and its scope is limited to the specific object in which it resides. Even though two objects may belong to the same class, they can have different values for their instance variables.
Characteristics of Instance Variables in Ruby
1. Instance variables have a value of nil before they are initialized.
2. All instance variables are private by default.
3. An object’s instance variables can only be accessed by the instance methods of that specific object.
4. Ruby’s instance variables do not need explicit declaration, providing a flexible object structure.
5. Every instance variable is dynamically added to an object when it is first referenced.
6. An instance variable belongs to the object itself; each object of a class has its own set of instance variables.
7. One instance object can modify the values of its instance variables without affecting any other instance.
8. An instance variable can be used by multiple class methods, except when the method is static.
Example :
# Ruby program demonstrating instance variables using a constructor
class Person
# Constructor
def initialize()
# Instance variable
@name = "John Doe"
end
# Method to display details
def display_details
puts "Person's name is #@name"
end
end
# Creating an object of the class Person
person = Person.new()
# Calling the method to display instance variable
person.display_details
Output:
Person's name is John Doe
Data Abstraction in Ruby
Data abstraction refers to representing important details while hiding the inner workings of a system. This concept is key in object-oriented programming. In Ruby, abstraction allows developers to focus on a few high-level functionalities at a time by isolating the implementation details from the interface.
Real-life Examples of Abstraction Making a Phone Call:
When making a call, a person simply dials the number and presses the call button. The caller does not need to understand the intricate working of the phone’s internal system. This separation of user interaction and underlying complexity exemplifies abstraction.
Using a Television: Users can turn the television on or off, change channels, or adjust the volume without knowing the internal mechanics of how these features work. This simplified interface hides the complex implementation behind it.
Data Abstraction in Modules
In Ruby, modules are collections of methods, classes, and constants. For example, the sqrt() method in the Math module is used to calculate the square root of a number. Users call this method without needing to know the internal algorithm that performs the calculation.
Data Abstraction in Classes
Classes allow data abstraction by grouping information and methods using access control (private, protected, and public). By using these access specifiers, a class can decide which information is accessible and which is hidden.
Data Abstraction Using Access Control
Ruby provides three levels of access control: public, private, and protected. These are used to implement data abstraction in classes:
- Public Members: Can be accessed from anywhere in the program.
- Private Members: Can only be accessed from within the class in which they are declared. They cannot be accessed from outside the class.
Example: Data Abstraction Using Access Control
# Ruby program to demonstrate data abstraction
class Person
# Defining a public method
public
def display_info
puts "This is a public method."
# Calling a private method inside the public method
secret_method
end
# Defining a private method
private
def secret_method
puts "This is a private method."
end
end
# Creating an object of the class Person
person = Person.new
# Calling the public method of the class Person
person.display_info
Output:
This is a public method.
This is a private method.
Static Keywords in Ruby
In many programming languages, the static
keyword is primarily used for memory management and sharing the same method or variable across multiple objects of a class. While Ruby does not have an explicit static
keyword, similar functionality can be achieved using class variables and class methods.
Static Variables in Ruby
Ruby allows for class-level variables, often referred to as static variables in other languages. These variables are shared across all instances of a class. In Ruby, static variables are implemented using class variables. Class variables begin with @@
and have a lifespan that spans the entire lifetime of the program.
Example: Static Variable Using Class Variable
Here’s an example demonstrating the use of a static (class) variable in Ruby:
# Ruby program to demonstrate static variable
class Student
# Class variable
@@student_count = 0
def initialize
@@student_count += 1
puts "Number of Students = #{@@student_count}"
end
end
# Creating objects of the class Student
s1 = Student.new
s2 = Student.new
s3 = Student.new
s4 = Student.new
Output:
Number of Students = 1
Number of Students = 2
Number of Students = 3
Number of Students = 4