Może ktoś polecić jakieś materiały które pomogą zrozumieć to w jakich sytuacjach używać obiektowości?
Dobrze to widać na przykładzie walidatora do pól formularzy. Wyobraź sobie stronę internetową, która ma kilka formularzy, np. możesz podać imię, nazwisko, numer telefonu komórkowego, adres e-mail oraz krótką wiadomość mającą nie więcej niż 500 znaków. Do każdej takiej "danej" masz zwykły formularz napisany w HTML-u. Po wypełnieniu formularzy użytkownik przesyła te dane na serwer, które następnie będą zapisane w bazie danych.
Przed zapisaniem takich danych dobrze jest je sprawdzić. Może się przecież zdarzyć, że jakiś troll (albo bot) wpisze wszędzie same iksy. Jako programiści mamy więc dwie drogi. Pierwszą jest napisanie kodu strukturalnego, drugą kodu obiektowego.
Kod strukturalny
Taki kod jest łatwy do stworzenia. Po odebraniu danych z dowolnego formularza możemy sprawdzić jego zawartość, wiedząc jaki typ danych powinien zostać obsłużony. Na przykład jeśli to był formularz adresu e-mail, to będziemy poszukiwać znaku @ w przesłanych danych. Oprócz tego adres e-mail nie może być pusty. Użytkownik zawsze musi go podać.
Chcemy być jednak spryciule i zależy nam, by pisać kod obiektowy. W takim razie wpadamy na genialny pomysł, by każdy formularz był obiektem, który ma ustawienia wpisane na sztywno. Tworzymy więc klasę:
class NotEmptyAddressEmail
{ ... }
Dla numeru telefonu tworzymy klasę:
class NotEmptyPhoneNumber
{ ... }
która sprawdza czy wypełniono to pole oraz ilość cyfr w numerze. Wpadamy na żenialny pomysł, że dla imienia i nazwiska wystarczy nam przecież ta sama klasa!
class NotEmptyTextField
{ ... }
I jeszcze żenialniejsze jest to, że możemy ją wykorzystać do formularza z komentarzem. Ale zaraz, zaraz, nie... W końcu komentarz może być opcjonalny, to pole nie musi być przecież wypełnione. Tworzymy więc nową klasę:
class TextField
{ ... }
W sumie to spoko. Ale, hm... Komentarz może mieć nie więcej jak 500 znaków. Czyli klasy TextField już nie wykorzystam, jeśli zrobię stronkę wysyłającą SMS czy krótkie twitty (do 140 znaków). To chyba nie jest jednak kod obiektowy, pomimo że korzystam z klas.
Kod obiektowy
Patrząc na powyższe rozważania dochodzimy do wniosku, że problemem są klasy. Za każdym razem, w zależności od formularza, musimy tworzyć w zasadzie unikalną klasę, która sprawdzi nam dane przychodzące od takiego formularza. Może lepszym pomysłem byłoby stworzenie jednego obiektu, który sprawdzałby wszystkie formularze, czyli wystarczy nam jedna klasa. No ale przecież formularze są różne, jak więc to pogodzić? To może "włóżmy" do tego obiektu każdy z formularzy. Przecież istnieją tablice. W sumie nic nam to nie da, dopóki nie powiążemy w jakiś sposób samych formularzy z tym, co mają sprawdzać.
Hm, gdyby zaprojektować to tak:
validator['imie'] = (niepuste, odpowiednia dlugosc, brak cyfr)
validator['nazwisko'] = (niepuste, odpowiednia dlugosc, brak cyfr)
validator['telefon'] = (niepuste, odpowiednia dlugosc, same cyfry)
validator['email'] = (niepuste, odpowiednie znaki)
validator['komentarz'] = (odpowiednia dlugosc)
Wszystkie te ograniczenia podane w nawiasie mogłoby być obsługiwane przez obiekty, czyli musiałbym stworzyć klasy odpowiednich walidatorów. Hm, no ale przecież nie mogę ustawić na sztywno długości w klasie odpowiedzialnej za odpowiednią długość, bo wtedy znów stworzę niepotrzebnie wiele klas, które obsługują różne długości. Ale zaraz, zaraz, przecież mogę przekazać je w konstruktorze. Wtedy mój kod wyglądałby tak:
validator['imie'] = (niepuste, odpowiednia dlugosc[3-30], brak cyfr)
validator['nazwisko'] = (niepuste, odpowiednia dlugosc[3-30], brak cyfr)
validator['telefon'] = (niepuste, odpowiednia dlugosc[9-9], same cyfry)
validator['email'] = (niepuste, odpowiednie znaki[znaki = tablica[@.]])
validator['komentarz'] = (odpowiednia dlugosc[5-500])
Jako odpowiednie znaki sprawdzam, czy adres email posiada @ oraz znak kropki. W takim razie muszę napisać silnik, który to obsłuży tylko 5 klas:
- niepuste
- odpowiednia dlugosc
- brak cyfr
- same cyfry
- odpowiednie znaki
W sumie to wygląda całkiem sensownie. A jak w przyszłości będę chciał dodać formularz z kodem pocztowym, to będę musiał stworzyć tylko nową klasę, która sprawdzi mi czy dane mają postać XX-XXX. No tak, ale jak ktoś inny dostanie mój kod, to skąd będzie wiedział jaką ta klasa ma mieć strukturę, w końcu musi być kompatybilna z silnikiem walidatora? Stworzę więc interfejs, który muszą implementować wszystkie tego typu klasy współpracujące z silnikiem. Jeśli ten interfejs nie będzie implementowany w klasie, to silnik zrzuci wyjątek.
Tak naprawdę to opisałem coś, czego nie wyjaśniłem od początku, publikując własny silnik walidatora. Nie jest on doskonały, zwróciłem uwagę że ma pewne wady, które poprawiam. Na przykład silnik przerywa działanie, jeśli już jedno pole nie przejdzie walidacji, a powinien sprawdzić wszystkie formularze i zwrócić wszystkie błędy na raz. Jest to jednak implementacja tego, co opisałem wyżej. Spójrz na to, jaki kod jest stosunkowo krótki.