Ostatnim razem przedstawiłem ideę typów generycznych i pokazałem jak wykorzystywać je w funkcjach. Jednak funkcje to nie jedyne mechanizmy, mające możliwość korzystania z typów generycznych. Funkcjonalność ta może być również wykorzystana przy interfejsach oraz klasach.
W dzisiejszym poście zaprezentuję jak tworzyć interfejsy oraz klasy w połączeniu z typami generycznymi. Ta potężna kombinacja daje programistom TypeScript naprawdę spore możliwości.
Główny cel używania typów generyczny wraz z interfejsami i klasami jest taki sam jak w przypadku funkcji. Chcę pisać kod, który mogę używać w różnych warunkach. Innymi słowy, chcę uniknąć powtarzania tego samego kodu dla różnych typów danych. Brzmi to fajnie, ale zdaję sobie sprawę, że przykłady z funkcjami mogły nie wyglądać przekonująco. Myślę jednak, że w przypadku klas i interfejsów, każdy będzie w stani9e zobaczyć prawdziwy potencjał tego rozwiązania.
Przejdę od razu do przykładu. Oto interfejs zaimplementowany przy użyciu generyków:
1 2 3 4 5 |
interface HandlerInterface<T> { animalList: Array<T>; getLastAnimal:()=> T; addAnimal: (T) => void; } |
Struktura powinna wyglądać już znajomo. Wygląda to jak zwykły interfejs z jedną różnicą. Po jego nazwie podaję listę parametrów typów. Tak jak w przypadku funkcji, znajdują się one wewnątrz ostrych nawiasów. I tak jak w przypadku funkcji, rzadko jest ich więcej niż jeden 😉 . Wewnątrz interfejsu, wszystkie te elementy, które chcę aby przybrały typ generyczny, oznaczam literką T (powtarzam: wielkie T to przyjęty zapis, jednak parametry typowe można nazywać dowolnie, z godnie z JSowymi konwencjami nazw).
Pierwszy element animalList będzie przechowywał tablicę elementów typu T. Drugi element, metoda getLastAnimal, zwraca obiekt/zmienną typu T. Natomiast ostatni element, metoda addAnimal, przyjmuje obiekt/zmienną typu T. Warto w tym miejscu zwrócić uwagę na to jak zapisałem typ pola animalList. To nic innego jak klasa Array o typie generycznym 🙂 . To może nie być jeszcze do końca jasne, ale za chwile powinno się wyklarować.
Sam w sobie interfejs ten nie jest specjalnie użyteczny. mógłbym stworzyć obiekt typu HandlerInterface, wyglądałoby to tak:
1 |
var exampleObject: HandlerInterface<number>; |
Tak zadeklarowana zmienna, przechowywać może dane typu HandlerInterface, który obsługuje tylko liczby. Oczywiście, można stworzyć drugi obiekt, który obsługiwać będzie inny typ danych, na tym polega piękno typów generycznych 🙂 . Nic nie stoi na przeszkodzie aby stworzyć interfejs, który sprawdziłby się w takim zadaniu. Jednak w moim przykładzie nie ma to większego sensu.
Znacznie lepszym rozwiązaniem byłyby zaimplementowanie go do klasy. Na przykład w taki sposób:
1 2 3 4 5 6 7 8 9 |
class AnimalHandler<T> implements HandlerInterface<T> { constructor(public animalList:Array<T>){}; getLastAnimal(){ return this.animalList[this.animalList.length-1] }; addAnimal(animal:T){ this.animalList.push(animal) } } |
Klasa AnimalHandler implementuje interfejs HandlerInterface. Jeśli implementowany interfejs korzysta z typów generycznych, klasa też musi z nich korzystać. W tym wypadku T odnosi się do tego samego typu i tak będzie traktowany.
Nic nie stoi na przeszkodzie, aby typy generyczne dodawać do klas bez konieczności korzystania z interfejsu. Gdyby z powyższego przykładu wyciąć fragment implements HandlerInterface
Klasa AnimalHandler w konstruktorze przyjmuje jeden argument, tablicę elementów typu T, która przypisana jest do publicznego pola animalList. Ponadto posiada dwie metody: getLastAnimal, która zwraca ostatni element tablicy animalList, oraz addAnimal, które przyjmuje argument typu T i dodaje go do tablicy animalList.
Od razu widać jak dobrze to wszystko współgra ze sobą. Żadne zasady obsługi typów nie są naginane a jednak taka klasa jest już naprawdę wszechstronna.
Załóżmy, że w programie mamy klasy Cat oraz Dog definiowane w taki sposób:
1 2 3 4 5 6 7 |
class Dog { constructor(public name:string){}; } class Cat { constructor(public name:string){}; } |
Teraz mogę tworzyć instancje klasy AnimalHandler Tak aby obsługiwały zarówno obiekty jednego jak i drugiego typu:
1 2 |
var dogHandler = new AnimalHandler<Dog>([]); var catHandler = new AnimalHandler<Cat>([]); |
Przedstawione tu rozwiązania, dają programiście ogromne możliwości. Typy generyczne to zdecydowanie jeden z ciekawszych elementów TypeScriptu. A to jeszcze nie koniec, czasem potrzebna jest możliwość ograniczenia generyków. Kolejny post poświęcę właśnie temu zagadnieniu.
Jeżeli chcesz być na bieżąco z postami na blogu zachęcam do polubienia mojej strony na facebooku. Zawsze zamieszczam tam informacje o wszystkich nowościach. Jest to też dobre miejsce na kontakt ze mną. Na wszystkie pytania zawsze odpowiem :).