Wspólny interfejs
Część sekcji Programowanie obiektowe ścieżki Lua w Coddy. Lekcja 40 z 70.
Duck typing pokazało nam, że niezwiązane ze sobą klasy mogą współpracować, jeśli mają metody o tych samych nazwach. Ta idea prowadzi do potężnej koncepcji projektowej: wspólny interfejs. Gdy celowo projektujesz wiele klas tak, aby miały takie same sygnatury metod, tworzysz wymienne komponenty.
Wyobraź sobie, że tworzysz system rejestrowania. Możesz chcieć wysyłać komunikaty do różnych miejsc docelowych: do pliku, na ekran, a nawet do sieci. Zamiast pisać osobny kod dla każdego z nich, projektujesz wszystkie klasy wyjściowe tak, aby implementowały tę samą metodę :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)
endTeraz każdy kod, który musi wyświetlać komunikaty, może akceptować oba typy:
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] Uruchamianie
logMessage(screen, "Starting up") -- [SCREEN] UruchamianieFunkcja logMessage nie wie ani nie obchodzi jej, jaką klasę otrzymuje. Oczekuje tylko metody :write(). Dzięki temu Twój kod jest elastyczny. Możesz później dodać nowe typy wyjścia bez zmieniania istniejących funkcji.
Wyzwanie
ŁatwyZbudujmy system przetwarzania płatności, w którym można używać różnych metod płatności zamiennie! Utworzysz dwie całkowicie niezależne klasy płatności ze wspólnym interfejsem — metodą :process(amount), dzięki czemu każdy kod obsługujący płatności będzie działał z dowolną z nich, nie wiedząc, z którą ma do czynienia.
Rozmieścisz swój kod w trzech plikach:
CreditCard.lua: Klasa płatności z konstruktorem:new(cardNumber), który przechowuje numer karty. Jej metoda:process(amount)powinna wypisywaćCharging {amount} to card {cardNumber}.BankTransfer.lua: Całkowicie oddzielna klasa płatności z konstruktorem:new(accountId). Jej metoda:process(amount)powinna wypisywaćTransferring {amount} from account {accountId}.main.lua: Utwórz funkcję o nazwiehandlePayment(paymentMethod, amount), która przyjmuje dowolny obiekt płatności i kwotę, a następnie wywołuje na nim:process(amount). Ta funkcja nie musi wiedzieć, czy ma do czynienia z kartą kredytową, czy przelewem bankowym. Oczekuje jedynie, że obiekt będzie mieć metodę:process().
Otrzymasz trzy dane wejściowe:
- Numer karty kredytowej (np.
4532-1234-5678) - Identyfikator konta bankowego (np.
ACC-9876) - Kwotę płatności (np.
150)
W głównym pliku utwórz po jednej instancji każdego typu płatności, używając dwóch pierwszych danych wejściowych. Następnie użyj funkcji handlePayment, aby przetworzyć podaną kwotę za pomocą obu metod płatności: najpierw karty kredytowej, a potem przelewu bankowego.
Na przykład, jeśli danymi wejściowymi są 1111-2222-3333, ACC-5555 i 75, wynikiem powinno być:
Charging 75 to card 1111-2222-3333
Transferring 75 from account ACC-5555Zaletą tego rozwiązania jest to, że funkcja handlePayment działa z każdym obiektem implementującym :process(). Jutro możesz dodać klasę PayPal, a jeśli będzie mieć metodę :process(amount), będzie działać z istniejącą funkcją bez żadnych zmian!
Spróbuj swoich sił
-- Zaimportuj klasy płatności
local CreditCard = require('CreditCard')
local BankTransfer = require('BankTransfer')
-- Odczytaj dane wejściowe
local cardNumber = io.read()
local accountId = io.read()
local amount = tonumber(io.read())
-- TODO: Utwórz funkcję handlePayment, która przyjmuje dowolny obiekt płatności
-- oraz kwotę, a następnie wywołuje na nim :process(amount)
-- TODO: Utwórz instancje CreditCard i BankTransfer na podstawie danych wejściowych
-- TODO: Użyj handlePayment, aby przetworzyć kwotę obiema metodami płatności
-- Najpierw kartą kredytową, a potem przelewem bankowym
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Koncepcja „self”
Tabele z funkcjamiJawne „self”Składnia z dwukropkiemKropka czy dwukropekPodsumowanie — poruszający się punkt4Projekt: Bank cyfrowy
Konfiguracja projektuMetoda wpłaty7Polimorfizm i nadpisywanie
Nadpisywanie metodWywoływanie metod klasy nadrzędnejTypowanie kaczeWspólny interfejsSprawdzanie typuPodsumowanie — role pracowników10Projekt: Menedżer kształtów
Konfiguracja projektuKlasa prostokątaKlasa kołaMetoda obwoduKolekcja kształtówPole całkowiteFiltrowanie kształtów2Wzorzec prototypu klasy
Koncepcja prototypuŁączenie za pomocą __indexKonstruktor :new()Inicjalizacja atrybutówNiezależne instancjePodsumowanie – fabryka samochodów5Przeciążanie operatorów w OOP
Dodawanie obiektówOdejmowanie obiektówŁączenie obiektówPorównywanie obiektów (<, >)Powtórka — działania na portfelu8Hermetyzacja
Konwencje nazewnictwaDomknięcia dla prywatnościDostęp za pomocą domknięćTabele tylko do odczytuLogika walidacjiPodsumowanie — bezpieczny sejf11Wzorce projektowe (podstawy)
Funkcje fabrykująceTabela SingletonWzorzec iteratoraObserwator (nasłuchujący)Powtórka — fabryka loggera3Stan i zachowanie obiektu
Zmienne instancjiMetody dostępoweMetody ustawiająceWłaściwości obliczaneFormatowanie ciągów znakówSprawdzanie równościPodsumowanie – ocena ucznia6Podstawy dziedziczenia
Konfiguracja dziedziczeniaDziedziczenie metodRozszerzanie konstruktoraDodawanie metod potomnychWspólne i unikalne elementyPowtórka — hierarchia kształtówPoćwicz samodzielnie: Kompilator Lua online