Ruby 零基础教程

Ruby 属性与方法

本章将介绍 OOP 中的两个基本概念:属性(Attributes)和方法(Methods)。属性定义了对象所持有的数据,而方法定义了对象可以执行的操作。理解如何定义和使用属性与方法,可以创建健壮且易于维护的 Ruby 应用程序。

1. 定义属性

属性是用于保存与对象相关联的数据的变量,它们代表了对象的状态。在 Ruby 中,我们使用 @ 符号加上属性名称(例如 @name@age)来定义属性。通常,这些属性会在 initialize 方法中被初始化,该方法充当了类的构造函数。

1.1 在 initialize 方法中初始化属性

initialize 是 Ruby 类中的一个特殊方法。当你创建一个类的新实例(对象)时,它会被自动调用。通常,这里就是你设置对象属性初始值的地方。

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

# 创建一个新的 Dog 对象
my_dog = Dog.new("Buddy", "金毛寻回犬")

在这个例子中,Dog 类有两个属性:@name@breed。当我们使用 Dog.new("Buddy", "金毛寻回犬") 创建一个新的 Dog 对象时,initialize 方法会被调用,并传入参数 "Buddy""金毛寻回犬"。随后,这些值被赋给了新 Dog 对象的 @name@breed 属性。

1.2 直接访问属性(不推荐)

虽然在技术上可以通过类似外部调用的方式尝试访问属性,但在 OOP 中,直接在类外部访问内部变量通常被视为一种糟糕的实践。直接访问属性违反了**封装(Encapsulation)**原则,该原则规定对象的内部状态应该对外部世界隐藏。

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

my_dog = Dog.new("Buddy", "金毛寻回犬")

# 避免这样做:
# puts my_dog.name # 这将导致错误,因为在类外部 @name 是私有的(没有读取方法)

1.3 使用 attr_reader、attr_writer 和 attr_accessor

为了优雅地控制属性的读取和修改,Ruby 提供了几个非常方便的方法(宏):

  • attr_reader:为属性创建一个 Getter(读取)方法,允许你读取它的值。
  • attr_writer:为属性创建一个 Setter(写入)方法,允许你更改它的值。
  • attr_accessor:同时为属性创建一个 Getter 和一个 Setter 方法。
class Dog
  attr_reader :name, :breed # 为 name 和 breed 创建 Getter (读取) 方法
  attr_writer :age # 为 age 创建一个 Setter (写入) 方法
  attr_accessor :tricks # 为 tricks 同时创建 Getter 和 Setter 方法

  def initialize(name, breed, age)
    @name = name
    @breed = breed
    @age = age
    @tricks = [] # 将 tricks 初始化为一个空数组
  end

  def add_trick(trick)
      @tricks << trick
  end
end

my_dog = Dog.new("Buddy", "金毛寻回犬", 3)

puts my_dog.name  # 访问 name 属性 (调用 Getter)
puts my_dog.breed # 访问 breed 属性 (调用 Getter)

my_dog.age = 4   # 设置 age 属性 (调用 Setter)
# puts my_dog.age # 这一行如果取消注释会导致错误,因为 age 没有 Getter 方法

my_dog.add_trick("接飞盘")
my_dog.add_trick("打滚")
puts my_dog.tricks # 访问 tricks 属性 (调用 Getter)

my_dog.tricks = ["装死"]
puts my_dog.tricks # 在使用 Setter 修改后,再次访问 tricks 属性 (调用 Getter)

在这个例子中:

  • attr_reader :name, :breed 创建了 namebreed 方法,分别返回 @name@breed 的值。你可以读取这些属性,但无法从类外部直接修改它们。
  • attr_writer :age 创建了一个 age= 方法,允许你从类外部更改 @age 的值,但你仍然无法直接读取它。
  • attr_accessor :tricks 同时创建了读取方法 tricks 和写入方法 tricks=。你可以从类外部自由地读取和修改 @tricks 的值。

2. 定义方法

方法定义了对象的行为——即它可以执行的操作。方法使用 def 关键字来定义,紧接着是方法名、该方法接收的任何参数,以及方法主体。

2.1 实例方法 (Instance Methods)

实例方法是在类的特定实例(对象)上运行的方法。它们可以访问该对象的属性,并可以修改对象的状态。

class Dog
  attr_reader :name, :breed
  attr_accessor :age, :tricks

  def initialize(name, breed, age)
    @name = name
    @breed = breed
    @age = age
    @tricks = []
  end

  def bark
    puts "汪汪!"
  end

  def introduce
    puts "你好,我的名字是 #{@name},我是一只 #{@breed}。"
  end

  def add_trick(trick)
    @tricks << trick
  end
end

my_dog = Dog.new("Buddy", "金毛寻回犬", 3)

my_dog.bark       # 调用 bark 方法
my_dog.introduce  # 调用 introduce 方法
my_dog.add_trick("接飞盘") # 调用 add_trick 方法
puts my_dog.tricks

在这个例子中:

  • bark 是一个让狗叫的方法。它只是简单地在控制台打印 "汪汪!"。
  • introduce 是一个介绍狗的方法,它使用了狗的 @name@breed 属性。
  • add_trick 是一个向狗的 @tricks 数组中添加技能的方法。

2.2 方法参数与返回值

方法可以接收参数(即调用方法时传递给它的值)。方法也可以返回值(即方法执行完毕后的结果)。

class Dog
  attr_reader :name, :breed
  attr_accessor :age, :tricks

  def initialize(name, breed, age)
    @name = name
    @breed = breed
    @age = age
    @tricks = []
  end

  # human_years_factor 有一个默认值 7
  def age_in_human_years(human_years_factor = 7)
    @age * human_years_factor # 隐式返回计算结果
  end
end

my_dog = Dog.new("Buddy", "金毛寻回犬", 3)

human_age = my_dog.age_in_human_years # 使用默认的乘数 (7) 调用方法
puts human_age

human_age = my_dog.age_in_human_years(5) # 使用自定义的乘数 (5) 调用方法
puts human_age

在这个例子中:

  • age_in_human_years 是一个计算狗“相当于人类年龄”的方法。它接收一个可选参数 human_years_factor,默认值为 7。该方法会隐式返回计算后的年龄值。

3. 真实世界应用案例

想象一个构建电子商务应用程序的场景。你可能需要一个 Product(产品)类。

class Product
  attr_reader :name, :price
  attr_accessor :quantity

  def initialize(name, price, quantity)
    @name = name
    @price = price
    @quantity = quantity
  end

  def is_available?
    @quantity > 0
  end

  def purchase(amount)
    if is_available? && @quantity >= amount
      @quantity -= amount
      puts "成功购买了 #{amount} 件 #{@name}。"
    else
      puts "抱歉,#{@name} 库存不足。"
    end
  end
end

product = Product.new("T恤", 20, 50)

puts product.is_available? # 检查是否可用
product.purchase(2)        # 购买 2 件
puts product.quantity      # 检查剩余库存
product.purchase(51)       # 尝试购买超过库存的数量

在这里,诸如 namepricequantity 等属性定义了产品的状态。而诸如 is_available?purchase 等方法定义了产品的行为。这完美地展示了属性和方法是如何在面向对象编程中,模拟现实世界实体的基础。