דפוס 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אתגר
קלהשלימו את 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
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
3מתודות ומשתנים של מחלקה
מתודות של מחלקהמשתני מופע של מחלקהקבועיםמתודות ליצירת מופעיםסיכום – קודי קופוןתרגלו בעצמכם: קומפיילר Ruby אונליין