Menu
CoddyTech

受保护的方法

本课属于 Coddy Ruby 学习旅程的“面向对象编程”单元。第 18 课,共 57 课。

受保护方法介于公共方法和私有方法之间。外部代码不能调用它,但同一个类的任何对象都可以在另一个对象上调用它。这样,两个对象就能比较那些不会向程序其他部分公开的数据:

class Account
  def initialize(owner, balance)
    @owner = owner
    @balance = balance
  end

  def richer_than?(other)
    balance > other.balance
  end

  protected

  def balance
    @balance
  end
end

ada = Account.new("Ada", 500)
bo = Account.new("Bo", 300)
puts ada.richer_than?(bo)
begin
  ada.balance
rescue NoMethodError => e
  puts e.message
end

输出:

true
protected method `balance' called for an instance of Account

与 private 的区别会在接收者是另一个对象时显现出来。对于受保护的方法,other.balance 可以正常使用;对于私有方法则会失败:

class Sample
  def initialize(n)
    @n = n
  end

  def compare_protected(other)
    other.guarded + guarded
  end

  def compare_private(other)
    other.hidden + hidden
  rescue NoMethodError
    "private refused other.hidden"
  end

  protected

  def guarded
    @n
  end

  private

  def hidden
    @n
  end
end

a = Sample.new(1)
b = Sample.new(2)
puts a.compare_protected(b)
puts a.compare_private(b)

输出:

3
private refused other.hidden

protected 也适用于 attr_reader,这是只在同一个类的对象之间共享值的常用方式,例如用来判断两个对象中谁会获胜:

class Player
  attr_reader :name

  def initialize(name, rating)
    @name = name
    @rating = rating
  end

  def stronger?(other)
    rating > other.rating
  end

  protected

  attr_reader :rating
end

a = Player.new("Ada", 1200)
b = Player.new("Bo", 1100)
puts a.stronger?(b)
puts [a, b].select { |pl| pl.stronger?(b) }.map(&:name).inspect

输出:

true
["Ada"]

选择合适的最窄访问级别。public 用于类的接口,protected 用于该类的对象彼此比较的数据,private 用于其他所有内容:

public      # 任何人都可以访问:order.total
protected   # 类的对象可以访问:在 Account 内使用 other.balance
private     # 只有对象自身可以访问:subtotal、self.subtotal
challenge icon

挑战

简单

完成 Account 类。只有其他账户可以读取余额:将它的读取器设为受保护。richer_than?(other) 比较余额,transfer_to(other, amount) 通过另一个账户的受保护方法 receive(amount) 将钱转到另一个账户,并返回 true;如果余额不足,则返回 false。to_s 只返回账户所有者的姓名。

提供的代码会读取两个账户所有者的姓名和余额,然后读取 transfer a b 50 命令,并打印 ok 或 refused。接着,它会打印 Ada is richer(或 Bo is richer,或 equal),最后尝试从外部读取 balance,并在 Ruby 拒绝时打印 balance is protected。

你的代码写在 account.rb 中。main.rb 包含提供的输入/输出代码,不能编辑。

亲手试试

require_relative 'account'

# 提供的输入输出代码:保持原样。
input = []
while (line = gets)
  input << line.chomp
end
accounts = input[0..1].map do |line|
  owner, balance = line.split
  Account.new(owner, balance.to_i)
end
a, b = accounts
input.drop(2).each do |line|
  _, from, to, amount = line.split
  source = from == "a" ? a : b
  target = to == "a" ? a : b
  puts source.transfer_to(target, amount.to_i) ? "ok" : "refused"
end
if a.richer_than?(b) then puts "#{a} is richer"
elsif b.richer_than?(a) then puts "#{b} is richer"
else puts "equal"
end
begin
  a.balance
  puts "balance is public"
rescue NoMethodError
  puts "balance is protected"
end
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Ruby 在线编译器