Menu
CoddyTech

Mẫu Builder

Thuộc phần Lập trình hướng đối tượng trong hành trình Ruby của Coddy. Bài học 48 trên 57.

Mẫu builder tạo một đối tượng phức tạp theo từng bước. Mỗi bước là một phương thức lưu một lựa chọn rồi trả về builder, nhờ vậy các bước có thể nối tiếp nhau; build kiểm tra các lựa chọn và trả về đối tượng hoàn chỉnh:

class Pizza
  def initialize(size, toppings)
    @size = size
    @toppings = toppings
  end

  def to_s
    "#{@size} pizza with #{@toppings.join(", ")}"
  end
end

class PizzaBuilder
  def initialize
    @toppings = []
  end

  def size(value)
    @size = value
    self
  end

  def topping(name)
    @toppings << name
    self
  end

  def build
    raise ArgumentError, "size is required" if @size.nil?

    Pizza.new(@size, @toppings)
  end
end

puts PizzaBuilder.new.size("large").topping("cheese").topping("ham").build

Đầu ra:

large pizza with cheese, ham

Vì builder chỉ tạo đối tượng trong build, nó có thể kiểm tra toàn bộ tổ hợp cùng lúc, chẳng hạn như một phần bắt buộc hoặc hai tùy chọn không thể kết hợp với nhau:

class TicketBuilder
  def seat(value)
    @seat = value
    self
  end

  def standing
    @standing = true
    self
  end

  def build
    raise ArgumentError, "seat and standing exclude each other" if @seat && @standing

    @standing ? "standing ticket" : "ticket for seat #{@seat || "any"}"
  end
end

puts TicketBuilder.new.seat("B7").build
puts TicketBuilder.new.standing.build
begin
  TicketBuilder.new.seat("A1").standing.build
rescue ArgumentError => e
  puts e.message
end

Đầu ra:

ticket for seat B7
standing ticket
seat and standing exclude each other

Một cách viết phổ biến trong Ruby là truyền đối tượng dựng vào một khối lệnh. Phương thức lớp tạo đối tượng dựng, gọi khối với đối tượng đó rồi gọi build ở cuối:

class Email
  attr_accessor :to, :subject, :lines

  def self.compose
    email = new
    email.lines = []
    yield email
    email
  end

  def to_s
    "To: #{to}\nSubject: #{subject}\n\n#{lines.join("\n")}"
  end
end

mail = Email.compose do |e|
  e.to = "ada@x.io"
  e.subject = "Hi"
  e.lines << "Hello Ada,"
  e.lines << "see you soon."
end
puts mail

Đầu ra:

To: ada@x.io
Subject: Hi

Hello Ada,
see you soon.

Với một vài giá trị tùy chọn, đối số từ khóa có giá trị mặc định trong Ruby thường đã đủ. Builder hữu ích khi các thành phần có thể lặp lại, thứ tự quan trọng hoặc cần kiểm tra tổ hợp trước khi đối tượng được tạo:

Pizza.new(size: "large", crust: "thin")          # Một vài tùy chọn: từ khóa (keyword)

Query.new.from("users").where("age > 18").where("active = 1").limit(10).to_s
challenge icon

Thử thách

Dễ

Hoàn thiện PizzaBuilder và Pizza. Các bước của builder là size(value), crust(value) và topping(name) (có thể lặp lại), trả về builder. build gây ra ArgumentError với size is required khi chưa có kích cỡ, dùng lớp vỏ classic khi chưa chọn lớp vỏ nào và trả về một Pizza. Pizza#to_s trả về large thin pizza with cheese, ham hoặc small classic pizza with no toppings.

Mỗi dòng đầu vào là một đơn hàng, chẳng hạn như size=large crust=thin topping=cheese topping=ham. Code được cung cấp gửi từng phần đến builder theo thứ tự, bằng cách nối chuỗi lời gọi, rồi in ra bánh pizza hoặc thông báo lỗi.

Code của bạn nằm trong pizza.rb và pizza_builder.rb. main.rb chứa code đầu vào/đầu ra được cung cấp và bạn không thể chỉnh sửa tệp này.

Tự làm thử

require_relative 'pizza'
require_relative 'pizza_builder'

# Đoạn code đầu vào/đầu ra có sẵn: giữ nguyên
input = []
while (line = gets)
  input << line.chomp
end
input.each do |line|
  builder = line.split.reduce(PizzaBuilder.new) do |b, part|
    step, value = part.split("=")
    b.public_send(step, value)
  end
  begin
    puts builder.build
  rescue ArgumentError => e
    puts e.message
  end
end
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch Ruby online