Menu
Coddy logo textTech

דפוס Builder

חלק מהיחידה תכנות מונחה עצמים במסלול ה-Ruby של Coddy. שיעור 48 מתוך 57.

תבנית הבונה יוצרת אובייקט מורכב צעד אחר צעד. כל צעד הוא מתודה ששומרת בחירה אחת ומחזירה את הבונה, כך שהצעדים משורשרים; build בודקת את הבחירות ומחזירה את האובייקט המוגמר:

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

פלט:

large pizza with cheese, ham

מכיוון שה־builder יוצר את האובייקט רק בתוך build, הוא יכול לבדוק את כל השילוב בבת אחת, למשל חלק נדרש או שתי אפשרויות שלא יכולות להתקיים יחד:

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

פלט:

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

צורת Ruby נפוצה מעבירה את הבונה לבלוק. המתודה של המחלקה יוצרת את הבונה, מעבירה אותו באמצעות yield וקוראת ל־build בסוף:

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

פלט:

To: ada@x.io
Subject: Hi

Hello Ada,
see you soon.

עבור מספר קטן של ערכים אופציונליים, ארגומנטים של מילות מפתח ב-Ruby עם ערכי ברירת מחדל מספיקים לעיתים קרובות. תבנית בונה מועילה כשאפשר לחזור על חלקים, כשהסדר חשוב או כשצריך לבדוק את הצירוף לפני שהאובייקט נוצר:

Pizza.new(size: "large", crust: "thin")          # מעט אפשרויות: מילות מפתח

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

אתגר

קל

השלימו את PizzaBuilder ואת Pizza. שלבי הבנאי size(value), crust(value) ו־topping(name) (שניתן לחזור עליהם) מחזירים את הבנאי. build מעלה את ArgumentError עם size is required כשלא סופק גודל, משתמש בסוג הבצק classic כשלא נבחר סוג, ומחזיר Pizza. Pizza#to_s מחזירה large thin pizza with cheese, ham, או small classic pizza with no toppings.

כל שורת קלט היא הזמנה אחת, למשל size=large crust=thin topping=cheese topping=ham. הקוד שסופק מעביר כל חלק לבנאי לפי הסדר, באמצעות שרשור, ומדפיס את הפיצה או את הודעת השגיאה.

הקוד שלכם ייכתב בקבצים pizza.rb ו־pizza_builder.rb. הקובץ main.rb מכיל את קוד הקלט/פלט שסופק ואי אפשר לערוך אותו.

נסו בעצמכם

require_relative 'pizza'
require_relative 'pizza_builder'

# קוד קלט/פלט שסופק: יש להשאיר אותו כפי שהוא
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 iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר Ruby אונליין