CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Ruby 语言入门/课时/OOP 与 Mixin
RawBlame
课时目录12 节

Ruby 语言入门

  1. 01Ruby 环境
  2. 02数据类型
  3. 03语法基础
  4. 04控制流
  5. 05方法与 Blocks
  6. 06OOP 与 Mixin
  7. 07元编程
  8. 08IO 与 Enumerable
  9. 09并发
  10. 10Gems 与框架
  11. 11测试与质量
  12. 12综合练习

OOP 与 Mixin

类、继承与模块。

30 分钟

OOP 与 Mixin

这一课你将能做到

  1. 定义类、属性读写器(attr_accessor)、构造方法,理解继承与 super
  2. 用模块(Module)做命名空间和 Mixin,说清 include 与 extend 的区别
  3. 理解方法查找链(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 不可用

步骤 4:singleton class 与 ancestors

class Person; end
p1 = Person.new
p2 = Person.new

# 给单个对象加方法——只有 p1 有
def p1.special_greet
  "我是特殊的"
end
puts p1.special_greet
# puts p2.special_greet   # NoMethodError!

# 查看方法查找链
class Dog; end
module Bark; def bark; "woof"; end; end
class Dog; include Bark; end
puts Dog.ancestors.inspect
# => [Dog, Bark, Object, Kernel, BasicObject]

步骤 5:Monkey Patching——知道但少用

# 给核心类打补丁
class String
  def shout
    upcase + "!"
  end
end
puts "hello".shout   # => HELLO!

# 危险:全局生效,别的库依赖 String 原行为会崩。
# 安全替代:Refinements(Ruby 2.0+),只在特定作用域生效
module StringShout
  refine String do
    def shout
      upcase + "!"
    end
  end
end

using StringShout
puts "hi".shout   # => HI!
# 出了这个文件/模块,String 恢复原样

讲清楚要点

  1. attr_accessor 只是语法糖:attr_accessor :name 等价于手动写 def name; @name; end 和 def name=(v); @name = v; end。没有 JVM 那种真正的字段——一切都是方法调用。
  2. include 把模块插到类后面:祖先链是 子类 → 模块(后 include 的先找)→ 父类 → ...。所以后 include 的模块会覆盖先 include 的同名方法。
  3. extend 给「类本身」加方法:class Foo; extend M; end 让 M 的方法成为 Foo 的类方法。Rails 的 belongs_to、has_many 就是这样定义的。
  4. super 不带括号 vs 带括号:super 把所有参数原样传给父类同名方法;super() 不传任何参数;super(a, b) 显式传指定参数。三种写法语义不同。
  5. singleton class 是每个对象的「影子类」:def obj.method 或 class << obj 都改的是它。Ruby 的类方法本质就是「类的 singleton class 上的实例方法」——理解了这点,所有 Rails 类方法魔法都能看穿。

你可能会懵的地方

  • 为什么 Ruby 用 Mixin 不用多重继承? 多重继承有菱形问题(父类 B、C 都继承 A,D 继承 B、C 时 A 的状态算几份?)。Mixin 是线性插入祖先链,没有歧义。Java 8 的 default method 本质也是在解决这个问题。

  • self 到底指向谁? 看上下文:类定义体里 self 是类本身(所以 def self.foo 定义类方法);实例方法里 self 是接收者对象;模块里 self 是模块。Ruby 的 self 随执行位置动态变化。

  • 模块能不能实例化? 不能。Module.new 可以造匿名模块,但 MyModule.new 会报错。模块只是「方法和常量的容器」,必须混进类里才能用。

  • is_a?、instance_of?、kind_of? 的区别? instance_of? 严格判断类相等;is_a?(别名 kind_of?)沿祖先链判断,包括父类和 include 的模块。Rails 里鸭子类型更常用 respond_to?。

  • prepend 是什么? Ruby 2.0+ 的第三种混入方式。prepend M 把模块插到类的前面:祖先链变成 M → 本类 → ...。用于做 AOP 式的包装(在方法前后加逻辑),比 alias_method_chain 干净。

错法对照

错法问题正解
直接 @name 暴露给外部外部无法访问用 attr_accessor/reader/writer
子类 initialize 忘了 super父类状态没初始化子类构造里先 super(...)
include 期望加类方法include 只加实例方法类方法用 extend,或 ActiveSupport::Concern
滥用 Monkey Patching全局污染、冲突难查Refinements 或装饰器模式
super 与 super() 混用参数意外传/不传明确想传参就带括号

卡点急救

  1. undefined method 'xxx' for nil:NilClass:变量没赋值或方法返回了 nil。用 &.xxx 或在调用前 puts obj.inspect 排查。
  2. 方法没找到但明明定义了:检查是不是 include 写成了 extend(或反过来)。用 MyClass.ancestors 看模块是否真在链上。
  3. super 报错 ArgumentError:父类同名方法参数个数不匹配。super(...) 显式指定参数。

面试常问

  • include、extend、prepend 的区别? include:模块方法成为实例方法,模块插到类后面;extend:模块方法成为类方法,加到 singleton class;prepend:模块插到类前面,方法查找先命中模块,常用于 AOP。用 ancestors 可以验证。

  • Ruby 的类方法本质是什么? 类方法是「类对象的 singleton class 上的实例方法」。def self.foo 实际是 class << self; def foo; end; end 的语法糖。所以类方法也走方法查找链。

  • Ruby 如何实现多态? 鸭子类型(Duck Typing):「如果它走像鸭子、叫像鸭子,它就是鸭子」。Ruby 不关心对象的类,只关心它是否响应某个方法(respond_to?)。同名方法在不同类各自实现即可。

自测三问

  1. attr_accessor :name 展开后等价于哪几个方法?
  2. 祖先链 Dog.ancestors 返回 [Dog, Bark, Animal, Object, Kernel, BasicObject],方法查找顺序是什么?
  3. include 和 extend 混用会怎样?如何给一个类同时加实例方法和类方法?

小结一句话

类是蓝图、模块是功能包、祖先链是方法查找地图——掌握了 include/extend/prepend 和 singleton class,Ruby OOP 就通了。

延伸

  • Ruby Module 官方文档
  • 下一课:元编程——define_method、method_missing 与 DSL 的魔法。
加载本节练习…
← 上一课方法与 Blocks下一课 →元编程