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_dog。initialize 方法接收了参数 "Buddy" 和 "金毛寻回犬",并使用它们来设置 @name 和 @breed 这两个实例变量。
1.1 new 方法的底层运作
new 是一个类方法(Class Method)(意味着它是直接在类本身上调用的,而不是在类的实例上调用),负责开辟天地、创建新对象。在幕后,new 执行了以下关键步骤:
- 为新对象分配内存。
- 在新对象上调用
initialize方法(如果存在),并将传递给new的所有参数原封不动地转发给它。 - 返回这个刚刚创建并初始化完毕的对象。
在日常开发中,你极少需要亲自重写 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 接收 width 和 height,并将它们分别赋给 @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_reader、attr_writer 和 attr_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 类的实例,使用不同的初始值初始化它们,然后调用它们的方法来模拟一场战斗。这生动地展现了实例在模拟现实(或虚拟)世界交互中的核心作用。