An Introduction to Object-Oriented Programming with Ruby

Object-oriented programming (OOP) is a programming paradigm that organizes code into objects, which are self-contained units that encapsulate data and behavior. OOP promotes code reusability, scalability and maintainability by enabling developers to create modular, loosely coupled components. This is in contrast to procedural programming, which structures programs as sequences of steps and reusable functions.

Ruby is a pure object-oriented language, meaning everything in Ruby is an object, even primitives like numbers and booleans. It was designed with a strong emphasis on OOP and provides an elegant, readable syntax for working with objects. In this tutorial, we‘ll explore the fundamentals of object-oriented programming in Ruby.

Classes and Objects

The core concept in OOP is the object, which is an instance of a class. A class is essentially a blueprint that defines the data and behavior for objects of that type. Let‘s create a simple Person class in Ruby:

class Person
  def initialize(name, age) 
    @name = name
    @age = age
  end

  def introduce
    puts "Hi, my name is #{@name} and I‘m #{@age} years old."
  end
end

Here we‘ve defined a Person class with an initialize method that takes a name and age parameter. In Ruby, initialize is a special method that gets called when a new object is instantiated from a class. We‘ve also defined an instance method called introduce.

The @ symbol before the variable names denotes instance variables. These are variables that belong to individual objects, so each Person object we create can have its own unique @name and @age values.

To create a Person object, we use the new keyword:

person1 = Person.new("Alice", 25)
person2 = Person.new("Bob", 30)

This creates two different Person objects with different names and ages. We can call methods on these objects like so:

person1.introduce  #=> "Hi, my name is Alice and I‘m 25 years old."
person2.introduce  #=> "Hi, my name is Bob and I‘m 30 years old."

Attribute Accessors

In our Person class above, the @name and @age variables are only accessible within the class. If we want to read or modify these values from outside the class, we need to define attribute accessor methods.

Ruby provides a convenient way to automatically create these accessor methods using the attr_accessor, attr_reader and attr_writer methods. Let‘s add an attr_accessor to our Person class:

class Person
  attr_accessor :name, :age

  def initialize(name, age)
    @name = name 
    @age = age
  end

  def introduce 
    puts "Hi, my name is #{@name} and I‘m #{@age} years old."
  end
end

The attr_accessor :name, :age line automatically creates getter and setter methods for the @name and @age variables. This allows us to read and write those variables from outside the class:

person1 = Person.new("Alice", 25)
person1.name    #=> "Alice"
person1.age     #=> 25
person1.age = 26 
person1.introduce  #=> "Hi, my name is Alice and I‘m 26 years old."

If we only want to allow read access, we can use attr_reader. And if we only want to allow write access, we can use attr_writer.

Inheritance

Another key principle of OOP is inheritance, which allows classes to inherit attributes and methods from other classes. This enables developers to create hierarchies of related classes and reuse common code.

In Ruby, we use the < symbol to inherit from a parent class (also called a superclass). Let‘s create a Student class that inherits from our Person class:

class Student < Person
  def initialize(name, age, school) 
    super(name, age)
    @school = school
  end

  def introduce
    puts "Hi, my name is #{@name}, I‘m #{@age}, and I attend #{@school}."
  end
end

The Student class inherits the @name and @age attributes from Person. In the initialize method, we call super to invoke the initialize method of the parent class and pass it the name and age arguments. We then set an additional @school instance variable.

We also override the introduce method to provide a Student-specific implementation. Now we can create Student objects like so:

student1 = Student.new("Charlie", 18, "Harvard")
student1.introduce #=> "Hi, my name is Charlie, I‘m 18, and I attend Harvard."

Public vs Private Methods

In Ruby, methods are public by default, meaning they can be called from anywhere in the program. But we can also define private methods, which can only be called from within the class itself.

Private methods are often used for internal implementation details that don‘t need to be exposed to the rest of the program. Let‘s add a private method to our Person class:

class Person
  attr_accessor :name, :age

  def initialize(name, age)
    @name = name
    @age = age
  end

  def introduce
    puts "Hi, my name is #{@name} and I‘m #{@age} years old."
    secret_info
  end

  private 

  def secret_info
    puts "Shh, don‘t tell anyone but my real age is #{@age + 5}!"  
  end
end

Here we‘ve added a private method called secret_info that prints out a message with a fake age. We‘ve also called this method from the public introduce method.

If we try to call secret_info from outside the class, we‘ll get an error:

person1 = Person.new("Alice", 25)
person1.secret_info  #=> NoMethodError (private method `secret_info‘ called)

But calling introduce works fine, since introduce is a public method and it‘s allowed to call the private secret_info method:

person1.introduce
#=> Hi, my name is Alice and I‘m 25 years old. 
#   Shh, don‘t tell anyone but my real age is 30!

Class Variables and Class Methods

In addition to instance variables, which belong to individual objects, Ruby also supports class variables, which are shared among all objects of a class. Class variables are denoted with @@ and are typically used to store class-level data.

Let‘s add a class variable to track the number of Person objects that have been created:

class Person
  @@count = 0

  def initialize(name, age)
    @name = name
    @age = age
    @@count += 1
  end

  def self.count
    @@count
  end
end

Here we‘ve initialized a @@count class variable to 0. In the initialize method, we increment @@count every time a new Person object is created.

We‘ve also defined a class method called count that returns the value of @@count. Class methods are denoted with self. and are called on the class itself, not on individual objects.

Now we can use the count method to see how many Person objects have been created:

puts Person.count #=> 0

person1 = Person.new("Alice", 25)
person2 = Person.new("Bob", 30) 

puts Person.count #=> 2

Wrapping Up

This tutorial covered the key concepts of object-oriented programming in Ruby, including:

  • Classes and objects
  • Attributes and methods
  • Attribute accessors
  • Inheritance
  • Public and private methods
  • Class variables and methods

These concepts form the foundation of OOP in Ruby and are used extensively in Ruby programming. By mastering these fundamentals, you‘ll be able to write clean, modular, reusable code and take full advantage of Ruby‘s object-oriented capabilities.

Ruby‘s elegant, expressive syntax makes it a pleasure to work with objects and a great language for learning OOP. Its rich standard library and vast ecosystem of gems (libraries) are also heavily object-oriented.

To dive deeper into OOP with Ruby, explore more advanced topics like modules, mixins, polymorphism, and metaprogramming. The Ruby documentation and popular books like "Practical Object-Oriented Design in Ruby" are great resources.

Stay curious, keep coding, and enjoy your journey into the wonderful world of object-oriented Ruby!

Similar Posts