OOP 与 Mixin
类、继承与模块。
OOP 与 Mixin
这一课你将能做到
- 定义类、属性读写器(attr_accessor)、构造方法,理解继承与 super
- 用模块(Module)做命名空间和 Mixin,说清 include 与 extend 的区别
- 理解方法查找链(Method Resolution Order, MRO)和 singleton class,避免过度使用 Monkey Patching
为什么要学
Ruby 是纯面向对象语言——连整数和 nil 都是对象,每个对象都有类。Ruby 的 OOP 模型比 Java 更灵活:单继承 + Mixin(模块混入)替代了多重继承;对象有「singleton class」让方法只属于单个对象。Rails 的 ActiveRecord、Concern 全都是 Mixin 的应用。
人话:Ruby 的类是蓝图,对象是实例;模块是「功能包」,可以像插件一样混进类里。Java 用接口+组合,Ruby 用 include/extend 一步到位。
先建立图像
对象的方法查找路径(MRO)
my_dog.speak
│
▼
┌──────────────────────────────┐
│ 1. singleton class of my_dog │ ← 单个对象独有方法
│ 2. Dog class │ ← 本类定义
│ 3. Speakable module (include) │ ← 最后 include 的先找
│ 4. Animal class (父类) │
│ 5. Walkable module │
│ 6. Object class │
│ 7. Kernel module │ ← puts/p 都在这里
│ 8. BasicObject │ ← 一切类的根
└──────────────────────────────┘
用 my_dog.class.ancestors 可以直接看到这个链
跟着做
步骤 1:类、继承与 super
class Animal
attr_accessor :name, :age # 自动生成 name, name=, age, age= 四个方法
attr_reader :species # 只读
def initialize(name, age, species = "未知")
@name = name
@age = age
@species = species
end
def speak
"..."
end
def describe
"#{@name}(#{@species},#{@age} 岁)"
end
end
class Dog < Animal
def initialize(name, age)
super(name, age, "犬") # 调父类构造方法
@tricks = []
end
def speak
"汪汪!"
end
def learn(trick)
@tricks << trick
end
end
dog = Dog.new("旺财", 3)
dog.age = 4 # attr_writer
puts dog.describe # => 旺财(犬,4 岁)
puts dog.speak # => 汪汪!
dog.learn("握手")
puts dog.species # => 犬(attr_reader)
步骤 2:模块——命名空间
module Payments
class CreditCard
def charge(amount)
"信用卡扣款 #{amount} 元"
end
end
class Alipay
def charge(amount)
"支付宝扣款 #{amount} 元"
end
end
end
cc = Payments::CreditCard.new
puts cc.charge(99)
步骤 3:Mixin——include 与 extend
module Loggable
def log(message)
puts "[#{self.class}] #{message}"
end
end
module Serializable
def to_h
instance_variables.each_with_object({}) do |var, h|
h[var.to_s.delete("@")] = instance_variable_get(var)
end
end
end
class Order
include Loggable # 实例方法
include Serializable
extend Enumerable # 给类本身加方法(class methods)——演示用
def initialize(id, total)
@id = id
@total = total
end
def process
log("处理订单 #{@id},金额 #{@total}")
end
end
order = Order.new(1001, 299)
order.process
puts order.to_h.inspect # => {"id"=>1001, "total"=>299}
# include vs extend 的核心差异:
# include Loggable → log 是实例方法,order.log 可用,Order.log 不可用
# extend Loggable → log 是类方法,Order.log 可用,order.log 不可用