CrackedRuby CrackedRuby

Overview

Encapsulation restricts direct access to an object's internal state and requires interaction through defined interfaces. This principle binds data and methods that operate on that data into a single unit while hiding implementation details from external code. Encapsulation serves as a protective barrier that prevents external code from modifying an object's internal state in unintended ways.

The concept originated with object-oriented programming languages in the 1960s, particularly with Simula and later Smalltalk. Languages implemented encapsulation through access modifiers that control visibility of class members. Ruby implements encapsulation through method visibility controls (private, protected, public) and instance variable access restrictions.

Encapsulation addresses several software development challenges. First, it prevents tight coupling between components by limiting dependencies to public interfaces rather than internal implementations. Second, it enables developers to modify internal implementations without affecting external code that depends on the class. Third, it enforces invariants by controlling how state changes occur.

# Without encapsulation - direct state manipulation
class BankAccount
  attr_accessor :balance
end

account = BankAccount.new
account.balance = 1000
account.balance -= 5000  # Negative balance allowed - invalid state

# With encapsulation - controlled state changes
class BankAccount
  def initialize
    @balance = 0
  end
  
  def deposit(amount)
    raise ArgumentError, "Amount must be positive" if amount <= 0
    @balance += amount
  end
  
  def withdraw(amount)
    raise ArgumentError, "Amount must be positive" if amount <= 0
    raise "Insufficient funds" if amount > @balance
    @balance -= amount
  end
  
  def balance
    @balance
  end
end

Key Principles

Encapsulation operates on three fundamental mechanisms: information hiding, interface definition, and access control. Information hiding conceals implementation details from external code, making the internal structure opaque to clients. Interface definition establishes the public methods through which external code interacts with objects. Access control enforces visibility boundaries using language features like access modifiers.

The principle of least privilege guides encapsulation decisions. Class members default to the most restrictive access level necessary for functionality. Public interfaces expose only operations essential for client code. Internal helper methods, implementation details, and direct state access remain hidden behind access restrictions.

Encapsulation maintains a distinction between interface and implementation. The interface represents a contract specifying what operations an object provides and their expected behavior. The implementation contains the actual code that fulfills the contract. This separation allows implementation changes without breaking client code as long as the interface remains stable.

State protection forms a core aspect of encapsulation. Direct access to instance variables bypasses validation logic and allows invalid state. Controlled access through methods enables validation, logging, authorization checks, and other cross-cutting concerns at state transition points.

class Temperature
  def initialize(celsius)
    self.celsius = celsius
  end
  
  def celsius
    @celsius
  end
  
  def celsius=(value)
    raise ArgumentError, "Temperature cannot be below absolute zero" if value < -273.15
    @celsius = value
  end
  
  def fahrenheit
    @celsius * 9.0 / 5.0 + 32
  end
  
  def fahrenheit=(value)
    self.celsius = (value - 32) * 5.0 / 9.0
  end
end

Encapsulation creates abstraction layers that manage complexity. High-level code interacts with simplified interfaces while complex implementation remains hidden. This allows developers to reason about systems at appropriate abstraction levels without managing low-level details.

The Law of Demeter extends encapsulation principles by restricting the methods an object can call. An object should only interact with immediate neighbors: itself, objects passed as parameters, objects it creates, and objects in its instance variables. This prevents reaching through object chains and reduces coupling.

# Violates Law of Demeter
class Order
  attr_reader :customer
end

class Customer
  attr_reader :address
end

class Address
  attr_reader :city
end

order = Order.new(customer: Customer.new(address: Address.new(city: "Chicago")))
city = order.customer.address.city  # Chain of calls exposes internal structure

# Follows Law of Demeter
class Order
  def customer_city
    @customer.city
  end
end

class Customer
  def city
    @address.city
  end
end

order = Order.new(customer: Customer.new(address: Address.new(city: "Chicago")))
city = order.customer_city  # Single method call

Ruby Implementation

Ruby implements encapsulation through method visibility modifiers: public, private, and protected. Methods default to public visibility unless explicitly declared otherwise. The private keyword makes methods callable only within the class, preventing external objects from invoking them. The protected keyword allows methods to be called by instances of the same class or subclasses.

class Person
  def initialize(name, ssn)
    @name = name
    @ssn = ssn
  end
  
  # Public method - accessible everywhere
  def introduce
    "My name is #{@name}"
  end
  
  # Protected method - accessible within class and subclasses
  protected
  
  def ssn_match?(other)
    @ssn == other.ssn_value
  end
  
  def ssn_value
    @ssn
  end
  
  # Private methods - only callable within this instance
  private
  
  def validate_ssn
    @ssn.match?(/\A\d{3}-\d{2}-\d{4}\z/)
  end
end

person1 = Person.new("Alice", "123-45-6789")
person2 = Person.new("Bob", "123-45-6789")

person1.introduce  # Works - public
person1.ssn_match?(person2)  # Error - protected method called externally
person1.validate_ssn  # Error - private method called externally

Ruby's visibility modifiers affect method lookup and invocation. Private methods cannot be called with an explicit receiver, even self. This restriction forces all private method calls to use implicit self, making them clearly internal operations. Protected methods allow explicit receivers but only for objects of the same class or subclass.

class Account
  def initialize(balance)
    @balance = balance
  end
  
  def transfer_to(other_account, amount)
    return false unless sufficient_funds?(amount)
    @balance -= amount
    other_account.add_funds(amount)
    true
  end
  
  protected
  
  def add_funds(amount)
    @balance += amount
  end
  
  private
  
  def sufficient_funds?(amount)
    @balance >= amount
  end
  
  def display_balance
    puts format_balance  # Works - implicit self
    puts self.format_balance  # Error - cannot call private method with explicit receiver
  end
  
  def format_balance
    "$#{@balance}"
  end
end

Instance variables in Ruby remain private by default with no direct external access. The language provides no syntax for declaring instance variables as public. All external access to instance variables occurs through methods. This design enforces method-based encapsulation and prevents bypassing validation logic.

class Circle
  def initialize(radius)
    @radius = radius
  end
  
  # Getter method
  def radius
    @radius
  end
  
  # Setter method with validation
  def radius=(new_radius)
    raise ArgumentError, "Radius must be positive" if new_radius <= 0
    @radius = new_radius
  end
  
  def area
    Math::PI * @radius ** 2
  end
end

circle = Circle.new(5)
circle.radius  # 5 - calls getter method
circle.radius = 10  # Calls setter method
circle.@radius  # Syntax error - no direct instance variable access

Ruby provides attr_reader, attr_writer, and attr_accessor as shortcuts for creating getter and setter methods. These methods generate standard accessor code, reducing boilerplate while maintaining encapsulation. The generated methods remain regular methods that can be overridden or enhanced.

class Product
  attr_reader :name, :sku
  attr_accessor :price
  
  def initialize(name, sku, price)
    @name = name
    @sku = sku
    @price = price
  end
end

# Equivalent to:
class Product
  def initialize(name, sku, price)
    @name = name
    @sku = sku
    @price = price
  end
  
  def name
    @name
  end
  
  def sku
    @sku
  end
  
  def price
    @price
  end
  
  def price=(value)
    @price = value
  end
end

Visibility modifiers in Ruby apply to subsequently defined methods until another modifier appears. This differs from languages where visibility attaches to individual method declarations. The modifier creates a scope affecting all following method definitions.

class Example
  public  # Everything below is public (this is the default)
  
  def public_method_one
  end
  
  def public_method_two
  end
  
  private  # Everything below is private
  
  def private_method_one
  end
  
  def private_method_two
  end
  
  public  # Switch back to public
  
  def public_method_three
  end
end

Ruby also supports inline visibility declarations using symbol arguments. This syntax applies visibility to specific methods without affecting subsequent definitions.

class Example
  def method_one
  end
  
  def method_two
  end
  
  def method_three
  end
  
  private :method_two, :method_three
  
  def method_four  # Remains public
  end
end

Practical Examples

Encapsulation applies to financial domain modeling where invariants must be maintained. A money class encapsulates amount and currency, preventing invalid operations like adding amounts in different currencies.

class Money
  attr_reader :currency
  
  def initialize(amount, currency)
    @amount = amount
    @currency = currency
  end
  
  def amount
    @amount.round(2)
  end
  
  def +(other)
    raise ArgumentError, "Cannot add different currencies" unless same_currency?(other)
    Money.new(@amount + other.raw_amount, @currency)
  end
  
  def -(other)
    raise ArgumentError, "Cannot subtract different currencies" unless same_currency?(other)
    Money.new(@amount - other.raw_amount, @currency)
  end
  
  def ==(other)
    return false unless same_currency?(other)
    @amount == other.raw_amount
  end
  
  protected
  
  def raw_amount
    @amount
  end
  
  private
  
  def same_currency?(other)
    @currency == other.currency
  end
end

price = Money.new(99.99, "USD")
tax = Money.new(8.00, "USD")
total = price + tax  # Money.new(107.99, "USD")

euro_amount = Money.new(50, "EUR")
invalid = price + euro_amount  # Raises ArgumentError

Authentication systems benefit from encapsulation by hiding password storage and comparison logic. The interface provides authentication methods without exposing password hashes or comparison algorithms.

require 'digest'

class User
  attr_reader :username
  
  def initialize(username, password)
    @username = username
    @password_hash = hash_password(password)
    @failed_attempts = 0
    @locked_until = nil
  end
  
  def authenticate(password)
    return false if account_locked?
    
    if valid_password?(password)
      reset_failed_attempts
      true
    else
      increment_failed_attempts
      false
    end
  end
  
  def change_password(old_password, new_password)
    return false unless authenticate(old_password)
    @password_hash = hash_password(new_password)
    true
  end
  
  private
  
  def hash_password(password)
    Digest::SHA256.hexdigest(password)
  end
  
  def valid_password?(password)
    @password_hash == hash_password(password)
  end
  
  def account_locked?
    @locked_until && Time.now < @locked_until
  end
  
  def increment_failed_attempts
    @failed_attempts += 1
    @locked_until = Time.now + 900 if @failed_attempts >= 3  # Lock for 15 minutes
  end
  
  def reset_failed_attempts
    @failed_attempts = 0
    @locked_until = nil
  end
end

user = User.new("alice", "secret123")
user.authenticate("wrong")  # false
user.authenticate("secret123")  # true
user.@password_hash  # Syntax error - cannot access directly

Cache implementations use encapsulation to hide eviction strategies and storage mechanisms while providing simple get and set operations.

class LRUCache
  def initialize(capacity)
    @capacity = capacity
    @cache = {}
    @access_order = []
  end
  
  def get(key)
    return nil unless @cache.key?(key)
    update_access_order(key)
    @cache[key]
  end
  
  def put(key, value)
    if @cache.key?(key)
      @cache[key] = value
      update_access_order(key)
    else
      evict_oldest if at_capacity?
      @cache[key] = value
      @access_order << key
    end
  end
  
  def size
    @cache.size
  end
  
  private
  
  def at_capacity?
    @cache.size >= @capacity
  end
  
  def evict_oldest
    oldest_key = @access_order.shift
    @cache.delete(oldest_key)
  end
  
  def update_access_order(key)
    @access_order.delete(key)
    @access_order << key
  end
end

cache = LRUCache.new(3)
cache.put("a", 1)
cache.put("b", 2)
cache.put("c", 3)
cache.get("a")  # Moves "a" to end of access order
cache.put("d", 4)  # Evicts "b" (oldest)

State machine implementations encapsulate state transition logic and validation, ensuring that state changes follow defined rules.

class OrderStateMachine
  VALID_TRANSITIONS = {
    pending: [:processing, :cancelled],
    processing: [:shipped, :cancelled],
    shipped: [:delivered],
    delivered: [],
    cancelled: []
  }.freeze
  
  attr_reader :state
  
  def initialize
    @state = :pending
    @state_history = [:pending]
  end
  
  def transition_to(new_state)
    raise InvalidTransitionError, "Cannot transition from #{@state} to #{new_state}" unless valid_transition?(new_state)
    
    previous_state = @state
    @state = new_state
    @state_history << new_state
    
    execute_transition_hooks(previous_state, new_state)
  end
  
  def can_transition_to?(new_state)
    valid_transition?(new_state)
  end
  
  private
  
  def valid_transition?(new_state)
    VALID_TRANSITIONS[@state].include?(new_state)
  end
  
  def execute_transition_hooks(from, to)
    send("on_exit_#{from}") if respond_to?("on_exit_#{from}", true)
    send("on_enter_#{to}") if respond_to?("on_enter_#{to}", true)
  end
  
  def on_enter_shipped
    # Send shipping notification
  end
  
  def on_enter_cancelled
    # Process refund
  end
end

class InvalidTransitionError < StandardError; end

order = OrderStateMachine.new
order.transition_to(:processing)  # Valid
order.transition_to(:delivered)   # Raises InvalidTransitionError

Design Considerations

Encapsulation decisions involve trade-offs between flexibility and protection. Strict encapsulation with minimal public interfaces creates rigid APIs that resist change but protect internal state effectively. Relaxed encapsulation with broader access exposes implementation details but provides greater flexibility for client code.

The granularity of encapsulation affects maintainability. Fine-grained encapsulation with many small classes and narrow interfaces distributes responsibility but increases the number of interactions. Coarse-grained encapsulation with fewer, larger classes consolidates logic but creates classes with more responsibilities.

Encapsulation interacts with other design principles, particularly the Single Responsibility Principle. A class with a single responsibility has a cohesive interface with related methods. Encapsulation enforces this by hiding internal collaborators and preventing external code from bypassing intended interfaces.

# Poor encapsulation - mixed responsibilities
class Report
  attr_accessor :data, :format, :destination
  
  def generate
    formatted_data = format == :json ? data.to_json : data.to_xml
    case destination
    when :email
      send_email(formatted_data)
    when :file
      write_file(formatted_data)
    end
  end
end

# Better encapsulation - separated concerns
class Report
  def initialize(data, formatter, output)
    @data = data
    @formatter = formatter
    @output = output
  end
  
  def generate
    formatted_data = format_data
    @output.write(formatted_data)
  end
  
  private
  
  def format_data
    @formatter.format(@data)
  end
end

class JSONFormatter
  def format(data)
    data.to_json
  end
end

class EmailOutput
  def write(content)
    # Send email
  end
end

The decision to encapsulate collections requires careful consideration. Returning direct references to internal collections allows external code to modify the collection, breaking encapsulation. Returning copies prevents modification but impacts performance. Returning read-only proxies balances protection and performance.

class Team
  def initialize
    @members = []
  end
  
  # Poor - returns mutable reference
  def members
    @members
  end
  
  # Better - returns frozen copy
  def members
    @members.dup.freeze
  end
  
  # Better - returns defensive copy
  def members
    @members.map(&:dup)
  end
  
  # Best - provides specific operations
  def add_member(member)
    @members << member unless @members.include?(member)
  end
  
  def remove_member(member)
    @members.delete(member)
  end
  
  def member_count
    @members.size
  end
  
  def each_member(&block)
    @members.each(&block)
  end
end

Inheritance complicates encapsulation by creating tension between subclass flexibility and superclass encapsulation. Protected access enables subclass customization while maintaining some encapsulation. Composition often provides better encapsulation than inheritance by hiding implementation objects completely.

Encapsulation boundaries must align with system architecture. Microservices architecture applies encapsulation at the service level, hiding internal databases and data structures behind APIs. Monolithic architectures apply encapsulation at the class and module level within a single deployment unit.

Temporal encapsulation deals with state changes over time. Immutable objects provide strong temporal encapsulation by preventing state changes after construction. Mutable objects require careful design to ensure state transitions maintain invariants throughout the object's lifetime.

Common Patterns

The Tell-Don't-Ask pattern enhances encapsulation by pushing behavior into objects rather than extracting state for external manipulation. This pattern tells objects what to do rather than asking for their state and making decisions externally.

# Ask pattern - poor encapsulation
class ShoppingCart
  attr_reader :items
  
  def initialize
    @items = []
  end
  
  def add_item(item)
    @items << item
  end
end

# External code makes decisions
cart = ShoppingCart.new
total = cart.items.sum(&:price)
discounted = total * 0.9 if cart.items.size > 10

# Tell pattern - better encapsulation
class ShoppingCart
  def initialize
    @items = []
  end
  
  def add_item(item)
    @items << item
  end
  
  def total
    raw_total = @items.sum(&:price)
    apply_bulk_discount(raw_total)
  end
  
  private
  
  def apply_bulk_discount(amount)
    @items.size > 10 ? amount * 0.9 : amount
  end
end

cart = ShoppingCart.new
total = cart.total  # Cart handles its own calculation

The Builder pattern encapsulates complex object construction, hiding multi-step assembly and validation logic behind a fluent interface.

class EmailBuilder
  def initialize
    @to = []
    @cc = []
    @bcc = []
    @subject = ""
    @body = ""
    @attachments = []
  end
  
  def to(*addresses)
    @to.concat(addresses)
    self
  end
  
  def cc(*addresses)
    @cc.concat(addresses)
    self
  end
  
  def subject(text)
    @subject = text
    self
  end
  
  def body(text)
    @body = text
    self
  end
  
  def attach(file)
    @attachments << file
    self
  end
  
  def build
    validate_email
    Email.new(
      to: @to,
      cc: @cc,
      bcc: @bcc,
      subject: @subject,
      body: @body,
      attachments: @attachments
    )
  end
  
  private
  
  def validate_email
    raise "Email must have at least one recipient" if @to.empty?
    raise "Email must have a subject" if @subject.empty?
  end
end

class Email
  def initialize(to:, cc:, bcc:, subject:, body:, attachments:)
    @to = to
    @cc = cc
    @bcc = bcc
    @subject = subject
    @body = body
    @attachments = attachments
  end
end

email = EmailBuilder.new
  .to("user@example.com")
  .cc("manager@example.com")
  .subject("Project Update")
  .body("Status report attached")
  .attach("report.pdf")
  .build

The Strategy pattern encapsulates algorithms behind a common interface, allowing algorithm selection without exposing implementation details.

class PricingCalculator
  def initialize(strategy)
    @strategy = strategy
  end
  
  def calculate(base_price, quantity)
    @strategy.calculate(base_price, quantity)
  end
  
  def strategy=(new_strategy)
    @strategy = new_strategy
  end
end

class RegularPricing
  def calculate(base_price, quantity)
    base_price * quantity
  end
end

class BulkPricing
  def calculate(base_price, quantity)
    total = base_price * quantity
    quantity >= 100 ? total * 0.8 : total
  end
end

class VIPPricing
  def calculate(base_price, quantity)
    (base_price * quantity) * 0.75
  end
end

calculator = PricingCalculator.new(RegularPricing.new)
price = calculator.calculate(10, 50)  # 500

calculator.strategy = BulkPricing.new
price = calculator.calculate(10, 150)  # 1200

The Null Object pattern encapsulates absence, providing an object with a defined interface that performs no operation instead of using nil checks.

class RealLogger
  def log(message)
    File.open("app.log", "a") do |f|
      f.puts("[#{Time.now}] #{message}")
    end
  end
end

class NullLogger
  def log(message)
    # Do nothing
  end
end

class Application
  def initialize(logger = NullLogger.new)
    @logger = logger
  end
  
  def process(data)
    @logger.log("Processing started")
    # Process data
    @logger.log("Processing completed")
  end
end

# No null checks needed
app = Application.new  # Uses NullLogger
app.process(data)

app_with_logging = Application.new(RealLogger.new)
app_with_logging.process(data)  # Logs to file

Common Pitfalls

Ruby's attr_accessor creates public getter and setter methods, breaking encapsulation when used without consideration. Developers often default to attr_accessor for all instance variables, exposing internal state unnecessarily.

# Pitfall - over-exposed state
class Rectangle
  attr_accessor :width, :height
end

rect = Rectangle.new
rect.width = 10
rect.height = -5  # Invalid state allowed

# Better - controlled access
class Rectangle
  def initialize(width, height)
    self.width = width
    self.height = height
  end
  
  def width
    @width
  end
  
  def width=(value)
    raise ArgumentError, "Width must be positive" if value <= 0
    @width = value
  end
  
  def height
    @height
  end
  
  def height=(value)
    raise ArgumentError, "Height must be positive" if value <= 0
    @height = value
  end
  
  def area
    @width * @height
  end
end

Returning mutable references to internal collections violates encapsulation by allowing external modification of internal state.

class Library
  def initialize
    @books = []
  end
  
  def books
    @books  # Returns mutable reference
  end
  
  def add_book(book)
    @books << book
  end
end

library = Library.new
library.add_book("Book 1")
library.books << "Book 2"  # Bypasses add_book method
library.books.clear  # Destroys internal state

# Better - return frozen copy or iterator
class Library
  def initialize
    @books = []
  end
  
  def books
    @books.dup.freeze
  end
  
  def each_book(&block)
    @books.each(&block)
  end
  
  def add_book(book)
    @books << book unless @books.include?(book)
  end
end

Protected methods in Ruby differ from other languages. Protected methods can be called with an explicit receiver if the receiver is of the same class, which developers familiar with Java or C++ might not expect.

class Person
  def initialize(age)
    @age = age
  end
  
  def older_than?(other)
    @age > other.age  # Can call protected method with explicit receiver
  end
  
  protected
  
  def age
    @age
  end
end

person1 = Person.new(30)
person2 = Person.new(25)
person1.older_than?(person2)  # Works
person1.age  # Error - protected method called externally

Using send or public_send bypasses visibility restrictions, breaking encapsulation. While sometimes necessary for metaprogramming, this technique should be used sparingly.

class Account
  def initialize(balance)
    @balance = balance
  end
  
  private
  
  def balance
    @balance
  end
end

account = Account.new(1000)
account.balance  # Error - private method
account.send(:balance)  # 1000 - bypasses encapsulation
account.instance_variable_get(:@balance)  # 1000 - direct access

Lazy initialization in getter methods creates hidden side effects that violate the principle of least surprise. Getters should not modify state.

# Pitfall - getter with side effects
class DataStore
  def data
    @data ||= expensive_load_operation
  end
  
  private
  
  def expensive_load_operation
    # Load from database or API
    sleep(2)
    ["data"]
  end
end

store = DataStore.new
puts "Getting data..."
store.data  # First call is slow
store.data  # Subsequent calls are fast

# Better - explicit initialization
class DataStore
  def initialize
    @loaded = false
  end
  
  def load
    return if @loaded
    @data = expensive_load_operation
    @loaded = true
  end
  
  def data
    raise "Data not loaded - call load first" unless @loaded
    @data
  end
  
  private
  
  def expensive_load_operation
    sleep(2)
    ["data"]
  end
end

Class variables (@@variable) break encapsulation in inheritance hierarchies by sharing state across all subclasses and instances.

class Counter
  @@count = 0
  
  def increment
    @@count += 1
  end
  
  def count
    @@count
  end
end

class SpecialCounter < Counter
end

counter1 = Counter.new
counter2 = SpecialCounter.new

counter1.increment
counter2.increment
counter1.count  # 2 - shared state
counter2.count  # 2 - shared state

# Better - use class instance variables
class Counter
  @count = 0
  
  class << self
    attr_accessor :count
  end
  
  def increment
    self.class.count += 1
  end
  
  def count
    self.class.count
  end
end

class SpecialCounter < Counter
  @count = 0
end

Reference

Visibility Modifiers

Modifier Accessible From Explicit Receiver Inheritance
public Everywhere Allowed Inherited as public
protected Same class and subclasses Allowed for same class instances Inherited as protected
private Same instance only Not allowed (not even self) Inherited as private

Access Control Methods

Method Purpose Example
attr_reader Creates getter method attr_reader :name
attr_writer Creates setter method attr_writer :name
attr_accessor Creates getter and setter attr_accessor :name
private Sets following methods as private private
protected Sets following methods as protected protected
public Sets following methods as public public

Encapsulation Patterns

Pattern Purpose Key Benefit
Tell-Don't-Ask Push behavior into objects Reduces coupling
Builder Encapsulate complex construction Simplifies object creation
Strategy Encapsulate algorithms Enables algorithm selection
Null Object Encapsulate absence Eliminates nil checks
Template Method Encapsulate algorithm structure Allows customization points

Common Violations

Violation Problem Solution
Public attr_accessor for all variables Exposes internal state Use attr_reader or custom setters with validation
Returning mutable collections Allows external modification Return frozen copies or iterators
Using send to bypass visibility Breaks access control Respect visibility boundaries
Lazy initialization in getters Hidden side effects Explicit initialization methods
Class variables in hierarchies Shared state across subclasses Use class instance variables

Design Guidelines

Guideline Description Rationale
Principle of Least Privilege Default to most restrictive access Minimizes coupling
Law of Demeter Only talk to immediate neighbors Reduces dependencies
Tell-Don't-Ask Command objects instead of querying state Improves encapsulation
Immutability When Possible Prevent state changes after construction Eliminates temporal coupling
Defensive Copying Copy mutable inputs and outputs Protects internal state

Method Visibility Declaration Syntax

Syntax Effect Scope
private Affects all following methods Until next modifier
private def method_name Declares single method private Single method
private :method1, :method2 Makes specific methods private Listed methods
class << self; private :method; end Private class method Class method