Menu
Coddy logo textTech

ממשק משותף

חלק מהיחידה תכנות מונחה עצמים במסלול ה-Lua של Coddy. שיעור 40 מתוך 70.

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

דמיינו שאתם בונים מערכת רישום. ייתכן שתרצו לשלוח הודעות ליעדים שונים: קובץ, המסך או אפילו רשת. במקום לכתוב קוד נפרד עבור כל אחד מהם, אתם מתכננים שכל מחלקות הפלט שלכם יממשו את אותה שיטה :write():

local LogFile = {}
LogFile.__index = LogFile

function LogFile:new(filename)
    local obj = {filename = filename}
    setmetatable(obj, LogFile)
    return obj
end

function LogFile:write(message)
    print("[FILE:" .. self.filename .. "] " .. message)
end

local Screen = {}
Screen.__index = Screen

function Screen:new()
    local obj = {}
    setmetatable(obj, Screen)
    return obj
end

function Screen:write(message)
    print("[SCREEN] " .. message)
end

כעת כל קוד שצריך להציג הודעות יכול לקבל כל אחד משני הסוגים:

local function logMessage(output, msg)
    output:write(msg)
end

local file = LogFile:new("app.log")
local screen = Screen:new()

logMessage(file, "Starting up")    -- [FILE:app.log] Starting up
logMessage(screen, "Starting up")  -- [SCREEN] Starting up

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

challenge icon

אתגר

קל

נבנה מערכת לעיבוד תשלומים שבה אפשר להשתמש בשיטות תשלום שונות באופן חלופי! תיצרו שתי מחלקות תשלום נפרדות לחלוטין עם ממשק משותף, מתודת :process(amount), שתאפשר לכל קוד המטפל בתשלומים לעבוד עם כל אחד מהסוגים בלי לדעת מה ההבדל ביניהם.

תארגנו את הקוד בשלושה קבצים:

  • CreditCard.lua: מחלקת תשלום עם בנאי :new(cardNumber) ששומר את מספר הכרטיס. המתודה :process(amount) שלה צריכה להדפיס Charging {amount} to card {cardNumber}.
  • BankTransfer.lua: מחלקת תשלום נפרדת לחלוטין עם בנאי :new(accountId). המתודה :process(amount) שלה צריכה להדפיס Transferring {amount} from account {accountId}.
  • main.lua: צרו פונקציה בשם handlePayment(paymentMethod, amount) שמקבלת כל אובייקט תשלום וסכום, ואז קוראת למתודה :process(amount) שלו. הפונקציה לא צריכה לדעת אם היא מטפלת בכרטיס אשראי או בהעברה בנקאית. היא רק מצפה שלאובייקט תהיה מתודת :process().

תקבלו שלוש קלטים:

  1. מספר כרטיס אשראי (לדוגמה, 4532-1234-5678)
  2. מזהה חשבון בנק (לדוגמה, ACC-9876)
  3. סכום לתשלום (לדוגמה, 150)

בקובץ הראשי, צרו מופע אחד מכל סוג תשלום באמצעות שני הקלטים הראשונים. לאחר מכן השתמשו בפונקציה handlePayment שלכם כדי לעבד את הסכום הנתון באמצעות שתי שיטות התשלום: תחילה כרטיס האשראי, ולאחר מכן ההעברה הבנקאית.

לדוגמה, אם הקלטים הם 1111-2222-3333, ACC-5555 ו-75, הפלט צריך להיות:

Charging 75 to card 1111-2222-3333
Transferring 75 from account ACC-5555

היתרון בעיצוב הזה הוא שפונקציית handlePayment שלכם עובדת עם כל אובייקט שמממש את :process(). תוכלו להוסיף מחר מחלקת PayPal, וכל עוד יש לה מתודת :process(amount), היא תעבוד עם הפונקציה הקיימת שלכם ללא שינויים!

נסו בעצמכם

-- ייבא את מחלקות התשלום
local CreditCard = require('CreditCard')
local BankTransfer = require('BankTransfer')

-- קרא את הקלטים
local cardNumber = io.read()
local accountId = io.read()
local amount = tonumber(io.read())

-- TODO: צור את הפונקציה handlePayment שמקבלת כל אובייקט תשלום
-- וסכום, ואז קוראת למתודה :process(amount) שלו


-- TODO: צור מופעים של CreditCard ושל BankTransfer באמצעות הקלטים


-- TODO: השתמש ב-handlePayment כדי לעבד את הסכום באמצעות שתי שיטות התשלום
-- קודם כרטיס האשראי, ואז ההעברה הבנקאית
quiz iconבחנו את עצמכם

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

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

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