Typowanie kacze
Część sekcji Programowanie obiektowe ścieżki Lua w Coddy. Lekcja 39 z 70.
Lua nie przejmuje się klasą obiektu. Liczy się tylko to, co obiekt potrafi zrobić. Ta koncepcja nazywa się typowaniem dynamicznym: „Jeśli chodzi jak kaczka i kwacze jak kaczka, to jest kaczką”. W terminologii programowania, jeśli obiekt ma metodę, którą wywołujesz, wszystko działa.
Oznacza to, że można używać razem zupełnie niezwiązanych ze sobą klas, o ile mają metodę o tej samej nazwie:
local Circle = {}
Circle.__index = Circle
function Circle:new()
local obj = {}
setmetatable(obj, Circle)
return obj
end
function Circle:draw()
print("Drawing a circle")
end
local Square = {}
Square.__index = Square
function Square:new()
local obj = {}
setmetatable(obj, Square)
return obj
end
function Square:draw()
print("Drawing a square")
endTe klasy nie są ze sobą powiązane dziedziczeniem: są całkowicie niezależne. Mimo to możemy traktować je identycznie:
local shapes = {Circle:new(), Square:new(), Circle:new()}
for _, shape in ipairs(shapes) do
shape:draw()
end
-- Wynik:
-- Rysowanie koła
-- Rysowanie kwadratu
-- Rysowanie kołaPętla nie sprawdza, jakiego typu jest każdy obiekt. Po prostu wywołuje :draw() i zakłada, że ta metoda istnieje. Ta elastyczność jest bardzo przydatna. Możesz dodawać nowe typy kształtów bez modyfikowania kodu pętli. Nowa klasa musi tylko implementować :draw(), a wszystko będzie działać.
Wyzwanie
ŁatwyDemonstrujmy moc duck typingu, tworząc system powiadomień z całkowicie niezależnych klas, które można przetwarzać w ten sam sposób!
Utworzysz trzy niezależne klasy powiadomień, z których żadna nie dziedziczy po pozostałych, ale wszystkie implementują metodę :send(). Oznacza to, że możesz iterować po mieszanej kolekcji i wywoływać :send() dla każdego elementu, nie przejmując się jego rzeczywistym typem.
Rozmieścisz swój kod w czterech plikach:
Email.lua: Klasa z konstruktorem:new(recipient), który przechowuje adresata wiadomości e-mail. Jej metoda:send()powinna wypisywaćEmailing {recipient}.SMS.lua: Całkowicie oddzielna klasa z konstruktorem:new(phoneNumber). Jej metoda:send()powinna wypisywaćTexting {phoneNumber}.PushNotification.lua: Kolejna niezależna klasa z konstruktorem:new(deviceId). Jej metoda:send()powinna wypisywaćPushing to {deviceId}.main.lua: Połącz wszystko w całość! Odczytaj trzy dane wejściowe, utwórz jedną instancję każdego typu powiadomienia, umieść je wszystkie w jednej tabeli, a następnie iteruj po niej i wywołuj:send()dla każdego elementu.
Otrzymasz trzy dane wejściowe:
- Adresata wiadomości e-mail (np.
alice@example.com) - Numer telefonu (np.
555-1234) - Identyfikator urządzenia (np.
device_99)
W pliku głównym utwórz wszystkie trzy obiekty powiadomień, przechowaj je w tabeli i iteruj po niej, wywołując :send() dla każdego elementu. Kolejność powinna być następująca: najpierw Email, potem SMS, a na końcu PushNotification.
Na przykład jeśli danymi wejściowymi są bob@mail.com, 555-9876 i phone_42, wynikiem powinno być:
Emailing bob@mail.com
Texting 555-9876
Pushing to phone_42Zauważ, że pętla nie musi sprawdzać, jakiego typu jest każdy obiekt. Po prostu zakłada, że każdy obiekt w kolekcji ma metodę :send(). Tak właśnie działa duck typing! Jeśli obiekt może wywołać :send(), jest powiadomieniem.
Spróbuj swoich sił
-- main.lua
-- Połącz wszystkie typy powiadomień i zademonstruj typowanie kacze
-- Zaimportuj klasy powiadomień
local Email = require('Email')
local SMS = require('SMS')
local PushNotification = require('PushNotification')
-- Odczytaj dane wejściowe
local recipient = io.read()
local phoneNumber = io.read()
local deviceId = io.read()
-- TODO: Utwórz po jednej instancji każdego typu powiadomienia
-- TODO: Umieść wszystkie powiadomienia w jednej tabeli (najpierw Email, potem SMS, a następnie PushNotification)
-- TODO: Przejdź przez tabelę i wywołaj :send() dla każdego powiadomienia
-- To demonstruje typowanie kacze — nie sprawdzamy typu, tylko wywołujemy :send()
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