Ruby 零基础教程

Ruby 类的实例化

创建类的实例(通常被称为“对象”)是面向对象编程(OOP)中的核心概念。正是通过这个过程,你将类所定义的“蓝图”赋予了生命,让你能够在代码中操作具有具体属性和行为的有形实体。

1. 实例化的基础

实例化就是从类创建对象的行为。在 Ruby 中,这通常通过使用 new 方法来完成。这个方法继承自 Object 类(Ruby 中所有类的共同祖先)。当你对一个类调用 new 时,Ruby 会为新对象在内存中分配空间,然后调用 initialize 方法(如果已定义)来设置该对象的初始状态。

来看一个简单的例子:

class Dog
  def initialize(name, breed)
    @name = name
    @breed = breed
  end

  def bark
    puts "汪汪!"
  end
end

# 创建 Dog 类的一个实例
my_dog = Dog.new("Buddy", "金毛寻回犬")

# puts my_dog.name # 注意:这里如果直接访问会报错,因为没有设置 attr_reader
my_dog.bark        # 输出: 汪汪!

在这个例子中,Dog.new("Buddy", "金毛寻回犬") 创建了一个新的 Dog 对象,并将其赋值给变量 my_doginitialize 方法接收了参数 "Buddy""金毛寻回犬",并使用它们来设置 @name@breed 这两个实例变量。

1.1 new 方法的底层运作

new 是一个类方法(Class Method)(意味着它是直接在类本身上调用的,而不是在类的实例上调用),负责开辟天地、创建新对象。在幕后,new 执行了以下关键步骤:

  1. 为新对象分配内存。
  2. 在新对象上调用 initialize 方法(如果存在),并将传递给 new 的所有参数原封不动地转发给它。
  3. 返回这个刚刚创建并初始化完毕的对象。

在日常开发中,你极少需要亲自重写 new 方法。Ruby 提供的默认实现足以应付绝大多数场景。只有在极其高阶的设计中(例如,当你想要实现单例模式,确保一个类只能被创建一个实例时),你才会去触碰它。

1.2 initialize 方法 (构造函数)

initialize 是一个实例方法(Instance Method)。每当使用 new 创建新对象后,它就会被自动调用。它的首要任务是设置对象的初始状态,这通常是通过为实例变量赋值来实现的。它充当了类的构造函数 (Constructor)

  • 目的: 设置对象的初始状态。
  • 参数initialize 可以接收任意数量的参数,这些参数是在你调用 new 时传进来的。
  • 实例变量: 在 initialize 内部,你通常会为实例变量(以 @ 开头的变量)赋值。这些变量保存着对象的私有数据,可以被该类中的其他实例方法访问。
class Rectangle
  def initialize(width, height)
    @width = width
    @height = height
  end

  def area
    @width * @height
  end
end

# 创建一个 Rectangle (矩形) 对象
my_rectangle = Rectangle.new(10, 5)

puts my_rectangle.area # 输出: 50

在这个例子中,initialize 接收 widthheight,并将它们分别赋给 @width@height 实例变量。随后,area 方法就可以使用这些实例变量来计算矩形的面积了。

2. 在实例化时传递参数

当创建类的实例时,你可以向 new 方法传递参数,这些参数会无缝衔接传递给 initialize 方法。这使得你可以为创建的每一个对象定制其初始状态。

2.1 关键字参数 (Keyword Arguments)

Ruby 强烈支持使用关键字参数。在处理具有多个参数的方法时,这能极大地提升代码的可读性和可维护性。

class Book
  def initialize(title:, author:, pages:)
    @title = title
    @author = author
    @pages = pages
  end

  def to_s
    "书名: #{@title}, 作者: #{@author}, 页数: #{@pages}"
  end
end

# 使用关键字参数创建一个 Book 对象
my_book = Book.new(title: "Ruby 编程语言", author: "David Flanagan", pages: 600)

puts my_book.to_s # 输出: 书名: Ruby 编程语言, 作者: David Flanagan, 页数: 600

使用关键字参数可以让人一目了然地看出哪个值被赋给了哪个参数,拒绝“盲猜”。

2.2 默认参数值

你也可以在 initialize 方法中为参数提供默认值。如果在创建对象时没有提供这些参数,系统就会自动采用这些合理的默认设置。

class Circle
  def initialize(radius: 1, color: "红色")
    @radius = radius
    @color = color
  end

  def area
    Math::PI * @radius**2
  end

  def to_s
    "半径: #{@radius}, 颜色: #{@color}"
  end
end

# 创建一个使用默认值的 Circle 对象
my_circle = Circle.new
puts my_circle.to_s   # 输出: 半径: 1, 颜色: 红色
puts my_circle.area   # 输出: 3.141592653589793

# 创建一个使用自定义值的 Circle 对象
another_circle = Circle.new(radius: 5, color: "蓝色")
puts another_circle.to_s  # 输出: 半径: 5, 颜色: 蓝色
puts another_circle.area  # 输出: 78.53981633974483

在这里,如果在创建 Circle 时不传参,radius 默认就是 1,color 默认就是 "红色"。

3. 访问与修改对象状态 (复习与深化)

在创建类的实例之后,你经常需要访问并修改它的状态(即它的实例变量的值)。如前一课所述,Ruby 提供了 attr_readerattr_writerattr_accessor 来优雅地处理这件事。

  • attr_reader:只读。创建 Getter 方法。
  • attr_writer:只写。创建 Setter 方法。
  • attr_accessor:读写全开。同时创建 Getter 和 Setter 方法。
class Car
  attr_accessor :make, :model, :year
  
  def initialize(make, model, year)
    @make = make
    @model = model
    @year = year
  end
  
  def to_s
    "#{@year} #{@make} #{@model}"
  end
end

car = Car.new("丰田", "凯美瑞", 2020)
puts car.to_s # 输出: 2020 丰田 凯美瑞

car.year = 2022 # 使用由 attr_accessor 自动生成的 Setter 方法
puts car.to_s   # 输出: 2022 丰田 凯美瑞
puts car.make   # 使用由 attr_accessor 自动生成的 Getter 方法 (输出: 丰田)

架构思考: 虽然 attr_accessor 提供了极大的便利,但在设计类时,务必审慎考虑是否所有的变量都应该暴露给外部读写。有时,为了维护对象内部状态的一致性,限制某些变量的访问权限(例如只使用 attr_reader,或者完全私有)是极其必要的。

4. 实战案例:构建简易回合制游戏

让我们构建一个非常基础的游戏场景,在一个更具实战意义的上下文中演示如何创建类的实例。

class Player
  attr_accessor :name, :health, :power

  def initialize(name, health: 100, power: 10)
    @name = name
    @health = health
    @power = power
  end

  def attack(enemy)
    damage = rand(1..@power)  # 造成的伤害基于自身的力量值随机浮动
    enemy.health -= damage
    puts "#{@name} 攻击了 #{enemy.name},造成了 #{damage} 点伤害!"
    puts "#{enemy.name} 的剩余生命值: #{enemy.health}"
  end

  def is_alive?
    @health > 0
  end
end

# 实例化两个玩家对象
player1 = Player.new("勇者", health: 120, power: 15)
player2 = Player.new("魔王", health: 80, power: 20)

puts "--- 战斗开始 ---"

# 模拟一场简单的生死决斗
while player1.is_alive? && player2.is_alive?
  player1.attack(player2)
  break unless player2.is_alive? # 如果魔王倒下,提前结束循环
  
  player2.attack(player1)
end

puts "--- 战斗结束 ---"

if player1.is_alive?
  puts "#{player1.name} 获得了最终的胜利!"
else
  puts "#{player2.name} 获得了最终的胜利!"
end

这个例子完美地展示了如何创建 Player 类的实例,使用不同的初始值初始化它们,然后调用它们的方法来模拟一场战斗。这生动地展现了实例在模拟现实(或虚拟)世界交互中的核心作用。