Paradygmaty programowania

advertisement
Wg. J. Bylina, B. Bylina „Przegląd języków i paradygmatów programowania”, UMCS, Lublin 2011
Paradygmaty
programowania
Paradygmat (gr. Paradeigma) - wzorzec lub przykład
Def. słownikowa: przyjęty sposób widzenia rzeczywistości w
danej dziedzinie
W informatyce: zestaw typowych dla danej grupy języków
mechanizmów dostępnych programistom oraz sposobów ich
interpretacji prze semantykę języka
Podstawowy podział paradygmatów
i języków programowania
Paradygmaty i odpowiadające im języki programowania można
podzielić na dwie główne grupy:
- imperatywne
- deklaratywne.
Języki imperatywne używają rozkazów opisujących czynności
które komputer ma w pewnej kolejności wykonywać.
Program jest więc listą rozkazów do wykonania, stąd nazwa
imperatywne („rozkazowe").
W językach deklaratywnych programista podaje (deklaruje)
komputerowi pewne zależności oraz cele, które program ma osiągnąć.
Języki imperatywne mówią komputerowi, jak ma osiągnąć wynik
(choć nie określają jaki wynik), natomiast języki
deklaratywne opisują, co ma być osiągnięte (choć nie podają jak).
Ten podział przedstawia rysunek
W programowaniu imperatywnym program jest listą instrukcji
(mniej lub bardziej elementarnych), które mają być wykonywane
kolejno z możliwością wielokrotnego powtarzania pewnych
czynności zapisanych raz czyli wykonywania pętli (iteracji).
Zestaw instrukcji zawierający rozkazy operacji na pewnych danych
wraz z rozkazami skoków bezwarunkowych i warunkowych
(zaburzających zasadę bezpośredniego następstwa) jest istotą
każdego z kodów maszynowych, najbardziej pierwotnego języka
wykonywanego w maszynach o tzw. architekturze von
Neumanna.
Architektura von Neumanna zakłada że:
— maszyna składa się z pamięci oraz jednostki centralnej, która
wykonuje rozkazy (procesora);
— rozkazy oraz dane zapisane są w tej samej pamięci w ten sam
sposób;
— rozkazy są kolejno z pamięci wczytywane do jednostki
centralnej i wykonywane;
— każdy rozkaz powoduję zmianę stanu maszyny rozumianego
jako zawartość całej pamięci włącznie z rejestrami i znacznikami
procesora; rozkazy mogą więc zmieniać wewnętrzne ustawienie
jednostki centralnej, w tym miejsce, z którego będzie czytany
następny rozkaz.
W praktyce dzisiejsze komputery budowane są (i działają) w
oparciu o architekturę von Neumanna. Zatem najbardziej
naturalnym paradygmatem dla maszyny jest paradygmat
imperatywny, zaś dla człowieka wygodniejszym sposobem
komunikowania jest określenie, co ma być osiągnięte, bez
wdawania się w detale wykonania czyli paradygmat
deklaratywny.
Maszyna von Neumanna i programowanie bezpośrednie
( w kodzie maszynowym)
Programowanie proceduralne
Programowanie proceduralne - jeden z rodzajów programowania
imperatywnego. W programowaniu proceduralnym występują części kodu
zwane podprogramami (procedury, funkcje, metody, operacje), które
mogą być wielokrotnie — także rekurencyjnie — wywoływane z różnymi
parametrami.
Warto zauważyć, że programowanie proceduralne umożliwiło
powstanie techniki programowania bottom-up od małych elementów do
coraz większych i w końcu do całego programu.
Ta technika pozwoliła na rozwój:
— programowania zespołowego — każdy z programistów dostaje do
wykonania swoją część zadania, z tych podprogramów budowane
jest całe oprogramowanie
— bibliotek oprogramowania —biblioteki są zbiorami podprogramów
wykonujących pewne działania, które programiści mogą składać by
po uzupełnieniu własnymi podprogramami stworzyć cały program
Programowanie strukturalne
To rozwinięcie paradygmatu proceduralnego Programowanie
strukturalne korzysta z możliwości zapisania każdego algorytmu za
pomocą elementarnych tzw. struktur sterujących :
- sekwencja( bezpośrednie następstwo), czyli kolejne wykonywanie
czynności
- selekcja, czyli wybór działania na podstawie spełnienia jakiegoś
warunku (instrukcja warunkowa z alternatywą)
- pętla „dopóki", czyli ściśle określony fragment algorytmu
powtarzany przy ściśle określonych warunkach
- podprogram pozwalający wydzielony podalgorytm zapisać, nazwać i
wywoływać wielokrotnie — (podprogramy mają dokładnie jeden
punkt wejścia oraz dokładnie jeden punkt wyjścia)
- rekurencja, czyli możliwość wywoływania podprogramu prze niego
samego.
Powyższa lista nie zawiera instrukcji skoku. Instrukcje skoku, a
zwłaszcza instrukcja skoku bezwarunkowego goto uważane są za
szkodliwe.
Ograniczenie się do powyższych pięciu konstrukcji umożliwia stosowanie
tzw. logiki Hoare'a do dowodzenia poprawności algorytmów strukturalnych.
Większość współczesnych języków programowania rozszerza tę listę
pozwalając na stosowanie różnego rodzaju „skoków strukturalnych", na
przykład:
— w wielu językach instrukcja return pozwala zakończyć
wykonywanie podprogramu w różnych miejscach, a więc utworzyć
wiele punktów wyjścia z jednego podprogramu;
— w C i językach pochodzących od C instrukcje break oraz continue po
zwalają odpowiednio na wyskoczenie z pętli oraz przeskoczenie jej części;
— niektóre systemy operacyjne dostarczają funkcji systemowych (na przy
kład longjump w Linuksie; także obsługa sygnałów, przypominająca nie
co obsługę wyjątków), których wywołanie może spowodować przekazanie
sterowania praktycznie dowolnemu miejscu w programie — a więc realnie
skok w dowolne miejsce programu;
— w końcu wiele języków dostarcza wprost instrukcję goto, choć czasem
są nakładane pewne ograniczenia w jej stosowaniu.
Programowanie strukturalne umożliwia także inną technikę
programowania, mianowicie top-down. Jest to odwróceniem techniki
bottom-up. Polega ona na dzieleniu całego zadania na mniejsze części
zgodnie z przewidywaną strukturą na najwyższym poziomie, wypełnianiu
tej struktury rozkazami elementarnymi, a następnie zastosowaniu
rekurencyjnie takiego dzielenia dalej w głąb, do coraz to drobniejszych
zadań.
Programowanie obiektowe
Najbardziej
rozpowszechnionym
w
dzisiejszych
czasach
paradygmatem programowania jest paradygmat obiektowy. Jest to
paradygmat strukturalny rozszerzony jedynie o pojęcia klas i obiektów.
Obiekty są tutaj zamkniętymi kontenerami zawierającymi dane (co
przypomina rekordy czy też struktury znane z takich języków jak Pascal
czy C), ale oprócz danych także podprogramy na tych danych
działające, zwane metodami.
Program w języku obiektowym jest nadal sekwencją rozkazów.
Jednakże te rozkazy — w języku czysto obiektowym — nie są wydawane
maszynie, lecz są wydawane poszczególnym obiektom, także przez
inne obiekty.
Takie wydzielenie danych wraz z możliwymi do wykonania na nich
czynnościami (w odróżnieniu od procedur, które są opakowaniem samych
czynności, oraz w odróżnieniu od wspomnianych wyżej rekordów, które
są opakowaniem samych danych) pozwala na wyróżnienie pewnych cech
programowania obiektowego, których próżno szukać w innych
paradygmatach imperatywnych:
— hermetyzacja, inaczej enkapsulacja, polegająca na tym, że tylko
pewne dane i metody obiektu (stanowiące jego interfejs) są
widoczne „na zewnątrz", dla innych obiektów; natomiast jego
implementacja jest ukryta przed — umyślnym bądź przypadkowym
„uszkodzeniem" czy też złym wykorzystaniem;
— dziedziczenie pozwalające tworzyć obiekty bardziej skomplikowane
na bazie prostszych; co więcej, dziedziczenie klas przekłada się na
zawieranie się jednej w drugiej, a to oznacza, że obiekty mogą należeć
jednocześnie do wielu klas, co ma istotne znaczenie dla polimorfizmu
— abstrakcja danych wynikająca bezpośrednio z hermetyzacji i
dziedziczenia — można w prosty sposób definiować ogólne obiekty
(czy też klasy), które są jedynie wzorcami pewnych bardziej
skomplikowanych, doprecyzowanych obiektów;
— polimorfizm dynamiczny (inaczej polimorfizm obiektowy], który
dzięki dziedziczeniu pozwala obiektom automatycznie dobierać
odpowiednie metody do swojego aktualnego typu.
Programowanie funkcyjne
Funkcyjny paradygmat programowania jest podparadygmatem
programowania deklaratywnego. W programowaniu funkcyjnym
(funkcjonalnym), tak jak w deklaratywnym, opisujemy pożądany wynik ale
w postaci funkcji. Zadaniem interpretera lub kompilatora języka
funkcyjnego jest obliczenie wartości funkcji, a więc pewnego wyrażenia.
W programowaniu czysto funkcyjnym funkcje zawsze przyjmują tę samą
wartość dla tych samych argumentów a więc nie zależą ani od stanu
maszyny, ani od urządzeń wejścia/wyjścia, użytkownika, pamięci
zewnętrznej itd.
W związku z pojęciem programu jako złożenia pewnych funkcji, w programowaniu funkcyjnym nie występują zmienne znane z programowania imperatywnego ani pętle, zamiast których używa się rekurencji (zmienne i
pętle potrzebują dostępu do stanu maszyny, którego tu nie ma).
Z drugiej strony, funkcje te mogą być takimi samymi wartościami
argumentów i wyników innych funkcji jak dane - liczby, napisy, listy...
Możliwe jest tutaj tzw. leniwe wartościowanie wyniku funkcji,
czyli obliczanie tylko fragmentów wyniku, potrzebnych dla innej
funkcji.
Umożliwia to obliczanie składowych funkcji niezależnie od
siebie, co pozwala na automatyczne zrównoleglanie kodu i
przetwarzanie potokowe.
Programowanie logiczne
W programowaniu logicznym (należącym do paradygmatu
deklaratywnego) — nie opisuje się drogi do rozwiązania, lecz
dostarczamy maszynie zbiór przesłanek oraz tezę do dowiedzenia
w postaci pytania. Maszyna ma udowodnić tezę na podstawie
danych przesłanek. Wszystkie inne działania maszyny są efektami
ubocznymi tego dowodzenia.
.
Tabela . Języki programowania a główne
paradygmaty
języki
asemblery, „stary" BASIC, „stary" Fortran
„stary" Pascal, C
C++, Object Pascal, Ada
Smalltalk, C#, Java
Lisp, Scheme, Logo, ML, OCaml
Haskell
Planner, Prolog
Python, Ruby
SQL
paradygmaty
imperatywny
proceduralny
imperatywny
proceduralny
strukturalny
imperatywny
proceduralny
strukturalny
obiektowy
obiektowy
proceduralny
funkcyjny
czysto funkcyjny
logiczny
proceduralny
strukturalny
obiektowy
funkcyjny
deklaratywny (ale ani
ściśle funkcyjny, ani ściśle
logiczny)
Dodatkowe paradygmaty
Programowanie modularne - pośrednie między programowaniem
obiektowym a proceduralnym. W tym paradygmacie głównym
elementem programu jest moduł (pakiet) zawarty zwykle w osobnym
pliku i w wielu aspektach traktowany jako obiekt. Języki: Ada, Haskell,
Python.
Programowanie aspektowe - blisko związane z powyższym. Jego celem
jest podział problemu na niezależne logicznie części i ograniczenie ich
liczby styków oraz ścisłe kontrolowanie każdego z nich. Język: AspectJ.
Programowanie komponentowe - paradygmat związany z modularyzacją
programów i
z
programowaniem
obiektowym.
Komponentami są samodzielne obiekty wyposażone w ściśle
wyspecyfikowany interfejs, wykonujące pewne określone usługi.
Paradygmat ten związany jest z tzw. programowaniem zdarzeniowym.
Języki: Eiffel, Oberon.
Programowanie agentowe - abstrakcyjna forma programowania
obiektowego. Elementem jest agent , czyli wyspecjalizowany i odporny
na błędy samodzielny obiekt, który w pewnym środowisku np. w sieci
komputerowej może pracować sam, a w potrzebie komunikować się z
innymi agentami. Działający w sieci agenci często dublują swoje
czynności, po to, by zapewnić maksymalną odporność na błędy i utratę
wyników. Nie bez znaczenia jest też ewentualna możliwość
samoreplikacji agentów. Języki: JADE (framework Javy).
Programowanie zdarzeniowe (sterowane zdarzeniami) - program składa
się z wielu niezależnych podprogramów, których kolejność wykonania nie
jest określona z góry przez program główny, lecz które są uruchamiane w
reakcji na zaistnienie pewnych zdarzeń. Występuje w systemach
operacyjnych. Obsługa wyjątków w różnych językach ma charakter
programowania zdarzeniowego.
Programowanie kontraktowe (związane z paradygmatem obiektowym
ale także jako rozszerzenie programowania strukturalnego) - takie
tworzenie kodu, by mógł być on automatycznie sprawdzony (pod
względem zgodności ze specyfikacją) i ewentualnie przetestowany.
Języki: Eiffel, interfejsy w Javie.
Programowanie generyczne (inaczej: uogólnione, rodzajowe) umożliwia tworzenie jednostek (klas, obiektów, funkcji, typów)
parametrycznych, (polimorficznych, uogólnionych), które stają się pełnoprawnymi jednostkami w chwili ich dookreślenia przy skorzystaniu z ich
definicji w gotowym programie. Języki: Ada, C++, Haskell.
Programowanie refleksyjne – umożliwia pisanie programów
samomodyfikujących się. Program może czytać własny kod, i go
modyfikować. Języki: Python, Lisp, Scheme.
Programowanie sterowane przepływem danych - programy
wykonywane nie według ustalonej kolejności czynności, lecz
według dostępności danych ( wykonywanie na nich czynności,
gdy dane staną się dostępne). Przykład: praca arkusza
kalkulacyjnego - przelicza dane, gdy tylko się zmienią oraz przetwarzanie potokowe w Uniksowych systemach operacyjnych.
Języki: Linda.
Programowanie współbieżne, równoległe, rozproszone –
powiązane ze sobą (choć nietożsame) paradygmaty, bliskie
programowaniu sterowanemu przepływem danych. Uwzględniają
zagadnienia związane są z podziałem czasu procesora (lub
procesorów) między procesy, synchronizacją procesów,
podziałem pamięci wspólnej, przesyłaniem komunikatów
pomiędzy procesami.
Download