Turbo Pascal. Programowanie

Turbo Pascal.
Programowanie

Autor: Tomasz M. Sadowski
Format: B5, stron: 136

Copyright © 1996 by Wydawnictwo Helion
wersja spakowana

helion.pl

Moduły własne

W miarę rozwoju Twoich umiejętności będziesz rozwiązywał coraz bardziej złożone problemy i pisał coraz bardziej złożone programy. Prędzej czy później staniesz też przed koniecznością rozbicia programu na kilka prostszych fragmentów, czyli podzielenia go na moduły. Modułowość, będąca jedną z podstawowych zasad dobrego programowania, jest rozszerzeniem podejścia proceduralnego. Struktura modularna zakłada wydzielenie funkcji zajmujących się daną dziedziną (np. operacjami wejścia-wyjścia) i zgrupowanie ich w oddzielnych plikach. Różnicę pomiędzy programem "liniowym" a programem modułowym realizującym to samo zadanie przedstawia poniższy rysunek.

Rysunek 13. Struktura programu "liniowego" (a) i modułowego (b)

Co daje zastosowanie struktury modułowej? Jak nietrudno zauważyć, program "liniowy" grupuje wszystkie elementy w jednym pliku źródłowym. Przy większych programach liczba wierszy kodu źródłowego idzie w tysiące, co z kolei drastycznie wydłuża czas potrzebny na znalezienie żądanego fragmentu programu, pogarsza jego czytelność i utrudnia interpretację i uruchamianie. Odpowiedni program o strukturze modułowej składa się z kilku krótszych (a więc łatwiejszych do czytania) plików zawierających funkcje pogrupowane tematycznie (co z kolei ułatwia wyszukiwanie odpowiednich fragmentów programu i jego uruchamianie).

Pierwszą korzyścią z zastosowania struktury modułowej jest poprawa czytelności kodu źródłowego oraz ułatwienie interpretacji i uruchamiania programu.

Nie dość tego, raz napisane i przetestowane funkcje i struktury danych mogą przydać Ci się w przyszłości. Oczywiście podczas pisania kolejnego programu możesz wrócić do odpowiedniego tekstu źródłowego i "potraktować go" edytorem, kopiując odpowiednie fragmenty, jednak znacznie lepiej jest zamknąć raz napisany kod w module. Dlaczego? Po pierwsze, unikasz w ten sposób "przekopywania się" przez stare programy, wyszukiwania odpowiednich fragmentów i złożonych nieraz operacji w edytorze, zastępując to wszystko zwykłym dołączeniem modułu. Po drugie, programowanie z użyciem bibliotek przypomina układanie klocków (bez konieczności zastanawiania się, z czego zostały one zrobione i jak działają - fachowo nazywa się to ukrywaniem szczegółów implementacyjnych). Po trzecie, wykorzystanie wcześniej napisanych i przetestowanych procedur i funkcji ułatwia uruchamianie programu (zakładając, że klocki zostały zrobione solidnie). Podejście modułowe wymusza również na programiście poprawne projektowanie elementów programu, co bez wątpienia przyniesie profity w przyszłości. Nie bez znaczenia jest też kwestia czasowa (raz skompilowany moduł nie kompiluje się ponownie). Wykorzystanie modułów daje wreszcie programistom możliwość rozpowszechniania kodu w postaci skompilowanych bibliotek, bez konieczności ujawniania tekstów źródłowych.

Drugą korzyścią z zastosowania struktury modułowej jest możliwość łatwego wykorzystania wcześniej stworzonych i przetestowanych funkcji i struktur danych (ang. reusability) oraz ułatwienie ich dystrybucji.

Jak już powiedzieliśmy, konieczność programowania modułowego przyjdzie z czasem - programy pisane przez Ciebie obecnie są nieco za krótkie. Dlatego też na razie ograniczymy się do przedstawienia ogólnych reguł tworzenia modułów i ich wykorzystania. Za przykład posłuży nam krótki moduł Test:

  unit Test;
   
 

interface { sekcja publiczna }

   
 

type

    float = real;
   
  function Pow(x, y : float) : float;
  { podnosi liczbę x do potęgi y }
   
  function IleWywolan : word;
  { zwraca liczbę wywołań funkcji Pow }
   
  implementation { sekcja prywatna }
   
  var
    Licznik : word; { licznik wywołań funkcji }
   
  function Pow(x, y : float) : float;
   
  begin
    Inc(Licznik);
    Pow := exp(y*ln(x));
  end;
   
  function IleWywolan : word;
   
  begin
    IleWywolan := Licznik;
  end;
   
  begin { sekcja inicjalizacji }
    Licznik := 0;
  end.

Moduł ten zawiera trzy zasadnicze sekcje: publiczną (interface), prywatną (implementation) oraz inicjalizacyjną.

Sekcja publiczna, otwierana słowem interface, definiuje obiekty, które będą "eksportowane" z modułu na zewnątrz. Zawiera on definicje stałych i typów oraz deklaracje zmiennych, a także tzw. deklaracje zapowiadające procedur i funkcji (czyli po prostu ich nagłówki). Sekcja publiczna stanowi jedyną bramę, przez którą program korzystający z modułu może dostać się do jego zawartości: wszelkie obiekty zdefiniowane wewnątrz modułu lecz nie zadeklarowane w sekcji publicznej, będą niewidoczne na zewnątrz. Warto również pamiętać, że komunikacja z modułem jest wyłącznie jednostronna, tj. obiekty zdefiniowane w module nie mogą odwoływać się do obiektów wykorzystującego go programu. Możliwe jest natomiast wykorzystanie w module innego modułu (w tym celu po nagłówku modułu należy umieścić odpowiednią deklarację uses).

Definicje obiektów zapowiedzianych w sekcji publicznej zawiera sekcja prywatna, otwierana słowem implementation. Jej zawartość jest niewidoczna na zewnątrz, co pozostaje w zgodzie z ideą ukrywania szczegółów implementacyjnych: obiekty prywatne wykorzystywane są najczęściej do wewnętrznych potrzeb modułu, tak więc nie ma sensu ujawnianie ich na zewnątrz. Możliwe jest co prawda odwoływanie się do funkcji i procedur zadeklarowanych w sekcji interface, ale "widoczność" sprowadza się tutaj wyłącznie do znajomości posłużenia się daną funkcją - wywołujący program musi znać jej nagłówek (określający sposób przekazania parametrów i odebrania wyniku), natomiast znajomość treści nie jest mu do niczego potrzebna.

Ostatnią sekcją modułu jest nieobowiązkowa sekcja inicjalizacji, rozpoczynająca się (podobnie, jak w zwykłym programie) słowem kluczowym begin. Instrukcje zawarte w sekcji inicjalizacji są wykonywane w chwili uruchomienia programu i zwykle wykorzystywane są (jak sama nazwa wskazuje) do automatycznego ustalania jego stanu początkowego (np. zapamiętywania kolorów ekranu w celu ich późniejszego odtworzenia, inicjalizacji drukarki, automatycznego ładowania plików konfiguracyjnych).

Całość modułu rozpoczyna się nagłówkiem o postaci unit nazwa (ang. unit - moduł) i kończy słowem kluczowym end (z kropką). Tu uwaga: treść modułu (kod źródłowy i wynikowy) zapamiętywana jest w oddzielnych plikach, których nazwy muszą być takie same, jak nazwa użyta w nagłówku (w naszym przypadku tekst źródłowy modułu należy umieścić w pliku TEST.PAS, zaś kod wynikowy zostanie zapisany do pliku TEST.TPU). Wymienione wyżej sekcje powinny występować w takiej kolejności, w jakiej zostały opisane, chociaż każdą z nich można pominąć. Pospolitym zjawiskiem jest pominięcie części inicjalizacyjnej (w chwili uruchomienia programu moduł nie wykonuje żadnych operacji). Pominięcie części prywatnej spotykane jest głównie w modułach deklarujących wyłącznie stałe, zmienne i typy. Brak części publicznej automatycznie uniemożliwia komunikację z modułem, toteż sytuacja taka spotykana jest niezwykle rzadko.

Wróćmy do naszego przykładu. Moduł Test zawiera w części publicznej deklarację dwóch funkcji: Pow, podnoszącej liczbę rzeczywistą do potęgi rzeczywistej, oraz (znacznie mniej użytecznej) IleWywolan, zwracającej liczbę wywołań funkcji Pow. Sekcja publiczna zawiera również definicję prostego typu zmiennoprzecinkowego float.

Sekcja prywatna zawiera oczywiście definicje funkcji zapowiedzianych w sekcji publicznej, jak również deklarację prywatnej zmiennej Licznik. Zmienna ta przechowuje liczbę wywołań funkcji Pow. Ponieważ Licznik jest niedostępny z zewnątrz (spróbuj!), jego wartość zwracana jest przez funkcję IleWywolan. Warto tu wspomnieć, że wszelkie globalne zmienne prywatne modułu są statyczne, tj. przechowują swoją wartość przez cały czas wykonywania programu.

Część inicjalizacyjna zawiera jedną instrukcję, inicjalizującą wartość zmiennej Licznik na zero. O sensowności tego rozwiązania nie trzeba chyba nikogo przekonywać.

Do sprawdzenia działania naszego modułu wykorzystamy krótki program TestModulu. Program ten jest na tyle banalny, że nie wymaga komentarza.

  program TestModulu;
   
  uses
    Test;
   
  var
    x : float; { wykorzystanie zdefiniowanego typu }
   
  begin
    writeln('2 do potegi 8 = ', Pow(2,8):8:4);
    x := 1.5;
    writeln('Pi do potegi 1.5 = ', Pow(Pi, x):8:4);
    writeln('Funkcje Pow wywolano ', IleWywolan, ' razy.');
  end.

Jak widać, wykorzystanie własnego modułu odbywa się dokładnie tak samo, jak standardowego modułu bibliotecznego. Jeśli przyjrzysz się uważnie raportowi z kompilacji zauważysz, że przed skompilowaniem samego programu Turbo Pascal wyświetli informację o kompilacji modułu. Po skompilowaniu programu na dysku powinien pojawić się plik o nazwie TEST.TPU, zawierający skompilowany kod wynikowy modułu. Odpowiednie fragmenty kodu są pobierane z pliku i dołączane do kodu wynikowego programu w procesie konsolidacji. Po utworzeniu pliku .TPU moduł nie będzie kompilowany ponownie, o ile nie wprowadzisz zmian do kodu źródłowego lub nie wydasz polecenia Build, wymuszającego bezwarunkowe skompilowanie wszystkich elementów składowych programu.

Jak powiedzieliśmy na początku tego rozdziału, zagadnienie modułów jest raczej "przyszłościowe", toteż nie będziemy się nim szerzej zajmować. Od samej strony technicznej (której opis możesz znaleźć w literaturze) ważniejsza wydaje się idea programowania (i projektowania) modułowego, pozwalająca na szybkie i skuteczne tworzenie efektownych, efektywnych i bezbłędnych programów.

Zapamiętaj

Poprzedni | Spis treści | Następny | Wersja spakowana |