I.
Wprowadzenie do  języków zapytań (1)
II.
Wprowadzenie do  języków zapytań (2)
III.
Pojęcia obiektowości w bazach danych (1)
IV.
Pojęcia obiektowości w bazach danych (2)
  Wstęp
  1. Klasy
  2. Polimorfizm
  3. Role
  4. Kolekcje
  5. Trwałość
  6. Moduły
  Podsumowanie
  Zadania
V.
Podstawy semantyczne języków zapytań
VI.
Modele składu obiektów
VII.
Stos środowisk, rezultaty zapytań, funkcja nested
VIII.
Język SBQL (Stack-Based Query Language) (1)
IX.
X.
Dalsze własności SBQL
XI.
Operatory order by i group by
XII.
Przetwarzanie struktur nieregularnych
XIII.
Rozszerzenie języków zapytań o konstrukcje imperatywne
XIV.
Procedury, procedury funkcyjne, metody, reguły zakresu
XV.
Parametry procedur i metod, procedury rekurencyjne, optymalizacja poprzez modyfikację zapytań

 

2. Polimorfizm

Termin "polimorfizm" nie jest jednoznaczny. Polimorfizm został wprowadzony w językach programowania dla oznaczenia pewnej własności systemu typów. W obiektowości termin ten został zaadaptowany dla określenia możliwości dynamicznego wyboru metody (wiązania metody) wykonywanej na obiekcie po skierowaniu do niego komunikatu zawierającego nazwę tej metody. Na ogół te dwa znaczenia są różne i prawie niezależne.

Istotę polimorfizmu objaśnimy na przykładzie. Załóżmy, że zespół programistów musi napisać oprogramowanie do rozliczania dochodów osób związanych z pewną instytucją. Sposób rozliczenia zależy od kategorii osoby. Wyróżnione zostały trzy takie kategorie: pracownik, studentemeryt, Rys. 18.

W programowaniu tradycyjnym konieczne jest napisanie procedury dochody (z parametrem Osoba), która będzie zawierała wewnątrz przełącznik uzależniający przetwarzanie od kategorii osoby, np.:

P4.1.

TypWyniku dochody( TypOsoby Osoba ) {
if Osoba.kategoria = "pracownik" then
...... // obliczenie dochodów dla pracownika */
else if Osoba.kategoria = "student" then
...... // obliczenie dochodów dla studenta */
else if Osoba.kategoria = "emeryt" then
...... // obliczenie dochodów dla emeryta */
else ...... };

Procedurę musi napisać jeden programista, przewidując w niej obliczenia dochodów dla wszystkich kategorii osób. Można sobie wyobrazić, co się zdarzy, jeżeli po kilku latach eksploatacji takiej procedury pojawi się nowa kategoria osób, powiedzmy "pracownik na zlecenie" lub zmienią się przepisy rozliczania jednej tylko kategorii, powiedzmy emerytów. W tej sytuacji, mimo lokalności zmiany, cała ta procedura musi być przerobiona, być może przez innego programistę, w celu uwzględnienia nowych wymagań. Osoby znające takie sytuacje z praktyki łatwo mogą sobie wyobrazić, że taka przeróbka może być trudna i może zaowocować błędami również w tych fragmentach, które nie miały być zmienione.

W przypadku rozwiązania obiektowego zadanie napisania kodu do rozliczania dochodów może być zdekomponowane na trzy całkowicie niezależne metody. Jeden z programistów pisze metodę dochody dla pracownika, inny dla studenta, inny zaś dla emeryta. Ich działalność może być prowadzona równolegle. W ten sposób powstaną trzy niezależne metody o nazwie dochody. Każda z tych metod zostaje przypisana do odpowiednich obiektów: pracownicy będą związani z własną metodą dochody, to samo dotyczy studentów i emerytów. Przełączenie, takie jak podano w powyższym przykładzie, nie wystąpi, gdyż odpowiednia metoda będzie automatycznie wywołana przez sam obiekt jako reakcja na komunikat dochody().

Stosując tę technikę wprowadzenie nowej kategorii osób wymaga napisania metody dochody wyłącznie dla tej kategorii, bez rozpatrywania lub zmieniania metod o tej samej nazwie przewidzianych dla innych kategorii. Podobnie, zmiana przepisów co do rozliczania dochodów emerytów wymaga interwencji wyłącznie wewnątrz metody dochody przewidzianej dla emerytów, bez konieczności wprowadzania nawet najdrobniejszych zmian w metodach dochody dla pracowników i studentów. Polimorfizm pozwala więc w znacznie większym stopniu zdekomponować oprogramowanie na niezależne fragmenty, co sprzyja szybkości jego tworzenia i jego jakości oraz zwiększa potencjał ponownego użycia i możliwości obrotu handlowego.

Omówiona forma polimorfizmu określana jest jako polimorfizm metod. Oznacza on, że decyzja o wyborze konkretnej metody jest podejmowana dynamicznie, w czasie wykonania, w zależności od tego, z jaką metodą jest skojarzony obiekt będący adresatem komunikatu. Polimorfizm metod wymaga dynamicznego wiązania nazw metod, tj. zamiany tych nazw na byty czasu wykonania (referencje do konkretnych fragmentów kodu) podczas czasu wykonania.

Z polimorfizmem wiązane są cechy określane jako przesłanianieprzeciążanie.


2.1. Przesłanianie

Obiektowość dopuszcza sytuację, w której jakiś byt programistyczny będący własnością klasy podrzędnej ma tę samą nazwę jak byt klasy nadrzędnej. Pomimo identyczności nazw mogą to być byty o różnych implementacjach. Dotyczy to w szczególności metod. Taką sytuację określa się jako przesłanianie (overriding).

19
Rys.19. Przesanianiemetod

Przesłanianie dotyczy sytuacji, kiedy różne metody o tej samej nazwie m znajdują się w klasach powiązanych dziedziczeniem (nadklasie i jej podklasie lub podklasach). Po wysłaniu do obiektu będącego członkiem tych klas komunikatu m(...) wybierana jest metoda m znajdująca się najniżej w hierarchii tych klas. Mówi się wtedy, że metoda ta przesłania metodę dziedziczoną z nadklasy.

Przesłanianie stanowi ważny element modelowania pojęciowego i wielokrotnego użycia. Dzięki przesłanianiu jest możliwe dokonanie modyfikacji lub przystosowania metod zdefiniowanych w klasie bardziej ogólnej do potrzeb wymaganych przez klasę wyspecjalizowaną. Rys.19 przedstawia klasę PRACOWNIK, która między innymi zawiera metodę zwolnij, stosowaną do typowego pracownika. Jednakże z układu zbiorowego wynika, że dla członka Rady Zakładowej ta metoda musi być inna; wobec tego klasa Członek Rady Zakładowej będąca specjalizacją klasy PRACOWNIK zawiera inną metodę o tej samej nazwie zwolnij. Ta metoda jest brana pod uwagę, jeżeli zwalniany jest pracownik będący członkiem Rady Zakładowej.

W podanym przypadku metoda z klasy wyspecjalizowanej całkowicie zastępuje metodę z klasy bardziej ogólnej. Niekiedy zachodzi potrzeba tylko nieznacznej modyfikacji (np. uzupełnienia) metody z klasy bardziej ogólnej. Przewidując takie przypadki konieczne jest zapewnienie środków syntaktycznych umożliwiających wywołanie metody z klasy bardziej ogólnej wewnątrz metody z jej podklasy (np. konstrukcja super znana z języka Smalltalk).

Przesłanianie jest realizacją najprostszego modelu dyspozycji (dispatching), tzw. pojedynczej dyspozycji (single dispatching). Polega on na tym, że jeżeli może być wybranych kilka metod o tej samej nazwie, to wybiera się tę, która jest "najbliżej" w stosunku do obiektu - adresata komunikatu. Metoda jest szukana najpierw w klasie, której obiekt jest wystąpieniem; jeżeli nie zostanie znaleziona, wówczas szukana jest kolejno w nadklasach (zgodnie z hierarchią).

Generalnie, wybór odpowiedniej metody może zależeć nie tylko od bliskości jej ulokowania w strukturze klas, ale także od liczby i typu parametrów metody. W takim przypadku pojęcie przesłaniania ma przecięcie koncepcyjne z pojęciem przeciążania (overloading), patrz dalej. Np. wybór metody o nazwie dodaj zależy od tego, czy argumentami są liczby integer czy też real. Taki model dyspozycji jest określany jako wielokrotna dyspozycja (multiple dispatching). Jest on zrealizowany w językach takich jak CLOS, C++ czy JAVA. W modelu tym przesłanianie ma miejsce tylko wtedy, gdy występuje zgodność nie tylko nazw metod usytuowanych w różnych klasach, ale również ich sygnatur.

20
Rys.20. Obiekty, ich rolei klasy

Najczęściej o przesłanianiu mówi się w kontekście metod. Można sobie jednak wyobrazić zastosowanie tego mechanizmu do dowolnych inwariantów przechowywanych w ramach klasy, np. w stosunku do atrybutów zlokalizowanych wewnątrz klas (tzw. zmiennych klasowych lub statycznych). Najprostszym przypadkiem jest tzw. atrybut domyślny (default), którego wartość jest dziedziczona przez obiekt z klasy, o ile nie posiada on sam tego atrybutu lub jego wartość jest NULL. Np. klasa PRACOWNIK może posiadać atrybut domyślny INWALIDZTWO z wartością "pełnosprawny". Dla pracownika nie będącego inwalidą można wtedy nie zapełniać tego atrybutu, gdyż będzie on odziedziczony z klasy. Dla obiektów pracowników będących inwalidami dowolna wartość atrybutu INWALIDZTWO, np. "inwalida ruchu" przesłoni wartość dziedziczoną z klasy. W niektórych propozycjach rozpatruje się także sytuację, kiedy pewna metoda funkcyjna (atrybut wirtualny) przesłania atrybut obiektu lub odwrotnie. Np. dla klasy FIRMA może być zdefiniowany atrybut ObrotyRoczne, który dla klasy SKLEP (będącej specjalizacją klasy FIRMA) jest przesłonięty przez metodę ObrotyRoczne obliczającą obroty roczne np. na podstawie informacji o dziennych utargach. Tego rodzaju sytuacje mogą powstać np. w standardzie ODMG, gdzie jego autorzy deklarują, że nie będą czynić rozróżnienia pomiędzy atrybutem zapamiętanym i wirtualnym.


2.2. Przeciążanie

Przeciążanie oznacza, że jakiś symbol (np. operatora, funkcji) ma znaczenie zależne od kontekstu jego użycia, np. od składni, liczby lub typu argumentów.

Pojęcie przeciążania (overloading) jest podobne do przesłaniania, ale posiada nieco różną semantykę. Przy przeciążaniu na tym samym poziomie widoczności występują dwie (lub więcej) różne operacje o tej samej nazwie, lecz różnej sygnaturze. Powszechne jest przeciążanie operatora równości =, który służy do porównania liczb całkowitych, liczb rzeczywistych, stringów, identyfikatorów, struktur itd. Podobnie, operator + może oznaczać dodawanie lub konkatenację.

Przeciążanie jest w gruncie rzeczy własnością z poziomu składni języka: jest odmianą homonimii, którą można rozstrzygnąć na podstawie kontekstu występowania homonimicznego elementu. Znaczenie takiego "przeciążonego" symbolu można rozpoznać podczas statycznej analizy tekstu programu. Przeciążanie może stać się środkiem wspomagającym wielokrotne użycie, gdzie jednostką jest fragment tekstu programu zmieniający swe znaczenie w zależności od kontekstu, w którym jest zastosowany. Niektórzy autorzy uważają też przeciążanie za formę polimorfizmu (tzw. statyczny polimorfizm), chociaż, jak się wydaje, zakres jego zastosowania jest ograniczony.

Przeciążanie występuje we wszystkich znanych językach jako własność "wszyta" w definicję języka. W nielicznych językach, np. w C++, przeciążanie jest udostępnione jako środek znajdujący się w rękach programisty. Oznacza ono, że pewne operatory lub funkcje mogą być "przeciążone", czyli ich definicja może być ad hoc zmieniona na życzenie programisty. W tej roli przeciążanie nie ma najlepszej opinii, gdyż z jego pomocą można łatwo napisać program, którego zrozumienie może być trudne dla osób innych niż jego twórca. Z tego względu w języku Java zrezygnowano z tej cechy.

W przypadku metod przeciążanie ma miejsce, gdy co najmniej dwie różne metody o tej samej nazwie, ale o różnych sygnaturach zdefiniowane są w tej samej klasie. Jest to dopuszczalne przy założeniu wielokrotnej dyspozycji (multiple dispatching). W odróżnieniu od przeciążania, przesłanianie jest własnością dynamiczną. Może okazać się niemożliwe wydedukowanie z tekstu programu, która z potencjalnie możliwych inkarnacji danej operacji ma być użyta w konkretnym wołaniu metody (komunikacie wysłanym do obiektu).

Copyrights © 2006 PJWSTK
Materiały zostały opracowane w PJWSTK w projekcie współfinansowanym ze środków EFS.