Języki i techniki programowania Ćwiczenia 4 Wzorce
Wzorce to metoda generacji różnych klas lub różnych funkcji. Autor: Marcin Orchel Przykład 1: Jeśli dana klasa C wykonuje obliczenia numeryczne, i wymaganiem jest przeprowadzanie obliczeń zarówno dla typu float jak i double, np w celach porównawczych to można zastosować wzorce. Alternatywą byłoby napisanie klasy C dla typu float jak i double, wadą tej metody byłaby redundancja kodu. Drugą alternatywą byłoby przekazanie do wszystkich funkcji wykonujących obliczenia na typie float lub double abstrakcyjnej klasy bazowej D dla opakowanych typów float i double. Wady tego rozwiązania: w ogólności konieczność stworzenia klasy bazowej również dla typów, które nie są ze sobą powiązane. Konieczność opakowywania typów prostych. Wszystkie funkcje wykonujące obliczenia na typie D mogłyby być również przeniesione do wnętrza klasy D. Klasa wzorcowa template<class T> C Operowanie na klasach wygenerowanych z wzorca: C<float> o1; C<double> o2; Przykład 2: Jak zrealizować metodę porównującą dwa elementy dowolnego typu? 1. Przekazanie dwóch elementów typu Comparable. Wadą tego rozwiązania, jest to, że element, który może być porównywany musi dziedziczyć po Comparable. 2. Przekazanie dwóch argumentów typu Object o1 i o2, oraz przekazanie obiektu Comparatora dla dwóch podanych obiektów o1 i o2. 3. Zastosowanie wzorców template<t> int compare(t o1, T o2) return o1.compareto(o2); Przykład 3: Często wykorzystujemy listy elementów tego samego typu. Zapewnienie, że lista będzie miała ten sam typ dla każdego elementu, może wyglądać następująco:
po dodaniu pierwszego obiektu do listy zapamiętywany jest typ tego obiektu, przy dodawaniu następnych obiektów typ tych obiektów jest weryfikowany. Takie sprawdzenie typów odbywałoby się juz w czasie działania programu. Zapewnienie kontroli typów w czasie kompilacji za pomocą wzorców: template<t> List public : add(t element) ; Teraz juz w trakcie kompilacji sprawdzany jest typ dodawanych obiektów, wszędzie tam gdzie jest używana metoda add. W Javie można używać list bez genericów, wtedy takie listy dopuszczają różne elementy, lub można używać list z genericami od wersji 1.5. W C++ biblioteka standardowa zawierająca m.in. różne kolekcje, w tym liste, napisana jest z wykorzystaniem wzorców, a więc stosowanie wzorców jest w tym wypadku konieczne. W C++ błędy związane z użyciem wzorców sprawdzane są w miejscu użycia wzorca, mogą być również sprawdzane na etapie linkowania. Błąd, taki, że parametr wzorca T nie ma metody, która jest wywoływana w klasie C, zostanie wykryty na etapie kompilacji przy wywołaniu tej metody. Różnice między genericami w Javie, a wzorcami w C++. W Javie nie mozna podawac typow prostych. W Javie public static <T> void foo(t arg) nie można wywołać metody np arg.he() w metodzie foo, ponieważ typ T ma zdefiniowane metody tylko te co są w typie Object. W C++ można coś takiego zrobić, wtedy przy wywołaniu metody foo sprawdzane jest czy istnieje metoda he() na podanym argumencie. W Javie generici są tylko informacją dla kompilatora, w kodzie pośrednim nie występują w ogóle informacje o genericach, są one usuwane. W C++ przy definicji nowej klasy z wzorca klasa ta jest tworzona i kod klasy wzorcowej jest reprodukowany do nowo powstałej klasy. Pytanie Jeśli metoda przyjmuje List<Figure> czy mozna podać do tej metody List<Circle>? Nie można tego zrobić w Javie, ponieważ gdyby było to możliwe to mielibyśmy listę dowolnych figur i moglibyśmy dodać do niej dowolną figurę. Rozwiązaniem jest List<? extends Figure>, które mówi, że podawany jest jakiś typ dziedziczący po Figure. W tym wypadku też nie można wywołać funkcji add, ponieważ wiemy, że jest to jakiś typ dziedziczący po Figure, ale nie wiemy jaki to jest typ.
W C++ również występuje to zagadnienie, ale nie ma takiego rozwiązania jak w Javie. Ogólnym rozwiązaniem tego zagadnienia jest zastosowanie templatów. template<t> foo(list<t> list). Wtedy możemy podać listę dowolnych elementów.
Zadania Zadanie podstawowe a) Zrobić własną implementację listy jednokierunkowej przechowującej obiekty tego samego dowolnego typu, który posiada operator ==, oraz > wykorzystując do tego wzorce zaimplementować dla tej listy metodę addelement, removeelement, swapelements, getelement, compareelements, parametr wzorca opakować typem InternalListElement zawierającym referencję do następnego obiektu b) Zrobić implementację listy jednokierunkowej bez wzorców, która może przechowywać elementy typu abstrakcyjnego ListElement, zaimplementować te same metody co w podpunkcie a), typ ListElement powinien zostać opakowany typem InternalListElement klasa ListElement powinna zawierać metodę compareto, która będzie wykorzystywana przez metodę compareelements c) zrobić dziedziczenie w klasie Triangle z klasy ListElement, stworzyć listę elementów typu Triangle zarówno dla sposobu pierwszego jak i drugiego, sprawdzić, czy istnieją dwa trójkąty w liście o tym samym obwodzie Zadanie dodatkowe 1 Zaimplementować globalną funkcję wzorcową sort, która przyjmuje jako parametr listę jednokierunkową wzorcową z pierwszego zadania, oraz jako drugi parametr przyjmuje abstrakcyjną klasę wzorcową Comparator<T>, w której zadeklarowana jest metoda compareelements, odziedziczyć po klasie Comparator<T> komparator dla obiektów Triangle, porównujący pola, zamiast obwodów. Posortować tą funkcją przykładową listę obiektów typu Triangle. Zadanie dodatkowe 2 Zaimplementować wzorcową listę dwukierunkową z tymi samymi metodami co w zadaniu 1, InternalListElement zawiera referencję do poprzedniego i następnego obiektu - stworzyć abstrakcyjną klasę List, po której dziedziczą klasy OneDirectionList, TwoDirectionsList - przerobić funkcję sort, tak aby otrzymywała abstrakcyjną klasę List, zrobić przykładowe sortowanie obiektów Triangle