Wstęp do programowania obiektowego w CA-Visual Objects

Marian Mysior

Wprowadzenie

Obiektem określamy zwykle wyodrębniony element otaczającego nas świata lub pewne pojęcie abstrakcyjne. Może to więc być cokolwiek, czemu potrafimy przypisać nazwę, np. rzecz, osoba, idea. Obiekt może oddziaływać na inne obiekty i może podlegać oddziaływaniu. Zbiór obiektów powiązanych interakcjami tworzy system. 
Modelowanie systemu na komputerze sprowadza się do opisania i zbudowania formalnego modelu tego systemu. Model ten jest wynikiem procesu abstrakcji tzn. umieszczenia w opisie systemu cech istotnych, a pominięcia mniej istotnych szczegółów. Model abstrakcyjny konstruuje się w ten sposób, aby istotne własności modelu i systemu rzeczywistego były takie same. 
W informatyce model systemu nazywamy obiektowym, jeżeli składa się on wyłącznie z obiektów programowych i ewentualnie sprzętowych. W języku programowania określenie obiekt odnosimy do pewnego identyfikowalnego fragmentu oprogramowania. Koncepcja obiektu wiąże się ściśle ze stosowaną w informatyce abstrakcją danych. 
Podstawową zmianą wprowadzoną przez programowanie obiektowe w stosunku do programowania strukturalnego, jest inne spojrzenie na kod programu. Pisząc program obiektowy (object oriented) uwagę zwracamy przede wszystkim na dane, na których chcemy wykonywać pewne działania, nie zaś na same działania (procedury). Programowanie obiektowe pozwala wiązać struktury danych z działaniami. Maksyma, że komputer służy do przetwarzania danych jest tutaj znacznie bliższa prawdzie. 
Świat zewnętrzny postrzegamy, w życiu codziennym, w sposób "obiektowy". Patrząc na telewizor zauważamy, że potrafi on przekazywać obraz i dźwięk. Zawiera przyciski i pokrętła regulacyjne. Niewiele osób wie natomiast, że telewizor składa się głowicy VHF i UHF, wzmacniaczy, detektora, generatorów odchylania itd. Są to podzespoły ukryte przed użytkownikiem, ale można nimi sterować (nie znając zasady działania i budowy) korzystając z przycisków i pokręteł. Tworząc program techniką obiektową postępujemy podobnie. Dajemy użytkownikowi działające obiekty, odziaływujące na siebie wzajemnie i "wyprowadzamy pokrętła" do modyfikowania ich własności. Programowanie proceduralne oferuje natomiast "zestaw podzespołów" i wymaga znajomości ich budowy aby prawidłowo regulować własnościami. 
Programowanie obiektowe pozwala tworzyć hierarchiczne struktury, gdzie nowe obiekty mogą dziedziczyć cechy przodków. Analogie, np. z biologii, nasuwają się same.
Obiekty, w sensie używanym w programowaniu obiektowym, zorganizowane są w klasy. Klasa jest wzorcem (szablonem) opisującym dane i dopuszczalne sposoby działania na tych danych. Zadeklarowana zmienna typu zdefiniowanej klasy zwana jest obiektem. Procedury i funkcje zdefiniowane w klasie (stanowiące jej integralną część) nazywane są metodami danej klasy i powinny (po prawidłowym zaprojektowaniu) być jedynymi funkcjami mogącymi manipulować danymi określonego obiektu. Każdy tworzony obiekt (zmienna typu wcześniej zdefiniowanej klasy) posiada własną kopię danych klasy, czyli różne obiekty mają swoje własne dane. Obiekty mogą wywoływać metody danej klasy, co często określa się jako wysyłanie komunikatów do obiektu. Komunikat wysłany do danego obiektu nie jest odbierany przez inne obiekty. Definiując nowe klasy możemy korzystać z dziedziczenia. Polega ono na tym, że nowo tworzona klasa przejmuje z klasy poprzedniej (rodzica) zarówno dane (własności), jak i metody. W nowej klasie możemy dodawać nowe własności, nowe metody i zmieniać działanie metod odziedziczonych. Fundamentalną rolę odgrywa tu zjawisko polimorfizmu. Nowa klasa może bezpośrednio dziedziczyć cechy jednej (w Turbo Pascalu, CA-Visual Objects) lub większej ilości innych klas (w C++). Struktura definiowania klas często kształtem przypomina drzewo. Klasa podstawowa (ta, z której dziedziczą inne) zwana jest bazową lub rodzicem, a klasy pochodne (te, które dziedziczą)- dziećmi. Po prawidłowym zaprojektowaniu i przetestowaniu klasy, możemy ją dalej używać tworząc kolejne klasy i wykorzystując techniki dziedziczenia. Nie musimy zmieniać zdefiniowanych już klas, lecz tworzymy klasy pochodne. Programowanie obiektowe polega na rozszerzaniu kodu, a nie na poprawianiu. Łatwiejsze staje się wtedy wyszukiwanie błędów i testowanie programu.

W naszych rozważaniach skoncentrujemy się na istocie budowania klas i filozofii programowania obiektowego. Programy nasze będziemy kompilować w trybie "emulacji terminala". CA-Visual Objects jest językiem "wizualno-obiektowym, a więc oferuje wiele gotowych narzędzi do budowy kodu bez konieczności ręcznego wpisywania oraz gotowe biblioteki obiektowe. Jest również językiem zorientowanym na tworzenie systemów zarządzania bazami danych. Zachęcam do własnych eksperymentów i do wykorzystywania tych możliwości. Język CA - Visual Objects jest zgodny z tzw. standardem XBase. Stosują go np. systemy dBase, Clipper, FoxPro. Ułatwieniem będzie więc podstawowa znajomość jednego z tych dialektów standardu XBase.

Klasy 

Klasa stanowi szablon, w którym definiujemy strukturę obiektu: dane i metody. Definicję klasy rozpoczynamy słowem kluczowym class i dalej nazwą klasy. Później deklarujemy zmienne (dane). Metody definiowane są poza definicją klasy. Wyjaśnijmy to na przykładzie:

class Towar
export Nazwa as string
export Cena as float

Powyższa klasa posiada 2 zmienne: pierwszą typu string (łańcuch) i drugą typu float (liczba rzeczywista). Specyfikator export przed nazwami zmiennych określa tzw. rodzaj dostępu do nich z różnych miejsc programu (tutaj - bez ograniczeń).
Aby wypróbować naszą klasę musimy zdefiniować funkcję Start():


function Start()
local oKomputer as Towar // deklaracja obiektu typu Towar
oKomputer := Towar{} // inicjowanie obiektu 
oKomputer:Nazwa := "Komputer PENTIUM -100"
// podstawienie wartości pod zmienną 
?oKomputer:Nazwa // wydruk wartości zmiennej
inkey(0) 


Definicje elementów (np. funkcji, procedur, klas) nie muszą kończyć się słowem kluczowym return (mogą). W CA-Visual Objects definicja kończy się w miejscu rozpoczęcia nowej. 
Dobrym zwyczajem jest rozpoczynanie nazw zmiennych od małej litery określającej typ zmiennej (np. "o" jak object).
Obiekt zostaje zainicjowany ("powołany do życia") w momencie podstawienia pod zmienną obiektową nazwy klasy obiektu zakończonej parą nawiasów klamrowych: {}. Zmienna obiektowa przechowuje adres obiektu. W CA-Visual Objects nie ma metod zwanych konstruktorami obiektów. W odróżnieniu od innych języków programowania (np.: C++, Pascal) - są niepotrzebne.





Inicjowanie zmiennych

Zmienne, deklarowane w definicji klasy, nazywamy inaczej zmiennymi instancyjnymi, danymi, atrybutami lub własnościami. Można je inicjować używając np. konstrukcji:
oKomputer:Nazwa := "Komputer PENTIUM -100"

Nie jest to jednak polecany sposób, ponieważ umożliwia swobodną manipulację atrybutami obiektu bez jakiejkolwiek kontroli. Jedną z podstawowych zasad programowania obiektowego jest ochrona danych przed "przypadkowymi" zmianami. Należy więc unikać bezpośredniego odwoływania się do atrybutów obiektów.
Dane początkowe można inicjować bezpośrednio w definicji klasy:


class Towar
export Nazwa := "Komputer" as string
export Cena := 2000 as float


Oczywiście nie zawsze ma to sens, ponieważ definicja klasy to tylko typ przyszłych obiektów, które mają taką samą strukturę, lecz mogą przechowywać różne wartości.
Inną możliwością jest zdefiniowanie metod (podprogramów) nadających wartości atrybutom obiektu. Nazywamy je akcesorami:


method SetNazwa( cNazwa ) class Towar // nagłówek metody
Nazwa := cNazwa
method SetCena( fCena ) class Towar
Cena := fCena


Słowo kluczowe method rozpoczyna definicję metody. Po nim występuje nazwa metody i ewentualne parametry w nawiasach okrągłych. Na końcu nagłówka metody podajemy słowo class i nazwę klasy do, do której należy nasza metoda. 
Końcem definicji metody jest miejsce rozpoczęcia definicji nowego elementu (np. następnej metody). 
Zdefiniujmy również metody (akcesory) zwracające wartości atrybutów obiektu (aby wyeliminować konstrukcję: ?oKomputer:Nazwa):


method GetNazwa() class Towar 
return Nazwa // zwrot wartości: Nazwa
method GetCena() class Towar
return Cena 


Program główny możemy teraz zapisać następująco:

function Start()
local oKomputer as Towar 
oKomputer := Towar{} 
oKomputer:SetNazwa("Komputer PENTIUM -100")
?oKomputer:GetNazwa() 
inkey(0) 


W CA-Visual Objects istnieje szczególna metoda, o nazwie Init(), która (o ile jest zdefiniowana), wywoływana jest automatycznie w chwili inicjowania obiektu. Można wykorzystać ją do nadawania wartości początkowych atrybutom obiektu (a także do wykonania innych czynności związanych z powołaniem obiektu, np.: otwarcia pliku).
Zastosujmy ją w naszym przykładzie:


method Init(cNazwa, fCena ) class Towar
Nazwa := cNazwa
Cena := fCena


Metody tej nie musimy wywoływać "ręcznie", ale należy przekazać jej parametry aktualne. Robimy to, wpisując parametry w nawiasach klamrowych, podczas inicjowania obiektu:


function Start()
local oKomputer as Towar 
oKomputer := Towar{ "Komputer PENTIUM -100", 2000 } 
?oKomputer:GetNazwa() 
inkey(0) 



Dziedziczenie 

Podstawową cechą programowania obiektowego jest możliwość dziedziczenia własności i metod zdefiniowanych klas. Wykorzystując dziedziczenie można rozbudowywać kod programu zamiast poprawiać go. Dobrze zaprojektowane klasy nie wymagają poprawek, aby wykorzystywać je wielokrotnie. Programując w stylu obiektowym definiujemy klasy pochodne, dziedziczące własności i metody swoich przodków.
Zdefiniujmy klasę dziedziczącą z naszej klasy Towar:

class Towar_z_VAT inherit Towar // nagłówek def. klasy dziedziczącej export VAT as byte

Nowa klasa: Towar_z_VAT dziedziczy z klasy Towar wszystkie jej atrybuty i metody oraz dodaje własny atrybut: VAT. Odziedziczone elementy klasy bazowej można używać w klasie pochodnej tak jakby były w niej zdefiniowane. W klasie pochodnej można definiować nowe metody lub zastępować odziedziczone z klasy bazowej. Zastępowanie odziedziczonych metod polega na zdefiniowaniu w klasie pochodnej metod o takich samych nazwach lecz o innej implementacji. Metody takie przesłaniają swoje odpowiedniki z klasy bazowej. Czasami zachodzi potrzeba tylko uzupełnienia działania metody w klasie pochodnej. Rozważmy przykład redefinicji metody Init():


method Init(cNazwa, fCena, bVAT ) class Towar_z_VAT
super:Init(cNazwa, fCena) // wywołanie metody z nadklasy
VAT := bVAT


W ciele tej metody wywołujemy metodę Init(cNazwa, fCena) z klasy bazowej - inaczej z nadklasy. Automatycznie tworzona zmienna super zawiera referencję (adres) do nadklasy. Wypróbujmy nasz przykład:


method GetVAT() class Towar_z_VAT
return VAT 

function Start()
local oKomputer as Towar_zVAT 
oKomputer:=Towar_z_VAT{"Komputer PENTIUM -100", 2000, 22} 
?oKomputer:GetNazwa()
?oKomputer:GetCena() 
?oKomputer:GetVAT() 
inkey(0) 



Hermetyzacja

Definiując metody zapewniające dostęp do atrybutów obiektu eliminujemy konieczność bezpośredniego odwoływania się do nich. Pełna ochrona danych obiektu wymaga jednak podjęcia kroków zabraniających bezpośrednich odwołań do danych z poza metod obiektu lub wprowadzenia kontroli poprawności takich odwołań. CA-Visual Objects posiada bogaty zestaw narzędzi służących do ukrycia danych przed niepożądanym dostępem. Działania takie nazywamy hermetyzacją lub enkapsulacją danych. 
Definiując klasę Towar do deklaracji zmiennych używaliśmy słowa export. Jest to jeden z czterech specyfikatorów określających poziom dostępu do zmiennych instancyjnych. Atrybuty typu export (eksportowalne) nie są w żaden sposób chronione. Można je zmieniać z różnych miejsc w programie używając konstrukcji: obiekt:nazwa := wartość.
Aby zabezpieczyć zmienne instancyjne przed takimi modyfikacjami, używamy specyfikatora hidden (ukryte). Spójrzmy na nasz przykład:


class Towar
hidden Nazwa as string // deklaracja zmiennej ukrytej
hidden Cena as float // j.w.

method SetNazwa( cNazwa ) class Towar 
Nazwa := cNazwa
method SetCena( fCena ) class Towar
Cena := fCena

method GetNazwa() class Towar 
return Nazwa 
method GetCena() class Towar
return Cena 

function Start()
local oKomputer as Towar 
oKomputer := Towar{} 
oKomputer:Nazwa := "Komputer PENTIUM -100" 
// błąd - brak dostępu do zmiennej
?oKomputer:Nazwa // błąd j.w.
oKomputer:SetNazwa("Komputer PENTIUM -100")
// prawidłowa konstrukcja
?oKomputer:GetNazwa() // j.w.
inkey(0) 


Atrybuty ukryte nie są dostępne dla kodu znajdującego się poza klasą. Zmienne te widoczne są tylko dla metod klasy, w której zostały zdefiniowane. Nie są dostępne nawet dla metod zdefiniowanych w klasach dziedziczących.
Chcąc uzyskać dostęp do zmiennych odziedziczonych z nadklasy należy zastosować specyfikator protect (zabezpieczone):


class Towar
protect Nazwa as string // deklaracja zmiennej zabezpieczonej
protect Cena as float // j.w.


Specyfikatory hidden i protect ograniczają dostęp do danych obiektu, ale nie chronią przed wprowadzaniem nieodpowiednich wartości przez uprawnione metody. Chcąc zapewnić taką ochronę należy zmodyfikować akcesory zmieniające wartości atrybutów, np. w następujący sposób:


method SetNazwa( cNazwa ) class Towar 
if IsString (cNazwa) // sprawdza, czy wprowadzana 
Nazwa := cNazwa // wartość jest łańcuchem
endif

method SetCena( fCena ) class Towar
if ValType( fCena ) == "N" .and. Between( fCena, 0, 10000)
// sprawdza, czy wprowadzana wartość jest typem numerycznym 
// i czy mieści się w przedziale między 0 a 10000 
Cena := fCena
endif


Język CA-Visual Objects umożliwia używanie ujednoliconych nazw akcesorów, zamiast stosowania nazw np. SetCena() i GetCena(). Aby je zastosować należy przed nazwą akcesora użyć słów kluczowych: assign lub access zamiast method. Dodatkowo umożliwiają one stosowanie prostej składni w wywołaniach, takiej jak dla zmiennych, zamiast bardziej złożonej przeznaczonej dla metod:


class Towar
protect Nazwa as string 
protect Cena as float 

assign Nazwa( cNazwa ) class Towar // akcesor zmieniający atrybut 
if IsString (cNazwa) 
Nazwa := cNazwa 
endif

assign Cena( fCena ) class Towar 
if ValType( fCena ) == "N" .and. Between( fCena, 0, 10000)
Cena := fCena
endif

access Nazwa() class Towar // akcesor podający wartość atrybutu
return Nazwa 
access Cena() class Towar
return Cena 

function Start()
local oKomputer as Towar 
oKomputer := Towar{} 
oKomputer:Nazwa := "Komputer PENTIUM -100" // składnia jak dla 
oKomputer:Cena := 2000 // zmiennych
?oKomputer:Nazwa 
?oKomputer:Cena
inkey(0) 


Zwróćmy uwagę na składnię użycia zmiennych Nazwa i Cena. Są to atrybuty chronione (protect), a mino to mamy dostęp do nich w funkcji zewnętrznej Start(). Udostępniane są w rzeczywistości nie atrybuty, lecz wywoływane akcesory o takich samych nazwach (składnia - jak dla atrybutów!). Aby przekonać się o słuszności tego stwierdzenia zróbmy drobną (tymczasową) modyfikację. Zmieńmy w nagłówkach metod assign i access oraz w funkcji Start()(tylko tam!) nazwę: Nazwa na abc:


.......
assign abc( cNazwa ) class Towar
......
access abc() class Towar
......
function Start()
...... 
oKomputer:abc := "Komputer PENTIUM -100" 
......
?oKomputer:abc 
...... 


Program dalej działa, mimo że odwołujemy się do nieistniejącego atrybutu abc! Tak jest tylko pozornie. Odwołanie: oKomputer:abc jest odwołaniem do akcesora abc(). Takie używanie akcesorów sugeruje jednak, że odwołujemy się do nieistniejących zmiennych obiektu. Określa się je więc jako zmienne wirtualne. Pozwalają one budować synonimy atrybutów i zmienne wyliczane. Hermetyzacja danych obiektu staje się wtedy pełniejsza. Użytkownik klasy "nie dostrzega" rzeczywistych zmiennych. Zbudujmy zmienną wyliczaną: 


access CenaBrutto() class Towar_z_VAT
return Cena * ( 1 + float(VAT)/100 ) // float(Vat)-konwersja do typu float
......


Atrybut CenaBrutto nie istnieje, chociaż możemy odwołać się do niego: oKomputer:CenaBrutto. 
Zmienne wyliczane nie zajmują przestrzeni klasy przeznaczonej do przechowywania danych.
W naszym akcesorze CenaBrutto() nastąpiło jednak bezpośrednie odwołanie do atrybutów Cena i VAT, ponieważ jest on metodą klasy, której składnikami są te atrybuty. Jeżeli zmienne obiektu dostępne są w danym podprogramie, przy wywołaniu używane są bezpośrednio atrybuty, zamiast akcesorów. Aby odwołanie następowało zawsze poprzez akcesory Cena() i VAT() należy użyć specyfikatora dostępu instance zamiast protect:


class Towar
protect Nazwa as string 
instance Cena as float 

class Towar_z_VAT inherit Towar 
instance VAT as byte


Specyfikator instance wymusza użycie akcesorów access i assign. Atrybut z tym specyfikatorem nie jest dostępny poza definicją akcesora. Stwarza to możliwość budowania zmiennych obiektu tylko do odczytu (definiujemy jedynie akcesor access) lub tylko do zapisu (akcesor assign).
Atrybut VAT będzie tylko do odczytu jeżeli zdefiniujemy akcesor access (bez assign):

access VAT() class Towar_z_VAT
return VAT


Szczególny sposób dostępu do atrybutów z specyfikatorem instance jest konsekwencją tzw. późnego wiązania tych zmiennych. Ich adresy nie są znane podczas kompilacji. Odczytywane są z odpowiednich tablic dopiero w czasie wykonania programu. Dostęp do nich jest niestety wolniejszy niż do zmiennych wiązanych w sposób wczesny (podczas kompilacji).

Podsumujmy naszą wiedzę o specyfikatorach dostępu do atrybutów obiektu:
· export - dostęp bez ograniczeń z dowolnej funkcji (procedury);
· hidden - atrybut dostępny tylko dla metod danej klasy; 
· protect - atrybut dostępny dla metod klasy, w której został zdefiniowany i dla metod klas pochodnych; 
· instance - dostęp tylko za pośrednictwem akcesorów access i assign. 


Metody polimorficzne

Polimorfizm, czyli wielopostaciowość jest zjawiskiem charakterysty-cznym gdy w klasach pochodnych definiujemy metody o takich samych nazwach. Redefinicje potrzebne są jeżeli chcemy zmienić sposób działania metody. Metoda o takiej samej nazwie w klasie pochodnej przesłania metodę z klasy bazowej. Dostęp do metody z klasy bazowej jest jednak możliwy, jeżeli poprzedzimy ją podczas wywołania kwalifikatorem super.
Jednoznaczność powyższa nie jest tak oczywista, gdy niektóre metody klasy bazowej nie mają swoich redefinicji w klasie pochodnej (nie zawsze jest to potrzebne), a jednocześnie wywołują inne metody. Rozpatrzmy przykład.
Zdefiniujmy metodę drukującą atrybuty klasy Towar:


method Drukuj() class Towar
?"Nazwa: " + Nazwa
?"Cena: "; ??Cena 


W klasie Towar_z_VAT tworzymy redefinicję metody Drukuj(), aby uzupełnić ją o atrybut VAT:


method Drukuj() class Towar_z_VAT 
super:Drukuj() // wywołanie metody z nadklasy
?"VAT: "; ??VAT 


W klasie Towar definiujemy metodę DrukujAtrybuty() wywołującą usługę Drukuj():


method DrukujAtrybuty() class Towar
?"Atrybuty klasy :" 
self:Drukuj() // self - kwalifikator będący odwołaniem do obiektu, 
// do którego należy metoda


Treść metody DrukujAtrybuty() jest na tyle uniwersalna, że nie wymaga redefinicji w klasie Towar_z_VAT . W naszym programie wywołujemy ją w funkcji Start():
oKomputer:DrukujAtrybuty()

Która implementacja metody Drukuj() zostanie użyta? Przeanalizujmy to. 
W czasie kompilacji wywołania funkcji, procedur i zmiennych zostają zastąpione adresami. Ponieważ metoda DrukujAtrybuty() należy do klasy Towar, wywołanie self:Drukuj() powinno powodować wstawienie adresu tej metody z klasy Towar. W efekcie otrzymujemy niepełne dane na temat atrybutów klasy Towar_z_VAT. Należałoby dokonać redefinicji identycznej metody DrukujAtrybuty() w klasie Towar_z_VAT, aby program działał poprawnie. Tak jednak nie jest! Powyższe rozumowanie słuszne jest dla funkcji, procedur i zmiennych o tzw. wczesnym wiązaniu. W CA - Visual Objects wszystkie metody są późno wiązane. Oznacza to, że kompilator nie wstawia adresów metod do kodu aplikacji, lecz odwołania do odpowiednich tablic tych metod, przeszukiwanych dopiero w czasie działania programu. Metody lokalizowane w ten sposób nazywamy wirtualnymi bądź polimorficznymi. W przeciwieństwie do niektórych innych języków programowania w CA - Visual Objects wszystkie metody są wirtualne. W naszym przykładzie mimo braku redefinicji metody DrukujAtrybuty() w klasie Towar_z_VAT metoda Drukuj() pobierana jest z tej klasy.
Pisząc definicje metod nie określaliśmy typów zwracanych wartości i typów parametrów. Jest to konsekwencją późnego wiązania tych elementów klasy. Metody w CA - Visual Objects mogą mieć zmienną liczbę polimorficznych parametrów i polimorficzny typ zwracanej wartości. 


Destruktory

Obiekty są konstrukcjami powoływanymi i usuwanymi z pamięci operacyjnej podczas działania programu. Powołanie do życia obiektu wiąże się z zajęciem pamięci potrzebnej do przechowania jego struktury, a czasem również z dynamiczną alokacją pamięci, z wykonaniem takich czynności jak otwarcie plików itp. Kończąc wykorzystywanie obiektu należy odzyskać zajętą przez niego pamięć, pozamykać otwarte pliki, oraz cofnąć ewentualne inne zmiany. System CA - Visual Objects automatycznie zarządza odzyskiwaniem pamięci przeznaczonej na przechowanie struktury obiektu i na wskazania innych zmiennych będących referencjami do jakiegoś obszaru pamięci. Ingerencja programisty nie jest więc potrzebna dla większości klas.
W niektórych przypadkach trzeba jednak cofnąć pozostałe efekty. Służą do tego metody zwane destruktorami. W bibliotekach CA - Visual Objects noszą one nazwę Destroy(). Nie jest to jednak nazwa obowiązkowa i w klasach definiowanych przez programistę można nadawać im inne nazwy. Definiując własny destruktor można wykorzystać standardową metodę o nazwie Axit(). Jest to metoda wywoływana przez regenerator pamięci przy usuwaniu obiektu. Należy zarejestrować ją przy powoływaniu obiektu (najlepiej w metodzie Init()).
Służy do tego funkcja RegisterAxit(). Metoda Axit() wywoływana jest automatycznie, lecz może zajść konieczność "ręcznego" użycia jej. Regenerator pamięci działa podczas pracy programu, powodując okresowe odzyskiwanie niewykorzystywanych obszarów pamięci operacyjnej. Czas pomiędzy zakończeniem "życia" obiektu, a fizycznym odzyskaniem zajmowanej przez niego pamięci przez regenerator może być zbyt długi, jeżeli obiekt wykorzystywał duże obszary pamięci. Wywołując jawnie metodę Axit() należy zabezpieczyć ją przed powtórnym wywołaniem przez regenerator pamięci. Można odinstalować ją używając funkcji UnregisterAxit(). Najlepszym miejscem na wywołanie jej jest sama metoda Axit(). Trzeba zadbać również, aby użycie funkcji UnregisterAxit() nie nastąpiło w trakcie działania regeneratora pamięci. Do sprawdzenia tego służy funkcja InCollect().


Inne aspekty programowania obiektowego w CA-VO

Język CA - Visual Objects umożliwia tzw. przeciążanie operatorów, czyli przypisywanie niektórym operatorom arytmetycznym możliwości działań na innych argumentach niż standardowe, np.: dodawanie, odejmowanie, mnożenie, dzielenie obiektów. W opcjach kompilacji należy zaznaczyć wtedy: Operator Methods. 
Późne wiązanie metod i zmiennych typu instance pociąga za sobą pewne konsekwencje. Kompilator nie może wykryć błędów polegających na braku definicji tych metod i zmiennych. Błędy tego typu uwidoczniają się dopiero podczas wykonania programu. Możliwe jest jednak przejęcie przez programistę obsługi tych błędów, bez powodowania przerwania działania programu. Można zdefiniować metodę o zastrzeżonej nazwie NoMethod() przejmującą działanie w przypadku odwołania do nieistniejącej metody. Podobnie można użyć metody NoIVarGet() wywoływanej w przypadku odwołania do zmiennej wirtualnej, dla której nie jest zdefiniowana metoda access oraz metody NoIVarPut() w przypadku braku metody assign.
Późne wiązania w CA-Visual Objects pozwalają uzyskiwać wiele informacji o klasach i zmiennych wirtualnych w czasie działania programu. Służą do tego takie funkcje jak: IVarListClass(), IVarGetInfo(), IVarPutInfo(), ClassTree(), ClassTreeClass(), OOPTree() i inne.


Podsumowanie

Omówiliśmy podstawowe cechy programowania obiektowego przy użyciu języka CA - Visual Objects. Wstęp ten miał na celu wprowadzenie w filozofię techniki tworzenia i operowania obiektami. Pominęliśmy wiele ważnych i ciekawych aspektów tego stylu programowania. Forma i objętość tej pracy nie pozwoliła na to. Niemniej, jeżeli zachęciłem do dalszych własnych poszukiwań - cel został osiągnięty. 
Analizując nasz przykładowy program można odnieść wrażenie, że programowanie obiektowe mocno skomplikowało proste zagadnienie. Jest tak rzeczywiście, jeżeli tworzymy tak krótki program. Styl obiektowy programowania zaczyna "opłacać" się przy stosunkowo dużych kodach źródłowych (rzędu kilku tysięcy linii i więcej), przy zespołowej pracy nad kodem i tworzeniu tzw. bibliotek. Obecnie stał się standardem programowania. Prawie wszystkie współczesne języki programowania implementują filozofię obiektową. Nawet systemy użytkowe (np. MS Office) wymagają od użytkownika myślenia "obiektowego". Obiektowymi stały się systemy operacyjne (Windows, OS/2). W języku CA - Visual Objects (a także w innych) istnieją biblioteki obiektowe, czyli gotowe do użycia zestawy klas (np. GUI Classes, DBF Classes, SQL Classes).
Filozofia programowania obiektowego jest czymś więcej, niż sposobem zapisu kodu i chociażby tylko z tego powodu warta jest poznawania.


Literatura

· Śliwa L., Wolny M.: Wprowadzenie do programowania w CA - Visual 
Objects™, CLM Soft, Warszawa 1995.
· Struzińska-Walczak A., Walczak K.: CA - Visual Objects. Programowanie wizualno-obiektowe, W&W, Warszawa 1995.


Dodatek

Końcowa wersja kodu źródłowego naszego programu:


function Start()
local oKomputer as Towar_z_VAT 
oKomputer := Towar_z_VAT{ "Komputer PENTIUM -100", 2000, 22 } 
oKomputer:Nazwa := "Komputer DX4 - 100" 
oKomputer:Cena := -5
oKomputer:DrukujAtrybuty()
?
?"Zmienna wyliczana:"
?"CenaBrutto:"
??oKomputer:CenaBrutto 
inkey(0) 

class Towar
protect Nazwa as string
instance Cena as float

class Towar_z_VAT inherit Towar 
instance VAT as byte

access Cena() class Towar
return Cena 

assign Cena( fCena ) class Towar
if ValType( fCena ) == "N" .and. Between( fCena, 0, 10000)
Cena := fCena
endif

method Drukuj() class Towar
?"Nazwa: " + Nazwa
?"Cena: "; ??Cena 

method DrukujAtrybuty() class Towar
?"Atrybuty klasy :" 
self:Drukuj()


method Init(cNazwa, fCena ) class Towar
Nazwa := cNazwa
Cena := fCena

access Nazwa() class Towar 
return Nazwa 

assign Nazwa( cNazwa ) class Towar 
if IsString (cNazwa)
Nazwa := cNazwa
endif

access CenaBrutto() class Towar_z_VAT
return Cena * ( 1+float(VAT)/100)

method Drukuj() class Towar_z_VAT 
super:Drukuj()
?"VAT: "; ??VAT 

method Init(cNazwa, fCena, bVAT ) class Towar_z_VAT
super:Init(cNazwa, fCena) 
VAT := bVAT

access VAT() class Towar_z_VAT
return VAT