Grupa MagazynyInternetowe
Online: 906
Pisząc programy, często trzeba zbudować klasy, których obiekty mogą znajdować się w jednym z kilku stanów. Zadanie nie wydaje się trudne, często jednak modyfikacja kodu napisanego w typowy sposób staje się uciążliwa, powodując konieczność modyfikacji wielu metod klasy.
Marcin Staniszczak
Wiele z nich zaczyna z czasem przybierać postać rosnącej drabinki warunków, co utrudnia analizę kodu. Na szczęście istnieje rozwiązanie tego problemu.
Zajmijmy się prostym programem: autoryzacją użytkowników. Użytkownicy podają login i hasło, po czym kod ma za zadanie sprawdzić, czy podane dane są poprawne i w zależności od tego wykonać odpowiednią akcję. Dodatkowo, dla utrudnienia, po trzykrotnym błędnym logowaniu zablokujemy możliwość ponownego logowania na 30 sekund, co powinno utrudnić ewentualną próbę włamania.
Najprostszym rozwiązaniem problemu jest jedna klasa z szeregiem metod:
1. class Autoryzacja { 2. const SPRAWDZANIE = 1; 3. const BLAD_AUTORYZACJI = 2; 4. const BLAD_AUTORYZACJI_3_RAZY = 3; 5. const AUTORYZACJA_POPRAWNA = 4; 6. public function sprawdz($strLogin,$strHaslo) { 7. //... 8. } 9. public function autoryzacjaPoprawna() { 10. //... 11. } 12. public function blokada() { 13. //... 14. } 15. //... 16. }
1. public class Autoryzacja { 2. final static int SPRAWDZANIE = 1; 3. final static int BLAD_AUTORYZACJI = 2; 4. final static int BLAD_AUTORYZACJI_3_RAZY = 3; 5. final static int AUTORYZACJA_POPRAWNA = 4; 6. public void sprawdz(String login, String haslo) { 7. //.. 8. } 9. public void autoryzacjaPoprawna() { 10. //.. 11. } 12. public void blokada() { 13. //.. 14. } 15. //... 16. }
Kod więc jest dość prosty, może przyjmować stan określony przez jedną ze stałych. Niestety będziemy tutaj mieli do czynienia z kilkoma metodami, z których każda będzie miała szereg warunków i wymagała zmian w przypadku dodawania nowego sposobu przeprowadzania autoryzacji.
Ewentualnie całą logikę można zaszyć w metodzie sprawdź, co spowoduje stworzenie potężnego drzewka warunków, jeszcze trudniejszego w pielęgnacji.
Istnieje jeszcze jeden sposób rozwiązania problemu - skorzystanie ze wzorca projektowego stan (ang. state).
Używając wzorca stan, wyodrębniamy do osobnej klasy każdy ze stanów, w jakim może znaleźć się obiekt - tak więc stany nie będą więcej reprezentowane przez proste stałe. Każdy z obiektów stanu może mieć możliwość wpływania na stan obiektu Autoryzacji, a więc poszczególne stany powinny wiedzieć, na jaki inny stan zmodyfikować stan obiektu w zależności od zaistniałych okoliczności. Brzmi to skomplikowanie, ale w rzeczywistości jest bardzo proste. Przyjrzyj się kodowi programu.
Na samym początku należy wyodrębnić wspólne cechy wszystkich stanów, które będą interfejsem stanu. W naszym prosty przykładzie interfejs jest bardzo prosty i maa jedną metodę sprawdź:
1. interface Stan { 2. public function__construct(Autoryzacja $objAutoryzacja); 3. public function sprawdz($strLogin,$strHaslo); 4. }
1. public interface Stan { 2. public void sprawdz(String login,String haslo); 3. }
Sama klasa Autoryzacja powinna dostarczać metod pozwalających na odczyt oraz zmianę aktualnego stanu i metody (tzw. gettery), udostępniających obiekty wszystkich stanów, w jakich może się znaleźć dana klasa:
Listing zwinięty - 35 linii
Listing zwinięty - 35 linii
Głównym stanem klasy Autoryzacja jest SparwdzanieStan - sprawdza on login oraz hasło i w zależności od wyniku sprawdzania uruchamia stan:
Stany BladAutoryzacjiStan oraz Autoryzacja- PoprawnaStan są bardzo proste i nie wymagają szczegółowych wyjaśnień:
1. class BladAutoryzacjiStan implements Stan 2. { 3. private $objAutoryzacja = null; 4. public function__construct(Autoryzacja $objAutoryzacja) { 5. $this->objAutoryzacja = $objAutoryzacja; 6. } 7. public function sprawdz($strLogin,$strHaslo) { 8. echo "Niepoprawny login lub hasło!<br>"; 9. $this->objAutoryzacja->setStan($this->objAutoryzacja->getStanSprawdzania()); 10. } 11. } 12. class AutoryzacjaPoprawnaStan implements Stan { 13. public function__construct(Autoryzacja $objAutoryzacja) { 14. } 15. public function sprawdz($strLogin,$strHaslo) { 16. echo "Zalogowany - witam!<br>"; 17. } 18. }
1. public class BladAutoryzacjiStan 2. implements Stan { 3. private Autoryzacja autoryzacja; 4. public BladAutoryzacjiStan(Autoryzacja autoryzacja) { 5. this.autoryzacja = autoryzacja; 6. } 7. public void sprawdz(String login, String haslo) { 8. System.out.println("Niepoprawny login lub hasło"); 9. autoryzacja.setStan(autoryzacja.getStanSprawdzania()); 10. } 11. } 12. public class AutoryzacjaPoprawnaStan 13. implements Stan { 14. public AutoryzacjaPoprawnaStan(Autoryzacja autoryzacja) { 15. } 16. public void sprawdz(String login,String haslo) { 17. System.out.println("Zalogowany - witam!"); 18. } 19. }
Bardziej złożony jest stan BladAutoryzacji- 3RazyStan. Jego zadaniem jest zablokowanie możliwości logowania na 30 sekund. Gdy stan ten jest ustawiony, przy pierwszym wywołaniu metody sprawdź (wywołanie to następuje z poziomu stanu SprawdzanieStan) zainicjowana zostaje zmienna przechowująca aktualny czas.
Następnie stan ten staje się domyślny na 30 sekund, nie pozwalając tym samym na zalogowanie się. Po upłynięciu 30 sekund stan BladAutoryzacji3RazyStan sam ustala stan klasy Autoryzacja ponownie na stan Sparwdzanie-Stan, co pozwala na dokonanie kolejnych prób logowania (po 3 nieudanych następuje ponowna blokada na 30 sekund):
1. class BladAutoryzacji3RazyStan implements 2. Stan { 3. private $objAutoryzacja = null; 4. private $intCzas = null; 5. public function__construct(Autoryzacja $objAutoryzacja) { 6. $this->objAutoryzacja = $objAutoryzacja; 7. } 8. public function sprawdz($strLogin,$strHaslo) { 9. if ($this->intCzas === null) { 10. $this->intCzas = time(); 11. } 12. if ((time() - $this->intCzas) >=5) { 13. $this->objAutoryzacja->setStan($this->objAutoryzacja->getStanSprawdzania()); 14. $this->objAutoryzacja->getStan()->sprawdz($strLogin, $strHaslo); 15. $this->intCzas = null; 16. return; 17. } 18. echo "Podano trzy razy nieprawidłowy login lub hasło - możliwość logowania zablokowana na 30 sekund!<br>"; 19. } 20. }
1. import java.util.*; 2. public class BladAutoryzacji3RazyStan implements Stan { 3. private Autoryzacja autoryzacja; 4. private Long czas = -1l; 5. public BladAutoryzacji3RazyStan(Autoryzacja autoryzacja) { 6. this.autoryzacja = autoryzacja; 7. } 8. public void sprawdz(String login,String haslo) { 9. GregorianCalendar gc = new GregorianCalendar(); 10. if (czas == -1l) { 11. czas = gc.getTime().getTime(); 12. } 13. if ((gc.getTime().getTime() - czas) >= 30000) { 14. autoryzacja.setStan(autoryzacja.getStanSprawdzania()); 15. autoryzacja.getStan().sprawdz(login, haslo); 16. czas = -1l; 17. return; 18. } 19. System.out.println("Podano trzy razy nieprawidłowy login lub hasło - możliwość logowania zablokowana na 30 sekund!"); 20. } 21. }
W taki oto sposób wyeliminowaliśmy niepraktyczne drzewka warunków, wyodrębniliśmy zachowanie charakterystyczne dla każdego stanu oraz ułatwiliśmy modyfikowanie kodu. W tej chwili dodanie kolejnego stanu wiązało się będzie zazwyczaj z:
Pamiętaj, że przedstawiony tu program został napisany tak, aby był możliwie jak najbardziej prosty do zrozumienia. Hasło i login są w nim zakodowana na stałe, podczas gdy w rzeczywistości powinny być pobierane np. z bazy danych.
Znasz już kilka prostych, lecz podstawowych wzorców projektowych. Dzięki temu możesz pisać lepsze, łatwiejsze w rozwijaniu programy. Zachęcam jednocześnie do lektury kolejnych artykułów z tej serii - przed nami jeszcze długa droga.
Powiązane publikacje
Brak komentarzy
Artykuły tego autora:
Fasada jest wzorcem zaliczającym się do wzorców strukturalnych. Jest to jeden z popularniejszych, a jednocześnie najprostszych wzorców projektowych.
Polecamy:
Na skróty:
Magazyny Internetowe| Co za ile| Programy| Praca| Magazyn Internet| Internet Maker| Web Toster| ForumNasze serwisy: