Wspólny interfejs
Część sekcji Programowanie obiektowe ścieżki Lua w Coddy. Lekcja 40 z 70.
Duck typing pokazał nam, że niezwiązane ze sobą klasy mogą współpracować, jeśli mają metody o takich samych nazwach. Ta idea prowadzi do potężnej koncepcji projektowej: wspólnego interfejsu. 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: do pliku, na ekran, a nawet przez sieć. 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 interesuje się tym, jaką klasę otrzymuje. Oczekuje jedynie 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
ŁatwyStwórzmy 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, które mają wspólny interfejs — metodę :process(amount). Dzięki temu każdy kod obsługujący płatności będzie działać z dowolną z tych klas, nie wiedząc, czym się różnią.
Rozmieścisz 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 niezależna 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 obsługuje kartę kredytową, czy przelew bankowy. Wystarczy, że obiekt ma metodę:process().
Otrzymasz trzy dane wejściowe:
- Numer karty kredytowej (np.
4532-1234-5678) - ID konta bankowego (np.
ACC-9876) - Kwotę płatności (np.
150)
W pliku głównym 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ę przy użyciu obu metod płatności: najpierw karty kredytowej, a potem przelewu bankowego.
Na przykład, jeśli dane wejściowe to 1111-2222-3333, ACC-5555 i 75, wynikiem powinno być:
Charging 75 to card 1111-2222-3333
Transferring 75 from account ACC-5555Zaletą tego projektu jest to, że funkcja handlePayment działa z każdym obiektem implementującym :process(). Możesz jutro dodać klasę PayPal i o ile będzie miała metodę :process(amount), zadział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