Lesson 1
Exploring Encapsulation in Ruby
Introduction

Welcome! Today's subject is encapsulation, a cornerstone of object-oriented programming (OOP). Encapsulation bundles data and the operations that we perform on them into one unit, namely, an object. It protects data from unwanted alterations, ensuring the creation of robust and maintainable software.

Prepare yourself for an exciting journey as we delve into how encapsulation works in Ruby and explore the vital role it plays in data privacy.

Unraveling Encapsulation

Starting with the basics, encapsulation involves combining data and the methods that modify this data into a single unit known as a class. It protects the data within an object from external interference.

In classes, you will often work with instance variables. These variables are declared with a @ in front and are central to encapsulation in Ruby. They hold data specific to an object and persist across different methods within the same object. These variables are private by default, meaning they can’t be accessed directly from outside the object, which helps maintain data privacy and integrity.

To illustrate, consider a Ruby class representing a bank account. Without encapsulation, the account balance would be accessible to outside functions and subject to unsolicited alterations from within. However, with encapsulation, we can provide a standardized and streamlined approach to alter the balance, like depositing or withdrawing.

Ruby
1class BankAccount 2 def initialize(account_no, balance) 3 @account_no = account_no # Instance variable 4 @balance = balance # Instance variable 5 end 6 7 # Methods for depositing and withdrawing will be defined here 8end
Encapsulation: Guardian of Data Privacy

Encapsulation restricts direct access to an object's data and prevents unwanted data alteration. This principle is comparable to window blinds, allowing you to look out while preventing others from peeping in.

In Ruby, encapsulation pertains to marking methods as private, which is integral to data privacy. Private methods can only be accessed from within the class where they are defined.

To illustrate, let's consider a Ruby class named Person, which includes a private method name.

Ruby
1class Person 2 def initialize(name) 3 @name = name 4 end 5 6 def get_name 7 name # Accessor method 8 end 9 10 private 11 12 def name 13 @name 14 end 15end 16 17person = Person.new('Alice') 18puts person.get_name # Accessing private method via a public method. Output: Alice 19puts person.name # Error: private method `name' called

In this example, name is private, and get_name enables us to access name. We specify that direct access to the name method is not allowed.

Getter and Setter Methods in Encapsulation

In encapsulation, getters and setters are used to control access to an object's attributes. Initially, you can manually define these methods within a class to handle the reading and writing of instance variables. For example:

Ruby
1class Dog 2 def initialize(name) 3 @name = name # Instance variable 4 end 5 6 def name # Getter method 7 @name 8 end 9 10 def name=(new_name) # Setter method 11 @name = new_name 12 end 13end 14 15my_dog = Dog.new('Max') 16my_dog.name = 'Buddy' 17puts my_dog.name # Output: Buddy

However, manually creating these methods can be a bit verbose. Thankfully, Ruby offers a more concise approach through attr_reader and attr_writer. These methods streamline the code by automatically creating the getter and setter methods for you:

Ruby
1class Dog 2 def initialize(name) 3 @name = name 4 end 5 6 attr_reader :name # Automatically creates a getter 7 attr_writer :name # Automatically creates a setter 8end 9 10my_dog = Dog.new('Max') 11my_dog.name = 'Buddy' 12puts my_dog.name # Output: Buddy

For even greater simplicity, Ruby provides attr_accessor, which combines both functionalities into a single line, creating both the getter and setter methods seamlessly:

Ruby
1class Dog 2 def initialize(name) 3 @name = name 4 end 5 6 attr_accessor :name # Creates both getter and setter 7end 8 9my_dog = Dog.new('Max') 10my_dog.name = 'Buddy' 11puts my_dog.name # Output: Buddy

By utilizing these Ruby features, managing class attributes becomes more efficient and aligns well with encapsulation principles, maintaining clean and readable code.

Practical Application and Summary

Let's apply the principle of encapsulation to our BankAccount class, which includes private attributes like account number and balance, along with public methods for withdrawals, deposits, and balance checks.

Ruby
1class BankAccount 2 def initialize(account_no, balance) 3 @account_no = account_no 4 @balance = balance 5 end 6 7 attr_reader :account_no, :balance 8 9 def withdraw(amount) 10 if amount <= 0 11 puts "Withdrawal amount must be positive." 12 elsif amount > @balance 13 puts "Insufficient balance." 14 else 15 @balance -= amount 16 end 17 end 18 19 def deposit(amount) 20 if amount <= 0 21 puts "Deposit amount must be positive." 22 else 23 @balance += amount 24 end 25 end 26end 27 28account = BankAccount.new(1, 500) 29account.withdraw(100) 30account.deposit(50) 31puts account.balance # Prints: 450

In the above code, the BankAccount class encapsulates account details, and the attribute accessors manage the balance in a controlled manner.

Hands-on Exercise and Practice

Admirable! Now it's your turn to apply what you've learned by practicing encapsulation in Ruby. Remember, practice enhances your comprehension. Enjoy coding!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.