Obraz wygenerowany (Bing AI) - Tak uroczo głupi, że nie mogłem się powstrzymać.
Wreszcie udało nam się dotrzeć do końca serii wpisów na temat popularnych wzorców projektowych. Na ostatek zostawiłem coś ekstra – nie tyle wzorzec co prawdziwy schemat architektury oprogramowania: Model-View-Controller.
Z MVC przyszło mi korzystać już kilka razy, najpierw na kursie CodeGym, gdzie po prostu wykonywałem polecenia i nie za bardzo wiedziałem co, jak i dlaczego. Następnie przy pracy nad moim pierwszym samodzielnym projektem – tam już starałem się świadomie postępować zgodnie z regułami schematu, chociaż jeszcze nie do końca rozumiałem jak on działa.
Pora to zmienić.
Ogólny koncept MVC to podzielenie projektu na 3 części: Model, Widok i Kontroler. Pierwsza odpowiada za logikę, druga za wyświetlanie treści, a trzecia za przechwytywanie poleceń użytkownika.
Piękno tego rozwiązania polega na tym, że te elementy są bardzo luźno ze sobą połączone. Jeden Model można zastąpić innym, byleby miał to samo API. Może on korzystać z kilku różnych Widoków i mieć więcej niż jeden Kontroler. To sprawia, że nad każdym z tych modułów może pracować osobny zespół.

Powyżej opisałem wam klasyczny schemat MVC, który wciąż jest w użyciu, ale raczej wśród amatorskiego oprogramowania. Jego następca: „Model 2” został przystosowany do tworzenia aplikacji webowych, gdzie za widok odpowiada dział frontend operujący na plikach HTML. Kontroler stał się servletem, a Model podzielił się na część implementującą logikę oraz na bazy danych.
Ale i Model 2 został dość niedawno ulepszony za sprawą frameworku Spring MVC. Póki co wiem o nim zbyt mało, aby napisać cokolwiek ponad to, że uprasza i standaryzuje użycie MVC.

Podręcznik Rusz głową! nie oferuje definicji MVC, aczkolwiek z lektury rozdziału mu poświęconego można wyciągnąć kilka wniosków:
- Zaczynamy od stworzenia klasy Model. To z niego Widok będzie brał dane do wyświetlenia.
- Następnie tworzymy klasę Controller z modelem wstrzykniętym w konstruktor. Tenże konstruktor zajmuje się również tworzeniem klasy Widoku w oparciu o siebie i model.
- MVC bazuje na wzorcu Obserwator – Model dziedziczy klasę Observerable, View i Controller implementują interfejs Observer.
- Model „nic nie wie” o pozostałych klasach, dlatego nie możemy się odnosić do zmiennych i metod w Widoku czy Kontrolerze.
- Każda klasa ma inne zadanie i tego należy się trzymać, np. nie umieszczajmy logiki w klasach należących do View.
Znając reguły pozostaje już tylko zobaczyć jak to się sprawdza w akcji.

Ale po kolei…
Czego potrzeba aby stworzyć ekscytujący model walki w grze?
Pomijając oprawę graficzną i różnorodność skilli dochodzimy do prostego szablonu przypominającego zasadami grę Kamień/Papier/Nożyce. Koniec końców musimy odgadnąć intencje przeciwnika i przygotować się na jego ruch. Kiedy się odsłoni – szybko atakujemy, kiedy on atakuje – my się bronimy, kiedy szykuje finala przed którym nie uchroni nas tarcza – staramy się uciec poza zasięg rażenia.

To wszystko sprowadza się do jednego wspólnego mianownika:
Losowość
Jeśli wiemy, że z każdym ciosem zadajemy przeciwnikowi tyle a tyle obrażeń, a on nam tyle, że co trzeci atak jest zasłaniany tarczą, a co piąty zadaje podwójny „demydż”… Gra szybko przestaje bawić, a staje się pracą domową z matematyki.
Kiedy jednak wprowadzimy randomizację tych ruchów: nie każdy atak dosięgnie celu, nie każdy blok będzie udany, to już mamy namiastkę fajnej zabawy. A jak dodamy tam jeszcze zarządzanie zasobami takimi jak punkty życia, mana czy stamina to choćby warstwa wizualna odstraszała, program i tak znajdzie swoich zapaleńców próbujących różnych taktyk.
Starając się pokazać wzorzec MVC w działaniu stworzyłem prostą minigierkę, gdzie próbujemy przewidzieć ruchy przeciwnika i przyjąć postawę obronną kiedy ten atakuje, lub samemu napierać, gdy wróg nic nie robi. Na program składają się cztery klasy: Model, View, Controller oraz Main, której zadaniem jest tylko odpalenie gry.
Spójrzmy najpierw co się dzieje w konsoli:

Zasady są proste – patrzymy na szansę czy przeciwnik zaatakuje, jeśli jest spora to wpisujemy „block„. Gdy faktycznie wróg miał zadać cios to zostanie on automatycznie zablokowany. Ale jeśli nic nie chciał robić, a my zdecydowaliśmy się blokować, to wtedy i tak zaatakuje – tego ciosu nie zablokujemy, ale zada nam tylko połowę normalnych obrażeń.
Gdy natomiast wpiszemy „hit” i wróg atakuje, to my odniesiemy obrażenia, a on nie (przyjmijmy, że ma dłuższy oręż: wali z dzidy 😉 ). W przypadku kiedy atakujemy, a wróg nic nie planuje – tylko on odnosi obrażenia.
Możemy również wpisać jakiekolwiek inne słowo – zostanie to potraktowane jako komenda „wait” czyli nikt nikogo nie atakuje, ale szansa na atak przeciwnika zostaje przetasowana.
A jak ma się to do naszego MVC?

Działanie programu rozpoczyna się w klasie kontrolera. Jego konstruktor tworzy View (który od razu wyświetla: „Wpisz 'hit’ jeśli chcesz zaatakować lub 'block’ jeśli chcesz przygotować obronę.”), a następnie ręcznie (z poziomu metody main()) inicjalizujemy inputCommand() która czeka na wpisanie frazy przez gracza. Po wciśnięciu Enter pałeczka przechodzi do modelu:
model.processCommand(reader.readLine());
Następnie w klasie Model dzieje się cała magia związana z przetwarzaniem polecenia:

Metoda processCommand() nie tylko wylicza wynik starcia, ale też powiadamia o tym obserwatorów: klasy Controller i View. W naszym przykładzie kontroler nie potrzebuje tej informacji, ale to zależy od implementacji. Za to Widok od razu bierze się za wyświetlanie nowych elementów, które pobiera z getterów Modelu:

Odpalenie całości to tylko kilka komend w metodzie main – utworzenie Modelu, następnie Kontrolera i dodanie obserwatorów:
Model model = new Model();
Controller controller = new Controller(model);
View view = controller.getView();
model.addObserver(controller);
model.addObserver(view);
controller.inputCommand();
I gotowe! Wszystkie zasady poprawnego użycia wzorca zostały zachowane: każda klasa uprawia tylko swoje poletko, każda może być zastąpiona inną. Równie dobrze mogę stworzyć nowy kontroler obsługujący kliknięcia myszką i nowy widok w Swingu, gdzie zamiast wpisywać tekst będę klikał na przycisk „Hit” albo „Block”. Mogę dodać nowy model, który inaczej wyliczy szansę na blok i atak – byleby tylko gettery się zgadzały.
Podsumowanie
W tym miejscu kończymy zabawę ze wzorcami projektowymi. Właśnie doczytuję ostatnie strony podręcznika i już myślę nad pierwszymi etapami pracy nad moim drugim projektem (spokojnie, o tym też napiszę). Teraz jednak, pozostaje jeszcze odpowiedzieć na pytanie:
Czy warto się było uczyć tego wszystkiego?
Nie wiem. Jeszcze. Osobiście czuję się znacznie lepszym programistą – bardziej świadomym tego co robię i dlaczego akurat w ten sposób. Ale czy ta wiedza mi się przyda? Pierwsze odpowiedzi przyjdą w trakcie prac nad ITCandidateEvaluator’em – już wkrótce. Pomocnym może być tu również cytat z filmu „Alien vs Predator” (2004) do którego mam mały sentyment. Na pytanie jednej załogantki „Po co ci pistolet na wyprawie badawczej?” jej kolega odrzekł:
„Lepiej mieć i nie potrzebować niż na odwrót.”

Na odchodnym chciałbym wszystkim polecić książki z serii Rusz głową! Jak do tej pory przeczytałem dwie, ale już ciągnie mnie do kolejnej („Rusz głową! SQL”) bo to nie tyle garść wartościowych informacji, co również ciekawa lektura, którą chce się przerobić do samego końca.