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 关键字、类名大驼峰(UserArticle)、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)

经验: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 模块

✈️💬