Skip to content

Ruby Object-Oriented Programming

Object-Oriented Programming (OOP) is one of Ruby's core features. In Ruby, everything is an object, making OOP very natural and powerful. Ruby provides rich OOP features including classes, objects, inheritance, modules, encapsulation, and polymorphism. This chapter will provide a detailed introduction to various concepts and best practices of OOP in Ruby.

🎯 Object-Oriented Basics

What is Object-Oriented Programming

Object-Oriented Programming is a programming paradigm that uses "objects" to design software. Objects are combinations of data and methods for operating on that data. OOP features in Ruby include:

  • Class: Template or blueprint for objects
  • Object: Instance of a class
  • Encapsulation: Hide internal implementation details
  • Inheritance: Create new classes from existing classes
  • Polymorphism: Different implementations of the same interface
  • Module: Code reuse and namespace
ruby
# Basic class definition
class Person
  # Constructor
  def initialize(name, age)
    @name = name  # Instance variable
    @age = age
  end
  
  # Instance method
  def introduce
    "I am #{@name}, #{@age} years old"
  end
  
  # Getter method
  def name
    @name
  end
  
  # Setter method
  def name=(new_name)
    @name = new_name
  end
end

# Create object
person = Person.new("Alice", 25)
puts person.introduce  # I am Alice, 25 years old

# Use getter and setter
puts person.name       # Alice
person.name = "Bob"
puts person.name       # Bob

Classes and Objects Basics

ruby
# Class definition
class Car
  # Class variable
  @@total_cars = 0
  
  # Constructor
  def initialize(brand, model)
    @brand = brand
    @model = model
    @mileage = 0
    @@total_cars += 1
  end
  
  # Instance method
  def drive(distance)
    @mileage += distance
    "Drove #{distance} km, total mileage #{@mileage} km"
  end
  
  # Class method
  def self.total_cars
    @@total_cars
  end
  
  # Instance variable accessors
  attr_reader :brand, :model, :mileage
  attr_writer :mileage
  attr_accessor :color  # Automatically generate getter and setter
end

# Create objects
car1 = Car.new("Toyota", "Corolla")
car2 = Car.new("Honda", "Accord")

# Use objects
puts car1.brand        # Toyota
puts car1.drive(100)   # Drove 100 km, total mileage 100 km
car1.color = "red"
puts car1.color        # red

# Call class method
puts Car.total_cars    # 2

🏗️ Class Definition and Methods

Constructor and Initialization

ruby
class Student
  # Use attr_accessor to automatically generate accessors
  attr_accessor :name, :age, :grade
  
  # Constructor
  def initialize(name, age, grade = "Unknown")
    @name = name
    @age = age
    @grade = grade
    @courses = []  # Default value
  end
  
  # Method with default parameters
  def enroll(course, semester = "Fall")
    @courses << { name: course, semester: semester }
  end
  
  # Method with variable parameters
  def add_grades(*grades)
    @grades = grades.flatten
  end
  
  # Method with keyword parameters
  def update_info(name: nil, age: nil, grade: nil)
    @name = name if name
    @age = age if age
    @grade = grade if grade
  end
  
  # Method with block parameter
  def with_logging
    puts "Starting operation"
    result = yield if block_given?
    puts "Operation completed"
    result
  end
  
  # Instance method
  def info
    "Name: #{@name}, Age: #{@age}, Grade: #{@grade}"
  end
  
  def courses
    @courses.map { |course| "#{course[:name]} (#{course[:semester]})" }
  end
end

# Using Student class
student = Student.new("Alice", 20, "Sophomore")
student.enroll("Math")
student.enroll("English", "Spring")
student.add_grades(85, 92, 78)

puts student.info
puts student.courses.inspect

student.update_info(age: 21, grade: "Junior")
puts student.info

student.with_logging { puts "Updating student information" }

Access Control

ruby
class BankAccount
  # Public methods
  def initialize(account_number, initial_balance = 0)
    @account_number = account_number
    @balance = initial_balance
    @transaction_history = []
  end
  
  # Public methods - anyone can call
  def account_number
    @account_number
  end
  
  def balance
    @balance
  end
  
  def deposit(amount)
    return false if amount <= 0
    @balance += amount
    log_transaction("Deposit", amount)
    true
  end
  
  def withdraw(amount)
    return false if amount <= 0 || amount > @balance
    @balance -= amount
    log_transaction("Withdrawal", -amount)
    true
  end
  
  # Protected methods - only class and subclasses can call
  protected
  
  def log_transaction(type, amount)
    transaction = {
      type: type,
      amount: amount,
      balance: @balance,
      timestamp: Time.now
    }
    @transaction_history << transaction
  end
  
  # Private methods - only class internals can call
  private
  
  def validate_amount(amount)
    amount > 0 && amount.is_a?(Numeric)
  end
  
  def generate_statement
    "Account: #{@account_number}, Balance: #{@balance}"
  end
  
  # Private methods can also be called by public methods
  public
  
  def print_statement
    generate_statement  # Can call private method
  end
end

# Using bank account
account = BankAccount.new("123456789", 1000)
puts account.account_number  # 123456789
puts account.balance         # 1000

account.deposit(500)
puts account.balance         # 1500

account.withdraw(200)
puts account.balance         # 1300

puts account.print_statement # Account: 123456789, Balance: 1300

# account.log_transaction("Test", 100)  # Error: protected method
# account.generate_statement            # Error: private method

🔗 Inheritance and Polymorphism

Class Inheritance

ruby
# Base class
class Animal
  attr_accessor :name, :age
  
  def initialize(name, age)
    @name = name
    @age = age
  end
  
  # Virtual method (subclass should override)
  def speak
    raise NotImplementedError, "Subclass must implement speak method"
  end
  
  # Public method
  def info
    "#{@name} is a #{@age}-year-old animal"
  end
  
  # Private method
  private
  
  def species
    "Animal"
  end
end

# Inherit base class
class Dog < Animal
  attr_accessor :breed
  
  def initialize(name, age, breed)
    super(name, age)  # Call parent class constructor
    @breed = breed
  end
  
  # Override parent method
  def speak
    "#{@name} barks"
  end
  
  # Add new method
  def fetch
    "#{@name} fetches the ball"
  end
  
  # Override parent method and call parent method
  def info
    super + ", breed is #{@breed}"
  end
end

class Cat < Animal
  attr_accessor :color
  
  def initialize(name, age, color)
    super(name, age)
    @color = color
  end
  
  # Override parent method
  def speak
    "#{@name} meows"
  end
  
  # Add new method
  def climb
    "#{@name} climbs a tree"
  end
end

# Using inheritance
dog = Dog.new("Buddy", 3, "Golden Retriever")
cat = Cat.new("Whiskers", 2, "Orange")

puts dog.info        # Buddy is a 3-year-old animal, breed is Golden Retriever
puts dog.speak       # Buddy barks
puts dog.fetch       # Buddy fetches the ball

puts cat.info        # Whiskers is a 2-year-old animal
puts cat.speak       # Whiskers meows
puts cat.climb       # Whiskers climbs a tree

Method Lookup and super Keyword

ruby
class A
  def method1
    "A#method1"
  end
  
  def method2
    "A#method2"
  end
end

class B < A
  def method1
    "B#method1 (#{super})"  # Call parent method
  end
  
  def method2
    super  # Directly call parent method
  end
  
  def method3
    "B#method3"
  end
end

class C < B
  def method1
    "C#method1 (#{super})"  # Call B#method1, which calls A#method1
  end
end

# Method lookup chain
a = A.new
b = B.new
c = C.new

puts a.method1  # A#method1
puts b.method1  # B#method1 (A#method1)
puts c.method1  # C#method1 (B#method1 (A#method1))

puts a.method2  # A#method2
puts b.method2  # A#method2
puts c.method2  # A#method2

puts b.method3  # B#method3
puts c.method3  # B#method3

📦 Modules and Mixins

Module Definition and Usage

ruby
# Define module
module Drawable
  def draw
    "Drawing shape"
  end
  
  def erase
    "Erasing shape"
  end
  
  # Module constant
  PI = 3.14159
end

module Movable
  def move(x, y)
    "Moving to position (#{x}, #{y})"
  end
  
  def rotate(angle)
    "Rotating #{angle} degrees"
  end
end

# Using modules (include)
class Shape
  include Drawable
  include Movable
  
  attr_accessor :x, :y
  
  def initialize(x = 0, y = 0)
    @x, @y = x, y
  end
  
  def position
    "Position: (#{@x}, #{@y})"
  end
end

# Using extended module (extend)
class Circle < Shape
  extend Drawable  # As class method extension
  
  attr_accessor :radius
  
  def initialize(x, y, radius)
    super(x, y)
    @radius = radius
  end
  
  def area
    Math::PI * @radius ** 2
  end
  
  # Class method (obtained through extend)
  def self.draw_circle
    "Drawing circle"
  end
end

# Using modules
shape = Shape.new(10, 20)
puts shape.draw           # Drawing shape
puts shape.move(30, 40)   # Moving to position (30, 40)
puts shape.position       # Position: (30, 40)

circle = Circle.new(0, 0, 5)
puts circle.area          # 78.53981633974483
puts circle.position      # Position: (0, 0)

# Call class method
puts Circle.draw          # Drawing shape (from Drawable module)
puts Circle.draw_circle   # Drawing circle

Module Mixins and Namespaces

ruby
# Namespace module
module Graphics
  class Point
    attr_accessor :x, :y
    
    def initialize(x, y)
      @x, @y = x, y
    end
    
    def distance(other)
      Math.sqrt((@x - other.x) ** 2 + (@y - other.y) ** 2)
    end
  end
  
  class Line
    attr_accessor :start_point, :end_point
    
    def initialize(start_point, end_point)
      @start_point = start_point
      @end_point = end_point
    end
    
    def length
      @start_point.distance(@end_point)
    end
  end
end

# Using namespace
point1 = Graphics::Point.new(0, 0)
point2 = Graphics::Point.new(3, 4)
line = Graphics::Line.new(point1, point2)

puts point1.distance(point2)  # 5.0
puts line.length              # 5.0

# Mixin module providing shared functionality
module ComparableByAge
  def <=>(other)
    @age <=> other.age
  end
  
  def >(other)
    (@age > other.age)
  end
  
  def <(other)
    (@age < other.age)
  end
  
  def ==(other)
    @age == other.age
  end
end

class Person
  include Comparable
  include ComparableByAge
  
  attr_accessor :name, :age
  
  def initialize(name, age)
    @name, @age = name, age
  end
  
  def to_s
    "#{@name}(#{@age} years old)"
  end
end

# Using comparison functionality
people = [
  Person.new("Alice", 25),
  Person.new("Bob", 30),
  Person.new("Charlie", 20)
]

puts people.sort.map(&:to_s)  # Charlie(20 years old), Alice(25 years old), Bob(30 years old)
puts people.max.to_s          # Bob(30 years old)
puts people.min.to_s          # Charlie(20 years old)

🎯 Object-Oriented Practice Examples

User Management System

ruby
# Base user class
class User
  attr_accessor :username, :email, :created_at
  attr_reader :id
  
  @@next_id = 1
  @@users = {}
  
  def initialize(username, email)
    @id = @@next_id
    @@next_id += 1
    @username = username
    @email = email
    @created_at = Time.now
    @active = true
    
    @@users[@id] = self
  end
  
  def activate
    @active = true
  end
  
  def deactivate
    @active = false
  end
  
  def active?
    @active
  end
  
  def info
    "User ID: #{@id}, Username: #{@username}, Email: #{@email}, Status: #{@active ? 'active' : 'inactive'}"
  end
  
  # Class methods
  def self.find(id)
    @@users[id]
  end
  
  def self.all
    @@users.values
  end
  
  def self.active_users
    @@users.values.select(&:active?)
  end
  
  def self.deactivated_users
    @@users.values.reject(&:active?)
  end
end

# Admin user class
class AdminUser < User
  attr_accessor :permissions
  
  def initialize(username, email, permissions = [])
    super(username, email)
    @permissions = permissions
  end
  
  def grant_permission(permission)
    @permissions << permission unless @permissions.include?(permission)
  end
  
  def revoke_permission(permission)
    @permissions.delete(permission)
  end
  
  def can?(permission)
    @permissions.include?(permission)
  end
  
  # Override info method
  def info
    super + ", Permissions: #{@permissions.join(', ')}"
  end
  
  # Admin-specific methods
  def deactivate_user(user_id)
    user = User.find(user_id)
    user&.deactivate
  end
  
  def list_all_users
    User.all.map(&:info)
  end
end

# Using user management system
# Create regular users
user1 = User.new("zhangsan", "zhangsan@example.com")
user2 = User.new("lisi", "lisi@example.com")

# Create admin user
admin = AdminUser.new("admin", "admin@example.com", ["manage_users", "view_reports"])

# Admin operations
admin.grant_permission("delete_users")
puts admin.info

# Deactivate user
admin.deactivate_user(user2.id)
puts user2.active?  # false

# List all users
puts "All users:"
User.all.each { |user| puts user.info }

puts "Active users count: #{User.active_users.length}"
puts "Inactive users count: #{User.deactivated_users.length}"

Banking System

ruby
# Bank account base class
class BankAccount
  attr_reader :account_number, :balance, :owner
  
  @@next_account_number = 100000
  @@accounts = {}
  
  def initialize(owner, initial_balance = 0)
    @account_number = @@next_account_number
    @@next_account_number += 1
    @owner = owner
    @balance = initial_balance
    @transactions = []
    
    @@accounts[@account_number] = self
    log_transaction("Account opening", initial_balance)
  end
  
  def deposit(amount)
    return false if amount <= 0
    @balance += amount
    log_transaction("Deposit", amount)
    true
  end
  
  def withdraw(amount)
    return false if amount <= 0 || amount > @balance
    @balance -= amount
    log_transaction("Withdrawal", -amount)
    true
  end
  
  def transfer_to(other_account, amount)
    return false if amount <= 0 || amount > @balance
    return false unless other_account.is_a?(BankAccount)
    
    withdraw(amount)
    other_account.deposit(amount)
    log_transaction("Transfer to #{other_account.account_number}", -amount)
    other_account.log_transaction("Transfer from #{@account_number}", amount)
    true
  end
  
  def transaction_history
    @transactions.dup
  end
  
  def self.find(account_number)
    @@accounts[account_number]
  end
  
  def self.all_accounts
    @@accounts.values
  end
  
  def self.total_balance
    @@accounts.values.sum(&:balance)
  end
  
  protected
  
  def log_transaction(type, amount)
    transaction = {
      type: type,
      amount: amount,
      balance: @balance,
      timestamp: Time.now
    }
    @transactions << transaction
  end
end

# Savings account
class SavingsAccount < BankAccount
  def initialize(owner, initial_balance = 0, interest_rate = 0.02)
    super(owner, initial_balance)
    @interest_rate = interest_rate
    @last_interest_date = Date.today
  end
  
  def add_interest
    today = Date.today
    return if today <= @last_interest_date
    
    interest = @balance * @interest_rate
    deposit(interest)
    log_transaction("Interest", interest)
    @last_interest_date = today
  end
  
  def info
    "Savings Account #{@account_number}, Balance: #{@balance}, Interest Rate: #{@interest_rate * 100}%"
  end
end

# Checking account
class CheckingAccount < BankAccount
  def initialize(owner, initial_balance = 0, overdraft_limit = 0)
    super(owner, initial_balance)
    @overdraft_limit = overdraft_limit
  end
  
  # Override withdrawal method to support overdraft
  def withdraw(amount)
    return false if amount <= 0
    return false if (@balance + @overdraft_limit) < amount
    
    @balance -= amount
    log_transaction("Withdrawal", -amount)
    true
  end
  
  def info
    "Checking Account #{@account_number}, Balance: #{@balance}, Overdraft Limit: #{@overdraft_limit}"
  end
end

# Bank management system
class Bank
  def initialize(name)
    @name = name
    @accounts = []
  end
  
  def open_savings_account(owner, initial_balance = 0, interest_rate = 0.02)
    account = SavingsAccount.new(owner, initial_balance, interest_rate)
    @accounts << account
    account
  end
  
  def open_checking_account(owner, initial_balance = 0, overdraft_limit = 0)
    account = CheckingAccount.new(owner, initial_balance, overdraft_limit)
    @accounts << account
    account
  end
  
  def find_account(account_number)
    @accounts.find { |account| account.account_number == account_number }
  end
  
  def total_assets
    @accounts.sum(&:balance)
  end
  
  def accounts_info
    @accounts.map(&:info)
  end
end

# Using banking system
bank = Bank.new("National Bank")

# Open accounts
savings = bank.open_savings_account("Alice", 10000, 0.03)
checking = bank.open_checking_account("Bob", 5000, 1000)

# Operate accounts
savings.deposit(2000)
checking.withdraw(6000)  # Use overdraft limit

# Transfer
savings.transfer_to(checking, 3000)

# View account information
puts savings.info
puts checking.info

puts "Bank total assets: #{bank.total_assets}"

# View transaction history
puts "Savings account transaction history:"
savings.transaction_history.each do |transaction|
  puts "  #{transaction[:timestamp].strftime('%Y-%m-%d %H:%M')} - #{transaction[:type]}: #{transaction[:amount]}, Balance: #{transaction[:balance]}"
end

📊 Object-Oriented Design Principles

SOLID Principles Application

ruby
# S - Single Responsibility Principle (SRP)
# Each class should have only one reason to change

# Bad design
class User
  def initialize(name, email)
    @name = name
    @email = email
  end
  
  # User data management
  def save
    # Save to database
  end
  
  def validate
    # Validate user data
  end
  
  # Email sending
  def send_welcome_email
    # Send welcome email
  end
  
  # Logging
  def log_activity(activity)
    # Log user activity
  end
end

# Good design
class User
  attr_accessor :name, :email
  
  def initialize(name, email)
    @name = name
    @email = email
  end
end

class UserDatabase
  def self.save(user)
    # Save user to database
  end
  
  def self.find(id)
    # Find user from database
  end
end

class UserValidator
  def self.validate(user)
    # Validate user data
  end
end

class EmailService
  def self.send_welcome_email(user)
    # Send welcome email
  end
end

class ActivityLogger
  def self.log(user, activity)
    # Log user activity
  end
end

# O - Open/Closed Principle (OCP)
# Open for extension, closed for modification

# Base report class
class Report
  def generate
    data = fetch_data
    format_data(data)
  end
  
  private
  
  def fetch_data
    # Common logic for fetching data
    []
  end
  
  def format_data(data)
    # Default formatting
    data.join("\n")
  end
end

# Extend report class without modifying original code
class PDFReport < Report
  def format_data(data)
    # PDF formatting
    "PDF: #{data.join(', ')}"
  end
end

class ExcelReport < Report
  def format_data(data)
    # Excel formatting
    "Excel: #{data.join("\t")}"
  end
end

# L - Liskov Substitution Principle (LSP)
# Subclasses should be substitutable for their parent classes

class Bird
  def fly
    "Bird is flying"
  end
end

class Sparrow < Bird
  def fly
    "Sparrow is flying"
  end
end

class Ostrich < Bird
  # Ostriches can't fly, violates Liskov Substitution Principle
  def fly
    raise "Ostrich can't fly"
  end
end

# Improved design
class Bird
  # Bird's basic functionality
end

class FlyingBird < Bird
  def fly
    "Bird is flying"
  end
end

class Sparrow < FlyingBird
  def fly
    "Sparrow is flying"
  end
end

class Ostrich < Bird
  def run
    "Ostrich is running"
  end
end

# I - Interface Segregation Principle (ISP)
# Clients should not depend on interfaces they don't use

# Bad design
module Worker
  def work
    # Work
  end
  
  def eat
    # Eat
  end
  
  def sleep
    # Sleep
  end
end

class HumanWorker
  include Worker
  
  def work
    "Human is working"
  end
  
  def eat
    "Human is eating"
  end
  
  def sleep
    "Human is sleeping"
  end
end

class RobotWorker
  include Worker
  
  def work
    "Robot is working"
  end
  
  def eat
    # Robots don't need to eat
    raise "Robots don't eat"
  end
  
  def sleep
    # Robots don't need to sleep
    raise "Robots don't sleep"
  end
end

# Good design
module Workable
  def work
    # Work
  end
end

module Eatable
  def eat
    # Eat
  end
end

module Sleepable
  def sleep
    # Sleep
  end
end

class HumanWorker
  include Workable
  include Eatable
  include Sleepable
  
  def work
    "Human is working"
  end
  
  def eat
    "Human is eating"
  end
  
  def sleep
    "Human is sleeping"
  end
end

class RobotWorker
  include Workable
  
  def work
    "Robot is working"
  end
end

# D - Dependency Inversion Principle (DIP)
# Depend on abstractions, not concrete implementations

# Bad design
class EmailNotifier
  def notify(message)
    # Send email notification
    puts "Sending email: #{message}"
  end
end

class UserService
  def initialize
    @notifier = EmailNotifier.new
  end
  
  def create_user(user_data)
    # User creation logic
    @notifier.notify("User created")
  end
end

# Good design
class Notifier
  def notify(message)
    raise NotImplementedError
  end
end

class EmailNotifier < Notifier
  def notify(message)
    puts "Sending email: #{message}"
  end
end

class SMSNotifier < Notifier
  def notify(message)
    puts "Sending SMS: #{message}"
  end
end

class UserService
  def initialize(notifier)
    @notifier = notifier
  end
  
  def create_user(user_data)
    # User creation logic
    @notifier.notify("User created")
  end
end

# Usage
email_notifier = EmailNotifier.new
sms_notifier = SMSNotifier.new

user_service1 = UserService.new(email_notifier)
user_service2 = UserService.new(sms_notifier)

🛡️ Object-Oriented Best Practices

1. Design Pattern Applications

ruby
# Singleton pattern
class Logger
  @@instance = nil
  
  private_class_method :new
  
  def self.instance
    @@instance ||= new
  end
  
  def log(message)
    puts "[#{Time.now}] #{message}"
  end
  
  private
  
  def initialize
    # Private constructor
  end
end

# Using singleton
logger1 = Logger.instance
logger2 = Logger.instance
puts logger1.equal?(logger2)  # true

logger1.log("First log")
logger2.log("Second log")

# Factory pattern
class AnimalFactory
  def self.create_animal(type, name)
    case type.downcase
    when 'dog'
      Dog.new(name)
    when 'cat'
      Cat.new(name)
    when 'bird'
      Bird.new(name)
    else
      raise "Unknown animal type: #{type}"
    end
  end
end

# Using factory
dog = AnimalFactory.create_animal('dog', 'Buddy')
cat = AnimalFactory.create_animal('cat', 'Whiskers')

# Observer pattern
class Subject
  def initialize
    @observers = []
  end
  
  def add_observer(observer)
    @observers << observer
  end
  
  def remove_observer(observer)
    @observers.delete(observer)
  end
  
  def notify_observers
    @observers.each { |observer| observer.update(self) }
  end
end

class TemperatureSensor < Subject
  attr_reader :temperature
  
  def initialize
    super
    @temperature = 0
  end
  
  def temperature=(temp)
    @temperature = temp
    notify_observers if temp > 30
  end
end

class TemperatureDisplay
  def update(subject)
    puts "Warning: Temperature too high! Current temperature: #{subject.temperature}°C"
  end
end

class TemperatureLogger
  def update(subject)
    puts "Recording temperature: #{subject.temperature}°C at #{Time.now}"
  end
end

# Using observer pattern
sensor = TemperatureSensor.new
display = TemperatureDisplay.new
logger = TemperatureLogger.new

sensor.add_observer(display)
sensor.add_observer(logger)

sensor.temperature = 25  # No warning
sensor.temperature = 35  # Triggers warning

2. Code Organization and Structure

ruby
# Modular design
module Payment
  class Base
    def initialize(amount)
      @amount = amount
    end
    
    def process
      raise NotImplementedError, "Subclass must implement process method"
    end
  end
  
  class CreditCard < Base
    def initialize(amount, card_number, cvv)
      super(amount)
      @card_number = card_number
      @cvv = cvv
    end
    
    def process
      "Processing credit card payment: #{@amount} dollars"
    end
  end
  
  class Alipay < Base
    def initialize(amount, account)
      super(amount)
      @account = account
    end
    
    def process
      "Processing Alipay payment: #{@amount} dollars"
    end
  end
  
  class WeChatPay < Base
    def initialize(amount, openid)
      super(amount)
      @openid = openid
    end
    
    def process
      "Processing WeChat Pay payment: #{@amount} dollars"
    end
  end
end

# Using payment module
credit_card = Payment::CreditCard.new(100, "1234****5678", "123")
alipay = Payment::Alipay.new(100, "user@example.com")
wechat = Payment::WeChatPay.new(100, "openid123")

puts credit_card.process
puts alipay.process
puts wechat.process

# Strategy pattern
class PaymentProcessor
  def initialize(payment_strategy)
    @payment_strategy = payment_strategy
  end
  
  def process_payment(amount)
    @payment_strategy.process(amount)
  end
end

# Using strategy pattern
processor1 = PaymentProcessor.new(Payment::CreditCard.new(100, "1234****5678", "123"))
processor2 = PaymentProcessor.new(Payment::Alipay.new(100, "user@example.com"))

puts processor1.process_payment(100)
puts processor2.process_payment(100)

3. Test-Friendly Design

ruby
# Dependency injection improves testability
class OrderService
  def initialize(payment_gateway = nil, inventory_service = nil)
    @payment_gateway = payment_gateway || PaymentGateway.new
    @inventory_service = inventory_service || InventoryService.new
  end
  
  def process_order(order)
    # Check inventory
    return false unless @inventory_service.check_stock(order.items)
    
    # Process payment
    payment_result = @payment_gateway.charge(order.total_amount)
    return false unless payment_result.success?
    
    # Update inventory
    @inventory_service.update_stock(order.items)
    
    true
  end
end

# Mock objects for testing
class MockPaymentGateway
  def charge(amount)
    OpenStruct.new(success?: true)
  end
end

class MockInventoryService
  def check_stock(items)
    true
  end
  
  def update_stock(items)
    # Mock inventory update
  end
end

# Test code
# order_service = OrderService.new(MockPaymentGateway.new, MockInventoryService.new)
# result = order_service.process_order(order)
# assert(result, "Order processing should succeed")

📚 Next Steps

After mastering Ruby object-oriented programming, continue learning:

Continue your Ruby learning journey!

Content is for learning and research only.