Wprowadzenie
Methodmap'y są jedną z nowości wprowadzonych w sm 1.7, służą one do mapowania funkcji (jak sama nazwa wskazuje). Pozwalają one nam na abstrakcyjne myślenie, zobaczmy poniższy przykład.
Ten kod odpowiada za stworzenie menu wyboru z opcjami Tak i Nie:
Handle menu = CreateMenu(MenuHandler1); SetMenuTitle(menu, "Czy przeczytałeś to na AMXX.PL?"); AddMenuItem(menu, "yes", "Yes"); AddMenuItem(menu, "nie", "Nie"); SetMenuExitButton(menu, false); DisplayMenu(menu, client, 20);
Ten sam kod z użyciem methodmap możemy zapisać tak:
Menu menu = new Menu(MenuHandler1); menu.SetTitle("Czy przeczytałeś to na AMXX.PL?"); menu.AddItem("tak", "Tak"); menu.AddItem("nie", "Nie"); menu.ExitButton = false; menu.Display(client, 20);
Od razu widać że kod jest czytelniejszy
Budowa Methodmap
Definiowanie methodmap jest podobne do definiowania enum'a:
methodmap Nazwa { //... };
Tak samo się tworzy zmienną typu methodmap:
Nazwa zmienna;
Funkcje methodmap
Jeśli się przyjrzymy zobaczymy że definicja funkcji z methodmap prawie w ogóle nie różni się od zwykłej funkcji (po za tym gdzie się znajdują).
methodmap Nazwa { /... public void funkcja() { PrintToServer("Wywołano funkcje z methodmap Nazwa"); } /... };
Różnica natomiast od razu jest odczuwalna gdy chcemy wywołać funkcje, gdyż wpierw musimy stworzyć "obiekt"
Nazwa zmienna; zmienna.funkcja();
Setter i Getter
Nie ma możliwości aby w methodmap utworzyć zmienną, możemy natomiast utworzyć pseudo zmienną. Czym to się różni od zwykłej zmiennej? Tym że zamiast bezpośrednich operacjach na zmiennej są wywoływane 2 funkcjie:
- setter - kiedy wartość do pseudo zmiennej jest zapisywana
- getter - kiedy wartość z pseudo zmiennej jest odczytywana
Przykład:
int zmiennaPozaMethodmap; methodmap Nazwa { /... property int zmienna { public get() { return zmiennaPozaMethodmap; } public set(int wartosc) { zmiennaPozaMethodmap = wartosc; } } /... };
A teraz opis przykłądu:
property int zmienna
- property - informujemy kompilator że tworzymy pseudo zmienną
- int - jakiego typu jest pseudo zmienna
- zmienna - nazwa pseudo zmiennej
Budowa gettera jest taka sama jak zwykłej funkcji, ma natomiast narzucone kilka rzeczy:
- nie przyjmuje on żadnych argumentów
- zawsze musi wywołać funkcje return
- zwraca typ taki jaki był podany przy 'property' (dlatego nie musimy podawać typu zwracanego, jak w normalnej funkcji)
- musi się nazywa się get
Przykład gettera:
int zmiennaPozaMethodmap; methodmap Nazwa { /... property int zmienna { public get() { return zmiennaPozaMethodmap; } } /... };
Krótki opis:
- public get() - tworzymy getter dla zmiennej
- return zmiennaPozaMethodmap; - zwraca zmienną 'zmiennaPozaMethodmap'
Teraz czas przyszedł na setter, jego budowa także jest podobna do normalnej funkcji, i tak samo jak getter ma narzucone kilka rzeczy:
- nie zwraca żadnej wartości
- przyjmuje tylko 1 argument
- musi się nazywać set
Przykład settera:
int zmiennaPozaMethodmap; methodmap Nazwa { /... property int zmienna { public set(int wartosc) { zmiennaPozaMethodmap = wartosc; } } /... };
Krótki opis:
- public set(int wartosc) - tworzymy setter z wejściem dla argumentu typu 'int' o nazwie 'wartosc'
- zmiennaPozaMethodmap = wartosc; - ustawiamy zmiennej 'zmiennaPozaMethodmap' wartość zmiennej 'wartosc'
Jak pisałem wcześniej getter służy do pobierania wartości a setter do ustawiania, dlatego poniższy zapi jest logiczny
Nazwa mojMethodmap; mojMethodmap.zmienna = 20; //setter - ustawiamy wartość 20 PrintToChat(client, "zmienna = %d", mojMethodmap.zmienna); //getter - pobieramy wartość (w tym przypadku 20)
Alias
Jeśli chcemy stworzyć alias (tak naprawdę twórcy nazywają to "inline method", ale dla zobrazowania będę to nazywał alias'em) na jakąś funkcje możemy zrobić to w szybki sposób, poniższy przykład powinien to zilustrować:
public void innaFunkcja() { PrintToServer("Wywołano innaFunkcje"); } methodmap Nazwa { /... public void funkcja = innaFunkcja; /... };
Jak widać różnicą między zwykłą funkcją są od razu zauważane, parametry funkcji zostają bez zmian dlatego znikły '(' i '),' znaki '{' oraz '}' zostały zastąpione przez '=' ponieważ podczas kompilacji 'funkcja' zostanie zamieniona na 'innaFunkcja' a nie wywołana z funkcji 'funkcja'
Alias wywołuję się tak samo jak zwykłą funkcjie:
Nazwa mojMethodmap; mojMethodmap.funkcja();
Dziedziczenie
Po co pisać 2 razy to samo? Właśnie tutaj z pomocą przychodzi nam dziedziczenie, ale co tutaj pisać, najlepiej to zilustruje poniższy przykład:
methodmap Rodzic { public void funkcja() { PrintToServer("fun1"); } }; methodmap Dziecko < Rodzic { public void innaFunkcja() { PrintToServer("fun2"); } };
Jak widać powyżej zapis dziedziczenia wygląda następująco:
methodmap nazwa < od_kogo_dziedziczy
Dzięki dziedziczeniu możemy w methodmap'ie 'Dziecko' wywołać funkcje z methodmap'y 'Rodzic', ale na odwrót już nie (wywali błąd przy kompilacji)
Magiczne this
Pisałem wcześniej że methodmap nie ma zmienych, nie jest to do końca prawda, ponieważ ma 1 zmieną która jest w każdym methodmap, a mianowicie zmienną this, nie potrafię tego zbytnio wyjaśnić dlatego może przykład wam to bardziej przybliży
int jakasZmienna = 211; methodmap Nazwa { property int zmienna { public get() { return jakasZmienna; } } public void funkcja() { PrintToServer("this wynosi = %d", this"); PrintToServer("zmienna wynosi = %d", this.zmienna); //gdyby nie było this szukało by zmiennej po za methodmap } }; ... Nazwa mojMethodmap = view_as<Nazwa>(3); //trzeba this jest typu Nazwa, więcej o view_as znajdziesz w poradniku o składni mojMethodmap.funkcja(); //Wypisze "this wynosi = 3" oraz "zmienna wynosi = 211"
Methodmap a enum
Jedną z głównych zalet methodmap jest to że może służyć jako rozszerzenie enum'a, wystarczy że będą miały taką samą nazwę:
enum Nazwa { PIERWSZY = 0, DRUGI = 1, TRZECI = 2 }; Methodmap Nazwa { public bool jestPierwszy() { return this == PIERWSZY; } }; ... Nazwa varA = PIERWSZY; Nazwa varB = TRZECI; PrintToServer("%s", varA.jestPierwszy() ? "tak" : "nie"); // wypisze "tak", więcej na temat {wyrazenie} ? {prawda} : {fałsz} znajdziecie w opisie składni PrintToServer("%s", varB.jestPierwszy() ? "tak" : "nie"); // wypisze "nie"
Daję to nam też dodatkową korzyść, zobaczmy ten przykład:
methodmap AdminId { public int pobierzFlagi() { return GetAdminFlags(this); } }; ... GetPlayerAdmin(id).pobierzFlagi();
Na pierwszy rzut oka może wydać się to nie zrozumiałe, już tłumaczę dlaczego możemy skrócić kod takim zapisem:
- w bibliotece admin istnieje enum o nazwie 'AdminId'
- funkcja 'GetPlayerAdmin' zwraca wartość o type AdminId
Powyższy kod jest równo znaczny z poniższymi:
GetAdminFlags(GetPlayerAdmin(id)); //oraz AdminId zmienna = GetPlayerAdmin(id); zmienna.pobierzFlagi();
Konstruktor i Dekonstruktor
Powinienem o tym wspomnieć wcześniej, ale postanowiłem to zostawić na później.
Konstruktor wywołuję się przy tworzeniu "obiektu", a dekonstruktor przy jego usuwaniu. O to cała filozofia, a teraz zobaczmy na kod:
methodmap Nazwa { public Nazwa() { PrintToServer("Konstruktor Nazwa"); } public ~Nazwa() { PrintToServer("Dekonstruktor Nazwa"); } };
Od razu widać że konstruktor musi mieć taką samą nazwę jak methodmap, należy też dodać że może on przyjąć jakieś parametry oraz że zwraca wartość o typie methodmap'y.
Budowę dekonstruktora ma prawie taka sama nazwę funkcji jak konstruktor, tylko że na początek musimy dać znak '~', w przeciwieństwie do konstruktora nie zwraca żadnej wartości oraz nie przyjmuje żadnych parametrów.
Zatem przetestujmy:
Nazwa mojMethodmap = Nazwa(); PrintToServer("amxx.pl"); delete mojMethodmap;
powyższy kod zwróci nam:
Konstruktor Nazwa amxx.pl Dekonstruktor Nazwa
__nullable__
Zbyt dużo o '__nullable__' nie znajdziemy w sieci, służy to do powiadomienia kompilatora że methodmap nie przyjmuję żadnej wartości (czyli jest null'em), zastosowanie go wymusza od nas użycia new, więcej zobaczycie w poniższym przykładzie:
methodmap Nazwa _nullable__ { public Nazwa() { /... } }; ... Nazwa mojMethodmap = new Nazwa();
Methodmap a obiektowość
Specjalnie wczęsniej słowo obiekt brałem w cudzysłowie, ponieważ sourcepawn 1.7 nadal NIE JEST OBIEKTOWE.
Jeśli nazwali byśmy methodmap klasą, to byśmy zobaczyli że wszystkie funkcje (a raczej metody) oraz 'zmienne' należą do klasy a nie do obiektu (z wyjątkiem this)