Paradygmaty programowania

Paradygmaty programowania

Sztuka programowania (alternatywnie – rzemiosło, jeśli Czytelniku masz zastrzeżenia), tak jak każda dziedzina, w której coś jest wytwarzane, wymaga pewnej bazy wzorców, jakiegoś ustalonego poglądu na rzeczywistość danej dziedziny, co możemy w dziedzinie tworzenia oprogramowania określić jednym słowem – paradygmaty programowania. Patrząc od strony programisty za paradygmaty możemy przyjąć zbiór mechanizmów, jakich programista używa, żeby napisać program, jak i sposób wykonania tego programu przez komputer. Wraz z rozwojem informatyki wiele podstawowych paradygmatów programowania pojawiało się i odchodziło do lamusa, torując drogę dla tych, które dzisiaj są najpopularniejsze i które przytoczę tu jako najbardziej znane, aktualne przykłady.
Pamiętajmy, że paradygmaty programowania mogą, ale nie muszą być powiązane z konkretnymi językami programowania. Poszczególne języki mogą wspierać wybrany paradygmat (np. Java, stworzona do programowania obiektowego) lub dawać możliwość korzystania z wielu wzorców (np. Python, w którym możemy programować obiektowo, imperatywnie czy funkcyjnie).

Programowanie imperatywne

Najbardziej pierwotnym sposobem programowania komputerem jest programowanie imperatywne. Polega ono na opisaniu programu jako prosty ciąg poleceń zmieniających stan maszyny, na której jest wykonywany. W uproszczeniu mówiąc skupia się więc na tym w jaki sposób działa program, a nie jaki jest jego cel. Paradygmat ten powiązany jest z komputerami opartymi na architekturze von Neumanna, gdzie program rozumiany jako ciąg instrukcji wpływa na globalny stan maszyny – zawartość jej pamięci, rejestrów i znaczników procesora. Przykładem języka korzystającego z programowania imperatywnego był oryginalny FORTRAN czy też ALGOL. Przeciwieństwem programowania imperatywnego jest programowanie deklaratywne, które z definicji skupia się na tym, co program ma osiągnąć, a nie w jaki sposób. Naturalnymi następcami, czy może bardziej rozszerzeniami programowania imperatywnego są:

  • programowanie proceduralne – zaleca dzielenie kodu na procedury, czyli ciągi instrukcji wykonujące ściśle określone operacje. Powinny one pobierać wszelkie przetwarzane dane jako parametry wywołania i zwracać wynik na ich podstawie.
  • programowanie strukturalne -polega na dzieleniu kodu na procedury oraz hierarchiczne bloki przy użyciu instrukcji sterujących (if/else) i iteracji (for/while/repeat).
  • ostatecznie rozwój powyższych doprowadził do powstania paradygmatu programowania obiektowego, które zakłada łączenie danych i procedur w obiekty, o czym więcej w dalszej części tego artykułu.

Przykład programowania imperatywnego w języku FORTRAN – obliczenia pola trójkąta (źródło: Wikibooks)

C AREA OF A TRIANGLE - HERON'S FORMULA
C INPUT - CARD READER UNIT 5, INTEGER INPUT, NO BLANK CARD FOR END OF DATA
C OUTPUT - LINE PRINTER UNIT 6, REAL OUTPUT
C INPUT ERROR DISPAYS ERROR MESSAGE ON OUTPUT
  501 FORMAT(3I5)
  601 FORMAT(" A= ",I5,"  B= ",I5,"  C= ",I5,"  AREA= ",F10.2,
     $"SQUARE UNITS")
  602 FORMAT("NORMAL END")
  603 FORMAT("INPUT ERROR OR ZERO VALUE ERROR")
      INTEGER A,B,C
   10 READ(5,501,END=50,ERR=90) A,B,C
      IF(A=0 .OR. B=0 .OR. C=0) GO TO 90
      S = (A + B + C) / 2.0
      AREA = SQRT( S * (S - A) * (S - B) * (S - C) )  
      WRITE(6,601) A,B,C,AREA
      GO TO 10
   50 WRITE(6,602)
      STOP
   90 WRITE(6,603)
      STOP
      END

Programowanie funkcyjne

Według tego paradygmatu (odmiana programowania deklaratywnego) program traktujemy jako złożoną, matematyczną funkcję, która dla podanych danych wejściowych zwraca konkretny wynik. W językach czysto funkcyjnych nie bierzemy pod uwagę stanu maszyny (nie istnieją zmienne, tylko argumenty funkcji), nie występują również efekty uboczne (żaden efekt uruchomienia funkcji nie wykracza poza zwrócenie wartości). Tradycyjnym przedstawicielem takiej grupy jest język Haskell.

Najpopularniejszym jednak podejściem stosowanym dzisiaj są języki mieszane, które pozwalają na stosowanie zmiennych, efekty uboczne czy użycie tradycyjnego wejścia/wyjścia. Mieszają one styl funkcyjny z imperatywnym lub obiektowym. Takimi językami są Lisp, Scala, czy nowoczesny F#. W pewnym stopniu funkcyjne podejście możemy stosować w JavaScript, czy Javie.

Przykład programowania funkcyjnego w języku Scala – sortowanie a’la quicksort (źródło: Wikipedia)

def qsort(list : List[Int]): List[Int] = list match {
  case Nil => Nil
  case pivot :: tail => {
    val (smaller, rest) = tail partition (_ < pivot)
    qsort(smaller) ::: pivot :: qsort(rest)
  }
}

Programowanie logiczne

Zupełnie odmiennym od powyższych styli jest paradygmat programowania logicznego. Program pisany w tym stylu jest zbiorem wyrażeń logicznych – przesłanek (rozumianych w kontekście logiki matematycznej) wyrażających fakty i zależności należące do dziedziny problemu, a wynikiem obliczeń programu jest dowód pewnego twierdzenia (celu) w oparciu o te zależności. Znów jest to przykład programowania deklaratywnego – nie piszemy instrukcji, a opisujemy nasz zbiór wiedzy oraz efekt, jaki chcemy uzyskać. Przykładem języka umożliwiającego programowanie logiczne jest Prolog.

Przykład programowania logicznego w języku Prolog – sortowanie a’la quicksort (relacja listy do jej posortowanej wersji) (źródło: Wikipedia)

partition([], _, [], []).
partition([X|Xs], Pivot, Smalls, Bigs) :-
    (   X @< Pivot ->
        Smalls = [X|Rest],
        partition(Xs, Pivot, Rest, Bigs)
    ;   Bigs = [X|Rest],
        partition(Xs, Pivot, Smalls, Rest)
    ).
 
quicksort([])     --> [].
quicksort([X|Xs]) -->
    { partition(Xs, X, Smaller, Bigger) },
    quicksort(Smaller), [X], quicksort(Bigger).

Programowanie obiektowe

Programowanie obiektowe jest dzisiaj chyba najbardziej rozpowszechnionym paradygmatem. Definiuje on program jako zbiór komunikujących się ze sobą obiektów, czyli elementów składających się z danych (stan obiektu) oraz procedur/funkcji (zachowanie obiektu). Języki programowania obiektowego posiadają najczęściej przynajmniej następujące cechy:

  • Abstrakcja
  • Dziedziczenie
  • Polimorfizm
  • Enkapsulacja

Prawidłowe podążanie za paradygmatem programowania obiektowego umożliwia wielokrotne użycie całych programów lub ich fragmentów. Programowanie obiektowe prawdopodobnie najlepiej odzwierciedla sposób, w jaki ludzie postrzegają rzeczywistość. Przykładowe języki umożliwiające programowanie obiektowe (niezależnie czy używają klas czy prototypów) to: C++, C#, Java, PHP5, Python, Ruby, JavaScript, czy też OCaml.
Nawet jeśli Czytelniku twierdzisz inaczej, to musisz wiedzieć, że wypada programowanie obiektowe uznać za paradygmat aktualnie dominujący.
Z racji moich osobistych zainteresowań, jak i obranej przeze mnie ścieżki zawodowej, to właśnie programowaniu obiektowemu planuję poświęcić najwięcej miejsca na tym blogu.

Przykład najprostszego programu typu Hello, world! w języku Java:

 public class HelloWorld{
    public static void main(String[] args) {
        //W Javie wszystko jest obiektem, również obiekt out z klasy System, wykorzystany poniżej.
        //Obiekt out jest statyczny, tj. wywołany z klasy, a nie z jej konkretnej instancji. Więcej o tym w innych wpisach.
        System.out.println("Hello, I love object-oriented programming.");
    }
}

 

Inne paradygmaty

Możesz być pewny Czytelniku, że wymienione paradygmaty to tylko wierzchołek góry lodowej. Naturalnie niektóre są wykorzystywane częściej, niektóre rzadziej, ale istnieje jeszcze szeroki wybór: programowanie modularne, agentowe, uogólnione, sterowane zdarzeniami czy aspektowe. Być może w przyszłości będę mógł poświęcić im jakiś artykuł, a tymczasem zapraszam Cię do rozszerzania nabytej wiedzy we własnym zakresie.

Leave a Comment

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *