Menu
Coddy logo textTech

Ortak Arayüz

Coddy'nin Lua Journey'sinin Object Oriented Programming bölümünün bir parçası — ders 40 / 70.

Ördek tiplemesi (Duck typing), ilgisiz sınıfların metot isimlerini paylaşmaları durumunda birlikte çalışabileceğini bize gösterdi. Bu fikir, güçlü bir tasarım kavramına yol açar: ortak arayüz. Birden fazla sınıfı kasıtlı olarak aynı metot imzalarına sahip olacak şekilde tasarladığınızda, birbirinin yerine geçebilen bileşenler oluşturursunuz.

Bir günlükleme sistemi oluşturduğunuzu hayal edin. Mesajları farklı hedeflere—bir dosyaya, ekrana veya hatta bir ağa—göndermek isteyebilirsiniz. Her biri için ayrı kod yazmak yerine, tüm çıktı sınıflarınızı aynı :write() metodunu uygulayacak şekilde tasarlarsınız:

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

Artık mesaj çıktısı vermesi gereken herhangi bir kod, her iki türü de kabul edebilir:

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

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

logMessage(file, "Starting up")    -- [DOSYA:app.log] Başlatılıyor
logMessage(screen, "Starting up")  -- [EKRAN] Başlatılıyor

logMessage fonksiyonu hangi sınıfı aldığını bilmez veya umursamaz. Sadece bir :write() metodu bekler. Bu, kodunuzu esnek hale getirir; mevcut fonksiyonları değiştirmeden daha sonra yeni çıktı türleri ekleyebilirsiniz.

challenge icon

Görev

Kolay

Farklı ödeme yöntemlerinin birbirinin yerine kullanılabildiği bir ödeme işleme sistemi kuralım! Ortak bir arayüzü—bir :process(amount) metodunu—paylaşan, tamamen birbirinden bağımsız iki ödeme sınıfı oluşturacaksınız; bu sayede ödemeleri işleyen herhangi bir kod, aradaki farkı bilmeden her iki tiple de çalışabilecektir.

Kodunuzu üç dosya halinde düzenleyeceksiniz:

  • CreditCard.lua: Kart numarasını saklayan bir :new(cardNumber) yapıcı metoduna (constructor) sahip bir ödeme sınıfı. Bu sınıfın :process(amount) metodu "Charging " .. amount .. " to card " .. self.cardNumber (veya Charging {amount} to card {cardNumber} formatında) yazdırmalıdır.
  • BankTransfer.lua: Bir :new(accountId) yapıcı metoduna sahip, tamamen ayrı bir ödeme sınıfı. Bu sınıfın :process(amount) metodu "Transferring " .. amount .. " from account " .. self.accountId (veya Transferring {amount} from account {accountId} formatında) yazdırmalıdır.
  • main.lua: Herhangi bir ödeme nesnesini ve bir tutarı kabul eden, ardından bu nesne üzerinde :process(amount) metodunu çağıran handlePayment(paymentMethod, amount) adında bir fonksiyon oluşturun. Bu fonksiyonun bir kredi kartıyla mı yoksa banka havalesiyle mi uğraştığını bilmesine gerek yoktur; sadece nesnenin bir :process() metoduna sahip olmasını bekler.

Üç girdi alacaksınız:

  1. Bir kredi kartı numarası (örneğin, 4532-1234-5678)
  2. Bir banka hesap kimliği (örneğin, ACC-9876)
  3. Bir ödeme tutarı (örneğin, 150)

Ana dosyanızda, ilk iki girdiyi kullanarak her ödeme tipinden birer örnek (instance) oluşturun. Ardından, verilen tutarı her iki ödeme yöntemiyle işlemek için handlePayment fonksiyonunuzu kullanın—önce kredi kartı, sonra banka havalesi.

Örneğin, girdiler 1111-2222-3333, ACC-5555 ve 75 ise çıktı şu şekilde olmalıdır:

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

Bu tasarımın güzelliği, handlePayment fonksiyonunuzun :process() metodunu uygulayan herhangi bir nesneyle çalışabilmesidir. Yarın bir PayPal sınıfı ekleyebilirsiniz ve :process(amount) metoduna sahip olduğu sürece, mevcut fonksiyonunuzla hiçbir değişiklik yapmadan çalışacaktır!

Kopya kağıdı

Ortak bir arayüz (common interface), birbiriyle ilişkisiz birden fazla sınıfın kasıtlı olarak aynı metot imzalarını uyguladığı ve böylece birbirlerinin yerine kullanılabildiği bir tasarım desenidir.

Sınıfları metot adlarını paylaşacak şekilde tasarlayarak, bu arayüzü uygulayan herhangi bir nesneyle çalışan fonksiyonlar yazabilirsiniz:

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

Fonksiyonlar, beklenen metodu uygulayan herhangi bir nesneyi kabul edebilir:

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 fonksiyonunun hangi sınıfı aldığını bilmesine gerek yoktur; yalnızca bir :write() metodu bekler. Bu, kodu esnek hale getirir ve mevcut fonksiyonları değiştirmeden yeni türler eklemeye olanak tanır.

Kendin dene

-- Ödeme sınıflarını içe aktar
local CreditCard = require('CreditCard')
local BankTransfer = require('BankTransfer')

-- Girdileri oku
local cardNumber = io.read()
local accountId = io.read()
local amount = tonumber(io.read())

-- TODO: Herhangi bir ödeme nesnesini ve bir tutarı kabul eden,
-- ardından üzerinde :process(amount) yöntemini çağıran handlePayment fonksiyonunu oluşturun


-- TODO: Girdileri kullanarak CreditCard ve BankTransfer örneklerini oluşturun


-- TODO: Tutarı her iki ödeme yöntemiyle işlemek için handlePayment fonksiyonunu kullanın
-- Önce kredi kartı, ardından banka havalesi
quiz iconKendini test et

Bu ders kısa bir quiz içerir. Soruları yanıtlamak ve ilerlemeni kaydetmek için derse başla.

Object Oriented Programming bölümündeki tüm dersler