Ruby 类与面向对象
Ruby 是纯面向对象语言——连数字 5 都是对象,都有方法 5.times。理解类是写出可维护 Ruby 程序的根基。本章讲类的全部核心:class 定义、实例变量、attr 三兄弟、self、类方法、继承、访问控制。
1. 定义类:最基础的形式
# class 关键字定义类,大驼峰命名
class User
# initialize 是构造器,new 时自动调用
def initialize(name, age)
@name = name # @var 是实例变量,每个对象独有
@age = age
end
# 实例方法
def greet
"Hi, I'm #{@name}"
end
end
# new 创建对象
u = User.new("Alice", 30)
puts u.greet # Hi, I'm Alice几个关键:class 关键字、类名大驼峰(User、Article)、initialize 是构造器(new 时自动调用)、@name 是实例变量(每个对象独有,外部不可直接访问)。
2. attr_accessor / attr_reader / attr_writer
实例变量 @name 外部默认不可见。要暴露给外部,用 attr 三兄弟自动生成 getter/setter:
class User
# 三兄弟:attr_reader 只读 / attr_writer 只写 / attr_accessor 读写
# 参数是符号,会自动生成对应的 getter/setter 方法
attr_accessor :name # 生成 name / name= 两个方法
attr_reader :age # 只生成 age(只读)
def initialize(name, age)
@name = name
@age = age
end
# 写了 attr_accessor :name 后,等价于自动加了:
# def name; @name; end
# def name=(v); @name = v; end
end
u = User.new("Alice", 30)
puts u.name # Alice(attr_reader 生成的 getter)
u.name = "Bob" # (attr_writer 生成的 setter)
puts u.name # Bob
# u.age = 25 # NoMethodError(只有 reader,没有 writer)原则:能用 attr_reader 就别用 attr_accessor——最小暴露是好面向对象设计的核心。Rails 的 model 默认给所有字段 attr_accessor,但纯 Ruby 类要克制。
3. self、类方法、类变量、常量
class Counter
# @@var 是类变量,所有实例共享(慎用)
@@total = 0
def initialize
@@total += 1
end
# 类方法:在 def 前加 self.
def self.total
@@total
end
# 类常量:大写开头
MAX = 100
# 实例方法内的 self 指向当前对象
def whoami
"I am #{self.class.name}, id #{self.object_id}"
end
end
Counter.new
Counter.new
puts Counter.total # 2(类方法调用,用类名)
puts Counter::MAX # 100(常量用 :: 取)self 在方法定义处表示"类自己"(用于定义类方法),在方法体内表示"当前对象"。@@var 类变量少用——它在继承链里行为复杂,社区倾向用类实例变量 @var(定义在类方法里的)替代。
4. 继承与 super
Ruby 是单继承(一个类只能有一个父类),但通过模块(下章讲)实现类似多继承的能力。super 调用父类同名方法:
# Ruby 单继承:用 < 表示
class Animal
def initialize(name)
@name = name
end
def speak
"#{@name} makes a sound"
end
end
class Dog < Animal
def initialize(name, breed)
super(name) # 调用父类同名方法
@breed = breed
end
# 重写(override)父类方法
def speak
"#{@name} (#{@breed}) says Woof!"
end
end
d = Dog.new("Rex", "Lab")
puts d.speak # Rex (Lab) says Woof!
# Ruby 所有类最终都继承自 Object(再上是 BasicObject)
Dog.ancestors # [Dog, Animal, Object, Kernel, BasicObject]
d.is_a?(Animal) # true
d.instance_of?(Dog) # true(精确类型)super 三种写法:super(自动转发当前参数)、super()(显式不传参)、super(a, b)(传新参数)。最容易踩的是第一种——你本想调父类无参方法,却因为 super 自动转发搞出问题。
5. 访问控制:public / private / protected
Ruby 的访问控制比 Java/Python 都更"软"——它不是编译时强制,而是运行时消息分发机制。三种:
class Account
def initialize(balance)
@balance = balance
end
# 默认是 public(放在 private 之前的方法)
def deposit(amount)
@balance += amount
log("deposited #{amount}")
end
def balance
@balance
end
private # 以下方法都是 private(直到 private/public/protected 出现)
def log(msg)
# private 方法只能在当前对象内部用,不能用 other.private_method 调
puts "[LOG] #{msg}"
end
protected :balance # 把 balance 改成 protected
# protected:同类实例之间可调,外部不可
def richer_than?(other)
balance > other.balance # 这里能调 other 的 protected balance
end
end
a = Account.new(100)
a.deposit(50) # [LOG] deposited 50
puts a.balance # NoMethodError(private 后变不可见)
# a.log("hi") # NoMethodError(private)- public:任何人可调(默认)。
- private:只能在当前对象内部调用——不能用
self.private_method或other.private_method,只能"裸调用"。 - protected:可以在同类其他对象上调用。常用于比较方法,如
other.balance。
经验:99% 的时候用 public/private 就够了,protected 是 Ruby 独有的细微之处,实战少见。
6. 打开类与 to_s / inspect
# 类也可以重新打开(打开类)
class User
def admin?
false
end
end
class User # 重新打开,添加方法(不覆盖原有)
def guest?
!admin?
end
end
User.new.guest? # true
# to_s 与 inspect
class Point
def initialize(x, y); @x, @y = x, y; end
def to_s # puts/字符串插值时调用
"(#{@x}, #{@y})"
end
def inspect # p / irb 显示时调用
"#<Point x=#{@x}, y=#{@y}>"
end
end
p Point.new(1, 2) # #<Point x=1, y=2>(inspect)
puts Point.new(1, 2) # (1, 2)(to_s)"打开类"(open class)是 Ruby 强大又危险的特性——任何类都能随时往里加方法,Rails 的 1.hour、"foo".pluralize 就是这么来的。代价是全局影响(叫"猴子补丁"),用时要克制,新项目优先用 refinements(Ruby 2.1+ 的限定版打开类)。
小结
本章你掌握了 Ruby 面向对象的全部基础。实例变量存状态,方法定义行为,attr_* 控制暴露,继承复用代码,private 隐藏实现。下一篇看模块——Ruby 实现"多继承"和 DSL 的关键工具。
← 上一篇 Ruby 哈希
下一篇 Ruby 模块 →