Menu
CoddyTech

工厂方法

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

工厂方法是一种类方法,它会创建并返回一个实例。它为创建对象的一种方式命名。在类方法内部,new 表示 self.new:

class Temperature
  attr_reader :celsius

  def initialize(celsius)
    @celsius = celsius
  end

  def self.from_fahrenheit(f)
    new((f - 32) * 5 / 9.0)
  end

  def self.freezing
    new(0)
  end
end

puts Temperature.from_fahrenheit(212).celsius
puts Temperature.freezing.celsius

输出:

100.0
0

将文本转换为对象是工厂的常见任务。解析逻辑放在类旁边,而 initialize 保持简单:

class Duration
  attr_reader :seconds

  def initialize(seconds)
    @seconds = seconds
  end

  def self.parse(text)
    new(text.split(":").map(&:to_i).reduce(0) { |acc, part| acc * 60 + part })
  end
end

puts Duration.parse("1:30").seconds
puts Duration.parse("2:00:05").seconds

输出:

90
7205

工厂可以拒绝无效输入。返回 nil 后,调用方可以用 if 检查结果;引发错误会停止程序,除非调用方捕获该错误:

class Color
  attr_reader :hex

  def initialize(hex)
    @hex = hex
  end

  def self.parse(text)
    return nil unless text.match?(/\A#[0-9a-f]{6}\z/i)

    new(text.downcase)
  end
end

["#FF8800", "orange"].each do |text|
  color = Color.parse(text)
  puts color ? "ok #{color.hex}" : "not a color: #{text}"
end

输出:

ok #ff8800
not a color: orange

private_class_method :new使new只能在类内部调用,因此工厂方法成了创建对象的唯一方式:

class Money
  attr_reader :cents

  def initialize(cents)
    @cents = cents
  end
  private_class_method :new

  def self.from_cents(cents)
    new(cents)
  end
end

puts Money.from_cents(250).cents
begin
  Money.new(250)
rescue NoMethodError => e
  puts e.message
end

输出:

250
private method `new' called for class Money
challenge icon

挑战

简单

完成 Duration 类。initialize(seconds) 用秒数初始化对象。工厂方法 Duration.parse(text) 读取 m:ss 或 h:mm:ss,工厂方法 Duration.from_minutes(m) 接收整数分钟,plus(other) 返回一个秒数总和的新 Duration,而 to_s 返回格式为 h:mm:ss 的时间,其中分钟和秒数均为两位数。

提供的代码会读取时长:像 45m 这样的行会传给 from_minutes,其余行会传给 parse。代码会打印每个时长,最后打印 total 1:47:10。

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

亲手试试

require_relative 'duration'

# 提供的输入输出代码:保持原样
input = []
while (line = gets)
  input << line.chomp
end
durations = input.map do |line|
  line.end_with?("m") ? Duration.from_minutes(line.to_i) : Duration.parse(line)
end
durations.each { |d| puts d }
puts "total #{durations.reduce(Duration.new(0)) { |sum, d| sum.plus(d) }}"
quiz icon自我测试

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

面向对象编程中的所有课

自己练习: Ruby 在线编译器