poniedziałek, 15 maja 2017

Początki programowania obiektowego

Dawno się do was nie odzywałem... Cóż, "taki life" :-) Zwłaszcza że ten rozdział będzie trudny i nie mogłem się zebrać do jego napisania...
Ale wiosna przyszła i pora wrócić do nauki.

Wchodzimy dziś w zupełnie nowy obszar, albo jak mówią informatycy "zmieniamy paradygmat". Poprzednio trzymaliśmy się programowania strukturalnego programowania proceduralnego, choć tak naprawdę było w tym drobne oszustwo wynikające z głębokiej semantyki języka Processingu.

Teraz zaczniemy jawnie korzystać z możliwości programowania obiektowego.

Co to oznacza? Otóż zaczniemy rozumieć, że używamy predefiniowane obiekty,  a także nauczymy się je sami definiować. 

Czym są obiekty? Pewnie jeszcze nie skorzystaliście z linków do Wikipedii to wam ją przytoczę :-)


  • "Obiekt to podstawowe pojęcie wchodzące w skład paradygmatu programowania obiektowego w analizie i projektowaniu oprogramowania oraz w programowaniu. W tym paradygmacie programy definiuje się za pomocą obiektów – elementów łączących stan (czyli dane, nazywane najczęściej polami) i zachowanie (czyli procedury, tu: metody). Obiektowy program komputerowy wyrażony jest jako zbiór takich obiektów, komunikujących się pomiędzy sobą w celu wykonywania zadań."

Nie będę już dalej cytował teorii - ale zanim przejdziemy dalej zapoznajcie się z nią używając Wiki lub "wujka Google".
Przejdźmy do naszego nowego przykładu. Od razu mamy w nim obiekty
 

Mamy w nim od razu deklaracje dwóch obiektów. World jest tablicą, a tablice w Processingu są obiektami. Podobnie output ,  który jest narzędziem do zapisywania pliku tekstowego na dysku.
Mamy też tajemnicze RGB - "klasę" czyli "typ",  o którym napisano że dopiero zostanie zdefiniowany.  Tablica i PrintWriter są obiektami predefiniowanymi, które dostajemy w ramach języka Processing. RGB to będzie właśnie nasz własny typ obiektowy służący do przechowywania koloru rozbitego na poszczególne składowe. Zdefiniujemy go za chwilę.

Po czym to poznajemy że jakiś typ jest obiektem? Najłatwiej po sposobie użycia. Żebyśmy mogli używać tablicy World musimy najpierw użyć operatora new. Czyli w przeciwieństwie do int czy float samo zadeklarowanie nie wystarcza.
Podobnie będzie z typem elementu tablicy World. W przeciwieństwie do poprzednich naszych programów, gdzie elementy tablicy były albo typu int albo float, RGB nie jest typem prostym, ale klasą, czyli typem obiektowym. A skoro go sami wymyśliliśmy, to musimy zdefiniować (czyli powiedzieć dokładnie) czym jest:


Składnia takiej deklaracji polega na zamknięciu w strukturę class Name {   }
deklaracji danych i deklaracji funkcji/procedur. Te dane opisują stan obiektu, procedury opisują możliwe sposoby zmiany tego stanu. Dane nazywane są też atrybutami, a funkcje i procedury  metodami.
W naszym przykładzie mamy trzy atrybuty każdego obiektu RGB, trzy liczby typu int nazwane R, G i B. Mogłyby się nazywać też Red, Green, Blue, albo jeszcze inaczej (np. SkladowaCzerwona, SkladowaZielona, SkladowaNiebieska), ale po co komplikować sobie zapis?
Poza atrybutami mamy też kilka metod: Visualise(), isEmpty(), Set() oraz RGB().
Procedura Visualise() ma za zadanie narysowanie graficznej reprezentacji koloru w postaci punktu lub kwadratu w miejscu ustalonym przez współrzędne XY. Współrzędne odnoszą się jednak do położenia w tablicy World , a nie do okna, bo okno może być większe od tablicy dzięki "mnożnikowi" czyli współczynnikowi skali W.
Wyświetlanie odbywa się jednak tylko wtedy gdy obiekt RGB nie jest pusty, co jest sprawdzane specjalną funkcją zwracającą wartość logiczną (boolean).
Funkcja isEmpty() sprawdza czy obiektowi RGB nadano jakiś kolor inny niż czarny - czyli czy któraś ze składowych różni się od 0
Do zmiany wszystkich składowych "za jednym zamachem" służy procedura Set().
A do czego służy procedura czy też funkcja o nazwie RGB czyli takiej samej jak cały typ?
To jest tzw. konstruktor. Jest używany przez operator new do zainicjowania obiektu. Powinien zatem nadawać mu jakiś ściśle zdefiniowany stan. Nasz używa instrukcji R=G=B=0; żeby wszystkim składowym nadać stan 0. Konstruktor inkrementuje też zmienną RGB_Counter, dzięki czemu wiemy ile stworzyliśmy już obiektów typu RGB.
Jeszcze jedno. Definiując konstruktor nie podajemy typu jaki zwraca. Taka jest konwencja we wszystkich językach obiektowych jakie znam.  

Skoro typ RGB już mamy zdefiniowany, pora na jego użycie. 





Że RGB jest obiektem, widać już w procedurze setup. Żeby zacząć używać komórki tablicy World nusimy w niej najpierw utworzyć obiekt typu RGB za pomocą operatora new (linia 51).

Potem za pomocą metody Set() nadajemy mu jakiś kolor (linia 54, albo alternatywne 52 i 53 jeśli zdecydujemy się inaczej zakomentować), a za pomocą metody Visualise() rysujemy w środku okna (linia 55), jako że jest to akurat środkowy element tablicy.
Zwróćcie uwagę jak dostajemy się do tych metod.
Indeksowanie tablicy pozwala nam wybrać element tablicy. Na razie bezpiecznie możemy wybrać tylko obiekt w położeniu [Side/2][Side/2] bo tylko on został utworzony. Ale co ma zrobić obiekt? Do tego wyboru służy "kropka" (czyli operator wyboru składowej).
Po kropce możemy w Processingu napisać nazwę dowolnej metody, a także dowolnego atrybutu obiektu (w innych językach obiektowych sprawa jest nieco bardziej skomplikowana).


Nieco podobnie ma się sprawa z typem PrintWriter, który też występuje powyżej. Jest co prawda tworzony nie za pomocą operatora new ale specjalnej funkcji bibliotecznej createWriter() , ale operator new musi zapewne znajdować się wewnątrz tej funkcji.
Natomiast użycie odbywa się podobnie do pisania na konsolę tekstową Processingu, jednak właśnie z użyciem "operatora kropki". Pisząc output.println() wybieramy spośród różnych metod możliwych dla obiektu output typy PrintWriter operację println().


No to teraz pora na deser. Użyjemy sobie naszego typu do jakiejś ładnej symulacji definiując procedurę draw():




Procedura draw() wrzuca najpierw do pliku (linia 70) numer kroku i liczbę istniejących obiektów RGB. Pozwoli nam to wykonać w arkuszu kalkulacyjnym albo naszym własnymgramie wykres wzrostu. Właściwe działanie procedury odbywa się zaś w bloku dla warunku if(!Stop) - czyli dopóki nie nadamy zmiennej Stop wartości true.


W obrębie tego bloku powinniście rozpoznać znany już początek pętli kroku Monte-Carlo (Linie 75-79). Reszta działań odbywa się tylko wtedy gdy wylosowany element tablicy zawiera obiekt typu RGB co sprawdzamy za pomocą warunku World[Y][X] != null . Dla takiej sytuacji losujemy współrzędne Xt i Yt jakiejś sąsiedniej komórki w obrębie sąsiedztwa Moora (linie 82-83) i jeśli jest ona realnie w tablicy i jest pusta (warunek w liniach 84-85) dokonujemy jej zasiedlenia przez nowy obiekt, który będzie "potomkiem" obiektu znajdującego się w komórce Y X.

Potomek nie jest identyczną kopią, ponieważ z dużym prawdopodobieństwem może zajść mutacja jego składowych koloru (w liniach 89-94) dokonana metodą błądzenia losowego i dopiero takie zmutowane składowe są przypisywane na potomka (linia 96).
Na koniec sprawdzamy czy nie zasiedliliśmy właśnie jednego z czterech rogów macierzy, czyli punktów najbardziej odległych od środka (w programie sprawdzamy tylko jeden róg, ale nic nie stoi na przeszkodzie żebyście ten warunek uzupełnili). Jeśli doszliśmy do rogu to dalsze działanie modelu uznajemy za niecelowe i ustawiamy zmienną Stop na true.

A teraz małe wyjaśnienie w ramach post scriptum. Napisałem na początku że z paradygmat u obiektowego korzystaliśmy właściwie cały czas, choć o tym nie wiedzieliśmy. To fakt. Processing jest "nakładką" na język Java, a ten jest w 100% obiektowy. Cała aplikacja w Processingu jest więc obiektem języka Java, którego jedną, dwie lub trzy metody redefiniujemy (czy też  nadpisujemy). Te metody to draw(), setup() i ewentualnie exit(). Rozwiniemy tą kwestię przy okazji "dziedziczenia".