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.
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.
Ruby1class 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 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
.
Ruby1class 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.
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:
Ruby1class 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:
Ruby1class 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:
Ruby1class 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.
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.
Ruby1class 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.
Admirable! Now it's your turn to apply what you've learned by practicing encapsulation in Ruby. Remember, practice enhances your comprehension. Enjoy coding!