Ruby 类与对象
面向对象编程(Object-Oriented Programming,简称 OOP)是现代软件开发的基础。它为你提供了一种强大的编程范式,用于组织代码并对现实世界中的实体进行建模。
本章将带你深入探索 Ruby 中 OOP 的最核心概念:类(Classes)和对象(Objects)。理解这些概念是你构建复杂、高可维护性应用程序的关键。打好这个基础后,我们才能在后续章节中顺利进阶到继承(Inheritance)和模块(Modules)等高级 OOP 主题。
1. 理解类 (Classes)
类是一个蓝图(Blueprint)或模板,用于创建对象。它定义了该类的对象将拥有的属性(数据)和行为(方法)。你可以把类想象成一个饼干模具——它决定了饼干的形状,而烤出来的每一块饼干都是一个独立的对象(实例)。
1.1 定义类
在 Ruby 中,你可以使用 class 关键字来定义一个类,紧接着是类名(必须以大写字母开头),最后以 end 关键字结束类的代码体。
class Dog
# 类的代码体写在这里
end这段代码定义了一个名为 Dog 的空类。它目前什么也做不了,但这是一个完全合法的类定义。
1.2 属性 (Attributes)
属性是与对象关联的数据,它们代表了对象的状态。例如,一个 Dog(狗)对象可能有 name(名字)、breed(品种)和 age(年龄)等属性。
Ruby 提供了一种极其便捷的方式来定义属性,那就是使用 attr_accessor、attr_reader 和 attr_writer 访问器。
attr_accessor:为属性同时创建 Getter(读取)和 Setter(写入)方法。这意味着你可以读取并修改该属性的值。attr_reader:仅为属性创建 Getter(读取)方法。允许你读取属性的值,但不能直接修改它。attr_writer:仅为属性创建 Setter(写入)方法。允许你修改属性的值,但不能直接读取它。
class Dog
attr_accessor :name, :breed, :age
end这段代码为 Dog 类定义了三个属性:name、breed 和 age。因为我们使用了 attr_accessor,所以我们可以随时读取和修改任何 Dog 对象的这三个属性。
1.3 方法 (Methods)
方法定义了对象的行为。它们是操作对象数据(属性)或执行其他动作的函数。例如,一个 Dog 对象可能有 bark(吠叫)、eat(吃)和 sleep(睡)等方法。
class Dog
attr_accessor :name, :breed, :age
def bark
puts "汪汪!"
end
def eat(food)
puts "#{@name} 正在吃 #{food}。"
end
def display_info
puts "名字: #{@name}, 品种: #{@breed}, 年龄: #{@age}"
end
end这段代码为 Dog 类添加了三个方法:
bark:在控制台打印 "汪汪!"。eat:接收一个 food(食物)参数,并打印一条表明这只狗正在吃东西的信息。这演示了方法如何接收参数。display_info:打印狗的名字、品种和年龄。这展示了方法如何访问和使用对象自身的属性。
1.4 initialize 方法 (构造函数)
initialize 是一个特殊的方法,当你创建该类的新对象时,Ruby 会自动调用它。它通常用于初始化对象的属性。在其他编程语言中,这通常被称为类的构造函数 (Constructor)。
class Dog
attr_accessor :name, :breed, :age
def initialize(name, breed, age)
@name = name
@breed = breed
@age = age
end
def bark
puts "汪汪!"
end
def eat(food)
puts "#{@name} 正在吃 #{food}。"
end
def display_info
puts "名字: #{@name}, 品种: #{@breed}, 年龄: #{@age}"
end
end在这段代码中:
initialize方法接收三个参数:name、breed和age。- 在
initialize方法内部,我们将这些参数赋值给对应的实例变量 (Instance Variables):@name、@breed和@age。带有@符号前缀的变量就是实例变量,这意味着它们是属于对象本身的专属数据。
2. 创建对象 (Objects)
对象是类的一个实例 (Instance)。它是根据类定义的蓝图构建出来的具体实体。
2.1 实例化类
要创建一个对象,你需要在这个类上调用 new 方法。当你调用 new 时,Ruby 会在底层自动为你调用 initialize 方法(如果存在的话),以此来设置对象的初始状态。
# 接着上面的 Dog 类定义继续
dog1 = Dog.new("Buddy", "金毛寻回犬", 3)
dog2 = Dog.new("Lucy", "拉布拉多犬", 5)这段代码创建了两个 Dog 对象:
dog1是一只名叫 "Buddy" 的金毛寻回犬,今年 3 岁。dog2是一只名叫 "Lucy" 的拉布拉多犬,今年 5 岁。
每个对象都拥有自己独立的一套属性,这些属性在创建时由 initialize 方法完成了初始化。
2.2 访问属性与调用方法
你可以使用点号表示法 (.) 来访问对象的属性(通过之前用 attr_accessor 和 attr_reader 创建的读取方法)或调用对象的方法。
访问属性:
puts dog1.name # 输出: Buddy
puts dog2.breed # 输出: 拉布拉多犬调用方法:
dog1.bark # 输出: 汪汪!
dog2.eat("狗粮") # 输出: Lucy 正在吃 狗粮。
dog1.display_info # 输出: 名字: Buddy, 品种: 金毛寻回犬, 年龄: 3每个对象都在自己的上下文中执行方法,使用的是属于它自己的属性值。
3. 综合实战:BankAccount 银行账户类
让我们看一个更接近真实业务的例子:一个 BankAccount(银行账户)类。这个例子将向你展示如何利用类和对象来对具有复杂行为的现实世界实体进行建模。
class BankAccount
# 账号和余额对外只读
attr_reader :account_number, :balance
# 账户持有人姓名可以读取和修改
attr_accessor :account_holder_name
def initialize(account_holder_name, account_number, initial_balance)
@account_holder_name = account_holder_name
@account_number = account_number
@balance = initial_balance
end
def deposit(amount)
if amount > 0
@balance += amount
puts "已存款 $#{amount}。当前余额: $#{@balance}"
else
puts "无效的存款金额。"
end
end
def withdraw(amount)
if amount > 0 && amount <= @balance
@balance -= amount
puts "已取款 $#{amount}。当前余额: $#{@balance}"
elsif amount <= 0
puts "无效的取款金额。"
else
puts "余额不足。"
end
end
def display_balance
puts "#{@account_holder_name} 的账户余额为: $#{@balance}"
end
end
# 创建一个 BankAccount 对象
account1 = BankAccount.new("Alice Smith", "1234567890", 1000)
# 访问属性
puts account1.account_holder_name # 输出: Alice Smith
puts account1.account_number # 输出: 1234567890
# 调用方法
account1.deposit(500) # 输出: 已存款 $500。当前余额: $1500
account1.withdraw(200) # 输出: 已取款 $200。当前余额: $1300
account1.display_balance # 输出: Alice Smith 的账户余额为: $1300
account1.withdraw(2000) # 输出: 余额不足。核心要点解析:
attr_reader和attr_accessor: 代码中对account_number和balance使用了attr_reader,因为在类的外部,我们只希望它们能被读取,而不能被随意篡改。account_holder_name可能会变更(比如改名),所以使用了attr_accessor。这体现了对属性的受控访问,是 OOP 的核心原则之一。initialize方法: 它负责设置BankAccount对象的初始状态,接收账户名、账号和初始余额作为参数。deposit和withdraw方法: 这两个方法封装了存取款的业务逻辑。它们内部包含了数据校验,以确保金额是合法的。输出信息也能清晰地反馈操作结果和最新余额。- 余额封装 (Encapsulation): 余额 (
@balance) 被保护在对象内部。外部修改余额的唯一途径是通过对象提供的deposit(存)和withdraw(取)方法,这从根本上保证了数据的完整性和安全性。