Classes and Objects
16.1 Defining a Class
class BankAccount
attr_reader :owner, :balance # getters
attr_accessor :nickname # getter + setter
# attr_writer :pin # setter only
MINIMUM_BALANCE = 0
@@accounts_created = 0 # class variable (prefer class-level instance variables)
def initialize(owner, balance = 0) # constructor
@owner = owner
@balance = balance
@transactions = []
@@accounts_created += 1
end
def deposit(amount)
raise ArgumentError, "Amount must be positive" unless amount.positive?
@balance += amount
@transactions << [:deposit, amount]
self # return self to allow chaining
end
def withdraw(amount)
return false if amount > @balance
@balance -= amount
@transactions << [:withdraw, amount]
true
end
def to_s = "#{owner}: $#{format('%.2f', balance)}"
def inspect = "#<BankAccount owner=#{owner.inspect} balance=#{balance}>"
def self.accounts_created = @@accounts_created # class method
end
acc = BankAccount.new("Ana", 100)
acc.deposit(50).deposit(25)
puts acc # uses to_s
p acc # uses inspect
acc.nickname = "Savings"
Ba
…16.2 self and Class Methods
class Temperature
attr_reader :celsius
def initialize(celsius) = @celsius = celsius
def self.from_fahrenheit(f) = new((f - 32) * 5.0 / 9) # factory method
class << self # open the singleton class: several class methods
def freezing = new(0)
def boiling = new(100)
end
def fahrenheit = celsius * 9.0 / 5 + 32
def warmer_than?(other) = celsius > other.celsius
end
Temperature.from_fahrenheit(212).celsius
Temperature.boiling.warmer_than?(Temperature.freezing)16.3 Class-Level Instance Variables (Preferred over @@)
@@vars are shared across the whole subclass tree, which surprises people. Class-level instance variables belong to each class separately.
class Widget
@count = 0
class << self
attr_accessor :count
end
def initialize = self.class.count += 1
end16.4 Object Copying and Freezing
copy = acc.dup # shallow copy (not frozen, no singleton methods)
clone = acc.clone # shallow copy keeping the frozen state and singleton methods
acc.freeze; acc.frozen?
def initialize_copy(source) # customize dup/clone (deep copy fields)
super
@transactions = source.instance_variable_get(:@transactions).dup
end
deep = Marshal.load(Marshal.dump(obj)) # deep copy trick (only for marshalable objects)16.5 Open Classes (Monkey Patching)
Reopening core classes is powerful but risky. Prefer refinements (Module 26) or separate helper modules.
class String
def shout = upcase + "!"
end
"hi".shout # "HI!"