Intuicyjne podstawy programowania obiektowego
(wftg 11XI2K5)
Cel.
Opracowanie to dotyczy programowania obiektowego (OOP - Object Oriented Programming) wyjaśniając podejście obiektowe implementowane przez takie języki jak C++, Java, Visual Basic, C#, J++. Inne trochę podejście formalne prezentowane jest przez język Smalltalk (będący prekursorem języków obiektowych), czy języki skryptowe jak JavaScript - języki te nie posługują się ścisłym typowaniem zmiennych, co ma wpływ m. in. na ich większą elastyczność ale i mniejszą efektywność i trudniejszą kontrolę poprawności programów.
Powodem napisania tego tekstu jest próba wyjaśnienia jaką rolę odgrywa i jakie środki oferuje programowanie obiektowe w projektowaniu systemów przetwarzania - a nawet ogólniej: złożonych systemów informatycznych.
Obiektowym językiem, który służy tu jako podstawa wyjaśnień jest język Visual Basic .NET. Jako programista, który programował i uczył języka C++ i Javy (w mniejszym stopniu), mogę z pełnym przekonaniem powiedzieć, że obecna wersja VB, czyli .NET posiada środki, które w pełni pozwalają implementować systemy w sposób obiektowy.
Opracowanie jest skierowane przede wszystkim do ludzi, którzy nie posiadają intuicji związanych z OOP oraz z projektowaniem systemów i ma być wprowadzeniem do tej tematyki. Niektórych czytelników może zrazić niekiedy „łopatologiczne” podejście. Moje wieloletnie doświadczenie pedagogiczne pokazuje jednak, że nawet studenci uważający się za programistów, bystrzy w żonglowaniu instrukcjami, algorytmami, „z nerwem programowania” , w nikłym stopniu i z oporami przyswajają sobie techniki OOP oraz cel ich stosowania. Częściowo wynika to z faktu, że techniki OOP są prezentowane na prostych przykładach, co ukazuje ich mechanizmy ale nie oddaje istoty roli jaka pełnią w złożonych systemach. A trzeba powiedzieć, że dopiero złożony system ukazuje celowość i niezbędność obiektowego analizowania, projektowania i programowania. W wielu innych przypadkach można sobie skrobnąć kilka instrukcji (albo wielokilometrowe spagetti z instrukcji) i z zadowoleniem stwierdzić, ze program zrobił to co chcieliśmy. Moim celem jest m. in. pokazanie, że nawet najprostszy program, w środowisku obiektowym, powinien świadomie korzystać z technik obiektowych. Można nadal programować „intuicyjnie”, gdyż mechanizmy oprogramowania nie mogą „zostawić na lodzie” domorosłych programistów i starych wyjadaczy, ale w przyszłości środki ukrywające obiektową postać programu ulegną naturalnemu zanikowi - programowanie obiektowe będzie ,jak oddychanie, naturalnym sposobem programowania.
Zmienne w programowaniu nieobiektowym, pamięć operacyjna.
Trzeba zacząć od tego, że jakby nie próbować programować: obiektowo, nieobiektowo, strukturalnie, modularnie, nie ma ucieczki od podstawowych instrukji. Zawsze wykonawczą częścią programu będą sekwencje instrukcji:
przypisania,
wyrażenia numeryczne w instrukcjach,
instrukcje blokowe:
warunkowe typu If ... Then ...Else ... End If,
pętle programowe (iteracje) typu While … End While, For … Next,
czy użycia funkcji i wywołania procedur
zawsze zdefiniowane w blokach Function i Sub.
W systemie całkowicie obiektowym instrukcje te występują w definicjach funkcyj(!) i procedur (tak jak i w programowaniu nieobiektowym), a te są zawsze zawarte w definicji klasy, czyli we wzorcu obiektu. (Tu jak i w innych miejscach celowo wprowadzam uproszczenia: wiem, wiem, istnieje Module ... End Module a to nie klasa ... ale pokłon złożony nieobiektowej historii :)
Instrukcje zazwyczaj coś robią z danymi, czyli informacjami w postaci liczb, tekstów, tablic liczb itp. - przetwarzają dane. Dane te są zawsze zawarte w zmiennych (chyba że danymi są stałe ). Bardzo często początkujący programista traktuje zmienne w programie jak zmienne matematyczne, jako nazwy reprezentujące wartości jakiegoś typu. Można z tym żyć bardzo długo, np. jeśli nie programuje się obiektowo, wykonuje obliczenia numeryczne i nie programuje się w języku C. Takie spojrzenie bierze się często stąd, że programu nie traktuje się jako PRZEDMIOTU MATERIALNEGO i tu jest pies pogrzebany. Program pisze się po to by wykonywał się REALNIE „na komputerze” a to znaczy, że w komputerze program jest materialny (zapisany w pamięci operacyjnej ciąg bajtów, ciąg elektronicznych sygnałów). W bardzo niewielu sytuacjach można abstrahować od materialności programu i jego danych. Dla programisty najbardziej materialnym światem jest pamięć operacyjna komputera: tam w postaci ciągów bajtów leżą programy i dane. Zmienną możemy zdefiniować jako zestaw (cech?/informacji?):
Nazwa zmiennej
Adres (pierwszego bajtu) obszaru zmiennej (pamięć operacyjna ma od 0 PONUMEROWANE [stąd adres] wszystkie bajty i może być traktowana jako tablica bajtów)
Długość w bajtach obszaru zmiennej
Interpretacji zmiennej (typu jaki jest związany ze zmienną: Integer, Double ...)
Zawartości bitowej zmiennej (niezinterpretowany ciąg bitów, który po zastosowaniu p.4 staje się wartością jaką widzi programista.)
Gdy program (instrukcje maszynowe programu, tzw. kod maszynowy) korzysta ze zmiennej wykonując odczyt lub zapis wartości z/do zmiennej, nie używa nazwy <zapomnijmy o pamięci asocjacyjnej > z pkt.1 (która za to dla programisty piszącego program jest podstawą identyfikacji) ale posługuje się adresem pobierając/wpisując zawartość zmiennej. Zmienna w pamięci to pkt. 2 i pkt 5, Prawidłowe użycie wartości (czyli pkt. 3 i pkt 4) są już częścią programu (odpowiednie instrukcje zapisu, odczytu, przetwarzania). Gdy programuje się na tzw. niskim poziomie, np. procedury systemu operacyjnego, czy programy graficzne przetwarzające obraz, programista manipuluje zmienną w pkt. od 1. do 5. Programista użytkowy ma dostęp najczęściej do pkt. 1 a zaawansowany do pkt. 2. I tu pojawia się związek między pkt.1 i 2. Pisząc program używamy nazw, które w programie wykonywalnym stają się adresami. Uogólniając pojęcie adresu w językach obiektowych wprowadza się pojęcie referencji, którego nie rozumie niestety wielu programistów C/C++. Oni traktują referencję tylko jako adres. Tymczasem referencja to informacja ogólniejsza „jak dotrzeć” do zmiennej. Czasem wystarczy adres, a czasem może to być zestaw złożonych informacji, kórych interpretacja doprowadzi do zmiennej. Przykład: adres to „Lubicka 156”. Referencja to może być ten adres, ale może być też opis „dom naprzeciw końcowego przystanku 19-stki”. Trzeba przyznać, że na poziomie programu VB .NET referencja to rzeczywiście adres, ale może to ulec zmianie wraz z rozwojem systemów rozproszonych.
Upraszczając, programista powinien traktować zmienną jako nazwane pudełko na wartości umieszczone w wielkim pudle jaką jest pamięć operacyjna. Jeżeli zmienna ta zamiast zawierać wartość liczbową czy inną wartość elementarną zawiera referencję (adres? )do innej zmiennej), to należy myśleć że wartością w niej jest wędka, na której jest zaczepione inne pudełko. Taka interpretacja wystarcza, żeby prawidłowo! rozumieć zmienną na poziomie elementarnym. Przykłady:
Dim v as Integer (robocze/nietrwałe pudełko na 4-ro bajtowe liczby całkowite umieszczone w procedurze lub funkcji)
Static x as Double (trwałe pudełko na 8-mio bajtowe liczby zmiennoprzecinkowe umieszczone w procedurze lub funkcji)
Private y as Button (prywatne pudełko na wędki typu Przycisk umieszczone w obiekcie)
W przykładach 1. i 2. pudełko zawiera liczbę 0. W przypadku 3. zawiera informację, że jest puste - nie wstawiono jeszcze żadnej wędki do konkretnego Przycisku.
Pamięć operacyjna, którą widzi program nazywa się przestrzenią adresową (ściślej: technicznie to zdolność adresowania procesora). Jeżeli to jest program w komputerze z prostym systemem operacyjnym, to przestrzeń adresowa pokrywa się z pamięcią fizyczną komputera. W wieloprocesowym systemie operacyjnym środki sprzętowe (hardware) komputera wraz z oprogramowaniem systemowym komputera symulują wobec programu, że dysponuje pełna przestrzenią adresową. Oznacza to, że programista programuje tak, jakby dysponował całym komputerem dla swojego programu, gdy chodzi o pamięć operacyjną. Programy są wtedy wzajemnie izolowane i każdy myśli, że komputer, w zakresie dostępnej pamięci jest jego. Nie mogą się nawzajem zakłócić, a konkretny i ten sam adres w różnej przestrzeni jest naprawdę odwzorowany w inny adres fizyczny, czyli adresy w programie są wirtualne. To nie ma powinno mieć wpływu na programistę: dla niego pamięć jest rzeczywista, choć przemapowana na różne adresy pamięci fizycznej (tu widać, że adres, to adres wirtualny, a więc tak naprawdę omawiany wcześniej adres zmiennej to złożona, sprzętowo-programowa referencja!!!). Powyższa interpretacja pamięci przyda się przy omawianiu różnych aspektów programowania w środowisku VB .NET .
Zmienne, zdefiniowane i używane w programie można podzielić na kategorie zależnie od pełnionej przez nie roli. Najprostszą kategorią są zmienne tymczasowe, robocze, przechowujące na chwilę wartości pośrednich obliczeń. Inne zmienne przechowują dane przez długi czas i często grają rolę zmiennych stanu - opisują stan jakiegoś zjawiska, przechowują informacje trwałe, inne zmienne służą jako skrzynki kontaktowe - przekazują dane i wyniki między funkcjami. Większą część zmiennych tworzy programista. Mniejszą -środowisko programowe języka VB, np. nienazwane zmienne robocze, które służą jako pudełka do obliczeń pośrednich albo pudełka do przekazywania parametrów aktualnych między funkcjami i procedurami. Wiele zmiennych jest tworzonych na chwilę, a potem pamięć jest zwalniana (np. zmienne deklarowane jako Dim zawsze tworzone są przy starcie funkcji/procedury i kasowane podczas jej zakończenia). Nie było by wygodne lokować w pamięci zmienne chwilowe z długotrwałymi, bo robiłyby się zbyt często „małe dziury” - zwolnione obszary po zmiennych - defragmentacja pamięci. Praktyka spowodowała, że w językach programowania przestrzeń zmiennych programu została podzielona na trzy części: statyczną (static), stertę (heap) i stos (stack). Robocze i chwilowe zmienne służące tymczasowo wywołaniom procedur i funkcji umieszczane są na stosie (nazwa wynika ze sposobu umieszczania i zdejmowania danych zawsze od góry), trwałe zmienne umieszcza się w pamięci statycznej (tam także umieszcza się kod procedur i funkcji programu), natomiast długotrwałe zmienne ORAZ OBIEKTY umieszcza się na stercie, w której niestety nie udaje się uniknąć „dziur”. Z tego powodu w środowisku programowania VB istnieje program, który co pewien czas komasuje zmienne na stercie, żeby wolna część stanowiła spójny kawałek. Program ten to Garbage Collector - odśmieczacz. Podobna polityka obsługi pamięci dotyczy też Javy, J++, C#. Natomiast w C++ obiekty mogą być lokowane w każdej z tych 3. części pamięci.
Klasy jako elementy konstrukcyjne projektu.
Programowanie obiektowe stało się wyznacznikiem poprawnego stylu programowania, po czasach, gdy modne było programowanie modularne, a wcześniej jeszcze był styl programowania strukturalnego. Każde kolejne podejście wchłaniało idee poprzednich technologii programowania. Programowanie obiektowe jest techniką najlepszą, ponieważ łatwo wpisuje się w proces analizy systemów informatycznych oraz projektowania systemów informatycznych. Słowem „kluczem” w analizie jest modelowanie. Analizując istniejący system przedsiębiorstwa, by zaprojektować a potem stworzyć nowy system, tworzymy MODELE tych systemów. System składa się z podsystemów, podsystemy współpracują ze sobą, tworzy się sieć relacji. W jakich kategoriach ujmować sieć tych relacji, żeby spojrzenie to nie bazowało na środkach technicznych, czy technologiach projektowych, które w nienaturalny sposób narzucą swoją konstrukcją rozumienie budowanego systemu. Inaczej mówiąc - spojrzenie czysto funkcjonalne, które jest wynikiem programowania klasycznego, ujmuje system jako moduły funkcjonalne. A lepiej jest modelować system w sposób naturalny. Nasz świat jest światem obiektów, które rywalizują, współpracują ze sobą, wchodzą w różne interakcje. Nie są to interakcje jakie łatwo i jasno jest przestawić w formie sekwencji działań zewnętrznych w stosunku do, traktowanych jako bierne, części systemu. W projektowaniu takiej sekwencji zatracamy spojrzenie na całość. Tymczasem system to złożony obiekt współdziałający z otoczeniem będącym światem innych obiektów, sam złożony z wielu obiektów współdziałających, które znowu są strukturą złożoną z obiektów niższego rzędu. Naturalne jest widzieć wzajemne relacje między częściami systemu jako współpracę niezależnych obiektów, a nie jako przepis jak jeden kawałek programu przesyła informacje do drugiego kawałka systemu w takiej a nie innej kolejności. I to jest właśnie programowanie obiektowe. Środek, który pozwala modelować świat. Pozwala widzieć współpracę obiektów S1 z S2 i S3 a nie sekwencję działań typu: „wykonaj moduł” S1, potem S2, potem znowu S1 i, jeśli nie osiągnięto zamierzonego celu, „wykonaj moduł” S3. Sekwencje działań, sekwencje działań, sekwencje ... a gdzie logika problemu?
Oto istota programowania obiektowego, której nie da się wywieść z technologii poprzedzających:
rozbić dwie części systemów, wykonywanych w pewnej kolejności, na dwa współpracujące obiekty. Wtedy widać interakcje i niezależność obiektów. Każdy obiekt projektuje się autonomicznie, nie myśląc o nim jako o kawałku splecionym w działaniu z innym kawałkiem i kombinowaniu jak ustawić tę kolejność. Mimo że technologia może wymuszać sekwencyjne wykonywanie modułów, także i wtedy lepiej jest myśleć w kategoriach ich niezależności jako obiektów i ich współpracy - rozbijamy wtedy problem na podproblemy autonomiczne. To wielki skok pojęciowy, który trochę dokonywał się nieświadomie, aż przekroczył próg nowej jakości. Trzeba powiedzieć, że motorem stało się programowanie okienkowe - asynchroniczność działań, programowanie zdarzeniowe. Zdarzenia zdarzają się obiektom, obiekty muszą reagować, obiekty stają się wobec siebie niezależne, obiekty zaczynają się komunikować, obiekty stają się autonomicznie działającymi jednostkami. Łatwiej je pojektować, łatwiej łączyć w różnych konfiguracjach, łatwiej wielokrotnie wykorzystywać stworzone wcześniej obiekty. Obiekt działa!!!, żyje. A więc program to nie zestaw procedur i funkcji, które przetwarzają zestawy rekordów czy innych danych. Przekroczona została granica dane - procedury. Są tylko współpracujące obiekty. Obiekt działa, bo zawiera w sobie procedury i funkcje, które ustanawiają/definiują jego aktywność. Obiekt zawiera dane, które stanowią jego tożsamość, stan. Obiekty komunikują się żeby zlecić wykonanie pracy. Obiekty komunikują się, żeby przekazać wyniki tej pracy. Inny świat, a tylko trochę zmienione środki programistyczne. Inne spojrzenie na analizę systemu, inny sposób projektowania systemu, inny sposób programowania - i tu jest najtrudniej: zmienić mentalność programisty, żeby widział program jako model realizujący współpracę obiektów.
Oto obowiązujący paradygmat programowania: zastąpienie wzorca „procedury - dane” wzorcem „współpraca obiektów”.
Programowanie obiektowe prócz nowego spojrzenia na projektowanie systemów zdynamizowało programowanie. Klasyczny program był statyczny w tym sensie, że praktycznie zestaw zmiennych, funkcji i procedur był stały, choć istniały środki żeby zmieniać te zasoby (pamięć dynamiczna, rekursja, nakładkowanie podprogramów). Program obiektowy w VB .NET jest dynamicznie rozbudowywany zawsze: po uruchomieniu trzeba dynamicznie utworzyć choć jeden obiekt, bo inaczej program jest nieobiektowy, działa klasycznie jako zestaw procedur i funkcji. „Dynamicznie utworzyć” znaczy tutaj „utworzyć podczas wykonania programu a nie deklarować w tekście programu”.
Powtórzmy, obiekt to zestaw składowych: pól, zdarzeń, funkcji, procedur, właściwości. Nie wszystkie obiekty zawierają całe spektrum składowych. Jeśli obiekt zawiera tylko pola, możemy traktować go jak rekord/strukturę danych. Gdy obiekt zawiera tylko funkcje i/lub procedury, możemy go traktować jako bibliotekę, agregację tych funkcji/procedur. Jednak typowy obiekt zawiera pola definiujące jego stan i procedury/funkcje zapewniające mu aktywność. Pole to określenie na zmienną zawartą w obiekcie. Zdarzenie to pewien typ komunikatów, które obiekt deklaruje i może rozgłaszać w systemie, a inne obiekty mogą się rejestrować jako odbiorcy tych komunikatów (Event, RaiseEvent, event handler). Natomiast funkcje i procedury, które są dostępne publicznie a nie tylko lokalnie w obiekcie, nazywamy dodatkowo metodami. Funkcje i procedury publiczne, które są specyficznie definiowane (Property) i działają na zewnątrz obiektu jako pola publiczne nazywamy właściwościami. Oczywiście te określenia zostaną niżej uściślone i wyjaśnione. Dzięki składowym obiekt może żyć: osobowość zapewniają mu pola a aktywność pozostałe składowe.
Pozostaje wyjaśnić jak obiekty tworzy się i jak współdziałają, bo na tym polega wykonywanie się pogramu obiektowego.
W językach stypizowanych, do których należy język Visual Basic .NET każdy element danych jest określonego typu. Mamy wbudowane w język typy, m.in. typ Double i możemy tworzyć zmienne/pudełka tego typu. To samo dotyczy typu Integer. Gdy chcemy tworzyć własne obiekty, które też są, jak zmienne, kawałkami pamięci, choć nie tylko danymi, musimy zdefiniować ich strukturę. Do tego służy konstrukcja języka o nazwie Class. Definiujemy przy pomocy tej konstrukcji własny, czyli obiektowy typ danych, który nazywamy klasą.
Przykład:
Class Osoba
Private nazwisko As String
Private imię As String
Private wiek As Integer
Public Sub wpiszNazwisko(n as String)
nazwisko=n
End Sub
Public Function odczytajNazwisko() As String
Return nazwisko
End Function
End Class
Widać, że definicja tej klasa jest niepełna, gdyż użytkownik tej klasy (czytaj: obiekt jakiejś innej klasy, który skomunikuje się z obiektem tej klasy) nie będzie mieć dostępu do pól imię i wiek. Dodatkowo widać, że tak zdefiniowana klasa pełni rolę pojemnika przechowującego osobę i nic więcej. Jej działanie sprowadza się do pobierania danych do pól i udostępniania tych danych. Robi to jednak aktywnie - przy pomocy metod takich jak wpiszNazwisko i odczytajNazwisko. Widać, że w tej klasie wykorzystano tylko dwie instrukcje: przypisanie (...=...) w procedurze i zwrócenie wyniku (Return ...) w funkcji. Tego typu metody, często bardziej rozbudowane, nazywa się po angielsku getter/setter (jest to oczywiste ).
Gdy mamy nowy typ, możemy stworzyć zmienną tego typu, np.
Dim x as Osoba
Static y as Osoba
Private z as Osoba
Są to przykładowe deklaracje zmiennych obiektowych zanurzone w innej klasie, nazwijmy ją Użytkownik: dwie pierwsze w jakiejś funkcji/procedurze tej klasy a trzecia jako pole tej klasy - (PW) (znaczy:Patrz Wcześniej). W ten sposób, tzn. przez użycie jednej z tych zmiennych jeden obiekt może się dowiedzieć o innym obiekcie i skomunikować się z nim. Na razie jednak, zmienne te są puste! (PW). Zmienne obiektowe bowiem są zmiennymi referencyjnymi, czyli MOGĄ zawierać wędki. A na razie nie ma obiektów typu Osoba a tylko jest klasa Osoba, czyli wzorzec obiektu. Jak można utworzyć obiekt?A trzeba, żeby była współpraca! Zależy to czy chcemy (jako obiekt typu Użytkownik) skontaktować się z już istniejącym obiektem typu Osoba, czy chcemy stworzyć własnego niewolnika, żeby kazać mu robić to co umie i zaprząc go do roboty. Innego sposobu na kontakt nie ma. Jeśli potrzebny nam jest nowy obiekt typu Osoba będący niewolnikiem, wykonujemy instrukcję (która jest oczywiście częścią jakiejś procedury/funkcji klasy Użytkownik):
x=New Osoba
Jak to działa?
Operator New rezerwuje na stercie kawałek pamięci na obiekt. Obiekt jest czymś takim jak zmienna, ale NIE MA NAZWY!. Realizacja techniczna może być różna w języku programowania, ale programista MA I MUSI MYŚLEĆ, że obiekt jest kopią klasy. Zawiera w sobie te same pola i te same funkcje/procedury. Klasa jako wzorzec tkwi w pamięci w części statycznej, jako zmienna statyczna o nazwie Osoba. Natomiast obiekt tej klasy jest zawsze generowany przez operator New i umieszczony zostaje na stercie. Natomiast jego adres (przypisanie) zostaje wpisany do zmiennej x. Wędka została umieszczona w zmiennej! Zmienna referencyjna x ma teraz wartość. Tą wartością jest referencja do nowo utworzonego obiektu (PW). Od tego momentu o obiekcie będącym (jak każdy obiekt) zmienną bez nazwy MOŻEMY MYŚLEĆ jako o zmiennej x. Jeżeli chcemy wpisać nazwisko Kowalski do pola nazwisko zmiennej x, to piszemy
x.wpiszNazwisko(„Kowalski”)
Nazwisko zostanie wpisane nie do zmiennej x, gdyż tam jest adres obiektu, a do obiektu, którego adres jest w x - po wędce trafi „Kowalski” do obiektu do pola nazwisko. Czy to ważne? Tak. Chcąc kogoś skontaktować z pewną osobą nie przynosimy tej osoby ale podajemy jej adres. Traktując potem ten adres jak osobę kontaktujemy się z nią! (W C++ oba sposoby są możliwe).
W instrukcji powyższej wywołano procedurę (metodę) wpiszNazwisko „na rzecz” obiektu x. Jest to tzw. notacja kopkowa.
To jest zapis techniczny jak kontaktują się obiekty. Merytorycznie obiekty komunikują się i to jest podstawa obiektowego programowania. Jeden obiekt wysyła komunikat (nadawca), drugi go odbiera (odbiorca) i na niego w jakiś sposób reaguje i czasami wysyła komunikat zwrotny do nadawcy - synchronicznie lub później. Realizacja techniczna tej komunikacji jest trywialna: obiektem odbiorcą jest x, komunikatem zaś wysyłanym do x jest polecenie wpiszNazwisko z parametrem „Kowalski”, czyli komunikat skierowany od nadawcy do odbiorcy to instrukcja wywołania (u nadawcy) metody zdefiniowanej w odbiorcy. Jeśli to jest funkcja, to wynik, który otrzyma nadawca jest komunikatem zwrotnym!!!
Jaki jest drugi sposób skontaktowania danego obiektu z drugim obiektem (prócz stworzenia niewolnika)? Trzeba uzyskać referencję do drugiego już istniejącegoobiektu poprzez parametr funkcji/procedury, która zostanie wywołana na rzecz danego obiektu (ktoś podaje nam referencję do drugiego obiektu wołając naszą funkcję /procedurę) . Jest to bierny dostęp. Albo aktywny dostęp: to wywołanie funkcji innego obiektu, która w wyniku daje referencję do oczekiwanego drugiego obiektu. Wygląda to tak:
BIERNY DOSTĘP (DO ISTNIEJĄCEJ OSOBY).
Class Użytkownik
...
Private x As Osoba
Public Function/Sub XXX( p As Osoba, …) …
…
x=p
x.wpiszNazwisko(“Kowalski”)
...
End Function/Sub
...
End Class
gdzie INNA_KLASA ma postać:
Class INNA_KLASA
...
Private u As Użytkownik
Private os As Osoba
...
Public Function/Sub YYY
…
os= obiekt typu Osoba uzyskany w jeden z omawianych sposobów
u= obiekt typu Użytkownik uzyskany w jeden z omawianych sposobów
u.XXX(os)
…
End Function/Sub
AKTYWNY DOSTĘP (DO ISTNIEJĄCEJ OSOBY).
Class Użytkownik
...
Private z As INNA_KLASA
Private x as Osoba
…
Public Function/ Sub XXX( ...
...
z= inny_obiekt uzyskany wcześniej na jeden z omawianych sposobów
...
x=z.dajObiekt()
x.wpiszNazwisko(„Kowalski”)
...
End Sub
...
End Class
gdzie INNA_KLASA ma postać:
Class INNA_KLASA
...
Private s As Osoba
…
Public Function dajObiekt() As Osoba
…
s= uzyskany od innego obiektu albo utworzony lokalnie obiekt klasy Osoba
...
...
Return s
End Function
End Class
Po drodze widzieliśmy instrukcje przypisania postaci x=p, czyli wpisanie do zmiennej x referencji, która była w zmiennej p. Obie zmienne zawierają kopie tej samej wędki a więc pokazują na ten sam obiekt (w x nie ma kopii obiektu!). Była też instrukcja Return s, a więc wynikiem funkcji może być referencja do obiektu. Można to traktować jako oddanie przez funkcję obiektu jako jej wyniku!
W kategoriach technik projektowych UML obiekt Osoba w przypadku niewolnika ZAWARTY JEST w obiekcie Użytkownik jeśli stworzył go dla siebie. On go stworzył, on go unicestwi jawnie albo gdy sam zniknie. W przypadku otrzymania referencji do istniejącego obiektu Osoba przez obiekt Użytkownik, biernie albo aktywnie (będąc wywołanym lub wołając cudzą funkcję) mówimy, że obiekty te są w związku, w tym przypadku jednokierunkowym: użytkownik ma namiary na osobę, może się z nią kontaktować ale sam nie potrafi jej usunąć, nie należy bowiem do niego. Zauważmy jeszcze jedno. Na początku w programie nie ma obiektów, ktoś je może, MUSI, utworzyć przez operator New. Z tego wynika, że każdy obiekt powstaje jako czyjś niewolnik Ale operator New wykonuje się w funkcji/procedurze jakiegoś obiektu, a na początku nie ma żadnego ...
Wróćmy do postaci programu w momencie jego startu. W pamięci statycznej są zdefiniowane klasy, które wyglądają tak, jak później będą wyglądały obiekty na stercie, tyle że mają nazwę. Spójrzmy na składowe klasy: niektóre są poprzedzone specyfikatorem Public, inne Private, jeszcze inne Protected, Friend, Protected Friend. Wiemy, że Public znaczy dostępne dla wszystkich innych obiektów. Pozostałe specyfikatory zawężają dostęp do składowych tylko do siebie - Private, tylko do siebie i dzieci - Protected, dostęp tylko do siebie i dla kolegów z tego samego projektu - Friend, i wreszcie dostęp dla swoich dzieci nawet z innych projektów i dla kolegów z własnego projektu - Protected Friend. Tu wyprzedzamy: dzieci to klasy potomne, dziedziczące z klas rodziców. Niektóre składowe mają dodatkowy specyfikator Shared (w C++ to static). Oznacza to, że te składowe są aktywne w klasie. Pozostałe są bierne, służą jako wzorce dla przyszłych obiektów tej klasy. Wygląda to trochę dziwnie. Pole Shared (współdzielone) p jest prawdziwym polem programu, mamy do niego dostęp przez nazwę klasy K: czyli K.p. Gdy będą później obiekty, do tego samego jednego jedynego pola możemy się odwoływać przez nazwę obiektu ob.p. Jeżeli funkcja/procedura fp poprzedzona jest specyfikatorem Shared Public, to jest jednym jedynym egzemplarzem tej funkcji i możemy ją wywoływać jako K.fp lub później,gdy będą obiekty jako ob.fp. Natomiast składowe nie oznaczone jako Shared nie są dostępne w klasie, za to mają swoje kopie w obiektach, czyli egzemplarzach klasy. Więc funkcja/procedura BEZ Shared nie może być uruchomiona, gdy nie ma obiektu w którym byłaby ulokowana. Jej definicja w klasie jest tylko wzorem do skopiowania do obiektu, nie jest uruchamialna. Pole bez Shared też służy jako wzór pola dla przyszłego obiektu, nie można w nim umieścić żadnej wartości. Obrazowo wygląda to tak: W klasie pewne składowe są narysowane czarną kreską, to składowe Shared, a inne są narysowane bladą, to wzorce, nieaktywne, niematerialne w definicji klasy. Gdy zaś stworzymy obiekt, klasa służy jako stempel dla odbicia wzoru w pamięci sterty, to obiekt wygląda jak negatyw klasy: składowe blade są czarne, a czarne blade. Czyli składowa bez Shared w obiekcie staje się wykonywalną funkcją/procedurą albo dostępnym lokalnym w obiekcie polem. A co z bladymi w obiekcie składowymi ze specyfikatorem Shared? SĄ ONE DOSTĘPNE W OBIEKCIE ale nie są kopiami składowych z klasy - są składowymi współdzielonymi, wydaje się że są częścią obiektu ale zawsze sięga się do ich pól/definicji zawartej w klasie w pamięci statycznej.
Tak wygląda definicja klasy w pamięci statycznej:
Class K
Shared Private S1
Shared Public S2
Private S3
Public S4
End Class
A każdy obiekt na stercie tego typu wygląda tak:
początek obiektu k1, k2 itp
Shared Private S1
Shared Public S2
Private S3
Public S4
koniec obiektu
Użycie K.S3 czy K.S4 jest niemożliwe, to wzorce, niedostępne jako pola czy funkcje/procedury.
Użycie k1.S3 to inna zmienna niż k2.S3, bo to pola różnych obiektów chociaż odpowiadające sobie położeniem i typem w obiekcie.
Użycie K.S2 oraz k1.S2 oraz k2.S2 to TO SAMO wspólne pole zdefiniowane W KLASIE! mimo że deklarowane jako część obiektu. To samo dotyczy pól jak i funkcji/procedur, to przecież wszystko są składowe.
Wróćmy do startu programu: jak stworzyć obiekty których jeszcze nie ma? Nasz program to klasy a w klasach są zwykle tzw. aktywne funkcje/procedury i pola, czyli ze specyfikatorem Shared. Środowisko programowania, którym „otaczany” zostaje zawsze nasz program (tzw. Run Time System, zestaw procedur i funkcji pośredniczący między naszym „uklasowionym” programem a systemem operacyjnym) nie jest, jak i system, obiektowym podsystemem w kategoriach języka programowania. Uaktywnia więc nasz program jako zwykłą procedurę. Wymóg jest jeden. Procedura ta musi być zawarta w publicznej klasie, musi nazywać się Main i być zadeklarowana jako:
Shared Public Sub Main …
A przecież z punktu widzenia systemu jest to istniejąca składowa mimo, że nie ma obiektu. A co w niej? Na pewno jedną z instrukcji będzie x = New Jakiś Obiekt, a w nim kolejne y=New ... i tak program wygeneruje kolejno obiekty, które zaczną się komunikować.
Jest możliwy jeszcze jeden wariant. Możemy nie zdefiniować w żadnej klasie procedury/funkcji Main ale w opcjach kompilatora TRZEBA WTEDY określić od której klasy K zacząć wykonywanie programu. Wtedy Run Time System automatycznie uruchomi w sobie następującą instrukcję:
Application.Run(New K), gdy program jest okienkowy (Windows Application) albo New K, gdy program nie musi obsługiwać zdarzeń.
Application to wbudowana klasa, która uruchamia nowe programy sterowane zdarzeniami, a Run to jej procedura typu Shared Public Run... Jak widać parametrem jest nowoutworzony obiekt naszej klasy a on już dalej załatwi resztę dzięki procedurom zdarzeniowym i swojemu konstruktorowi - o czym dalej ...
obiekt Użytkownik
obiekt x typu Osoba
Komunikat: wpisz nazwisko „Kowalski”
KOMUNIKACJA OBIEKTÓW
Class Użytkownik
...
...
Dim x As Osoba
…
x=New Osoba
…
x.wpiszNazwisko(“Kowalski”)
...
Class Osoba
...
...
Public Sub wpiszNazwisko ...
...
...
...
End Sub
...
REALIZACJA PROGRAMOWA
To w klasie jest zapisane jak będą kontaktować się obiekty, gdy zostaną już utworzone ...