• Nie Znaleziono Wyników

Kontrola dostępu do składowych klasy

Nie widać w niej zbytnich restrykcji, gdyż faktycznie jest ona całkiem swobodna.

Kolejność poszczególnych elementów (pól lub metod) nie jest ściśle ustalona i może być w zasadzie dowolnie zmieniana. Najlepiej jednak zachować w tym względzie jakiś porządek, grupując np. pola i metody w zwarte grupy.

Na razie wszakże trudno byłoby stosować się do tych rad, skoro nie omówiliśmy dokładnie wszystkich części definicji klasy. Czym prędzej więc naprawiamy ten błąd :)

Kontrola dostępu do składowych klasy

Fraza oznaczona jako specyfikator_dostępu pewnie nie mówi ci zbyt wiele, chociaż spotkaliśmy się już z nią w którejś z przykładowych klas. Przyjmowała ona tam formę private lub public, dzieląc cała definicję na jakby dwie odrębne sekcje. Nietrudno wywnioskować, iż podział ten nie ma jedynie charakteru wizualnego, ale powoduje dalej idące konsekwencje. Jakie?…

Nazwa specyfikator_dostępu, chociaż brzmi może nieco sztucznie (jak zresztą wiele terminów w programowaniu :)), dobrze oddaje rolę, jaką ta konstrukcja pełni. Otóż specyfikuje ona właśnie prawa dostępu do części składowych klasy (czyli pól lub metod), wyróżniając ich dwa rodzaje: prywatne (ang. private) oraz publiczne (ang. public).

Różnica między nimi jest znacząca i bardzo ważna, gdyż wpływa na to, które elementy klasy są widoczne tylko w ramach jej samej, a które także na zewnątrz. Te pierwsze nazywamy więc prywatnymi, zaś drugie publicznymi.

Prywatne składowe klasy (wpisane po słowie private: w jej definicji) są dostępne jedynie wewnątrz samej klasy, tj. tylko dla jej własnych metod.

Publiczne składowe klasy (wpisane po słowie public: w jej definicji) widoczne są

zawsze i wszędzie - nie tylko dla samej klasy (jej metod), ale na zewnątrz - np. dla jej obiektów.

Danym specyfikatorem objęte są wszystkie następujące po nim części klasy, aż do jej końca lub… kolejnego specyfikatora :) Ich ilość nie jest bowiem niczym ograniczona.

Nic więc nie stoi na przeszkodzie, aby nie było ich wcale! W takiej sytuacji wszystkie składowe będą miały domyślne reguły dostępu. W przypadku klas (definiowanych poprzez class) jest to dostęp prywatny, natomiast dla typów strukturalnych64 (słówko struct) - dostęp publiczny.

Trudno uwierzyć, ale w C++ jest to jedyna różnica pomiędzy klasami a strukturami!

Słowa class i struct są więc niemal synonimami; jest to rzecz niespotykana w innych językach programowania, w których te dwie konstrukcje są zupełnie odrębne.

Dla skuteczniejszego rozwiania z powyższego opisu możliwej mgły niejasności, spójrzmy na ten oto przykładowy program i klasę:

// DegreesCalc - kalkulator temperatur

// typ wyliczeniowy określający skalę temperatur

64 A także dla unii, chociaż jak wiemy, funkcjonują one inaczej niż struktury i klasy.

enum SCALE {SCL_CELSIUS = 'c', SCL_FAHRENHEIT = 'f', SCL_KELVIN = 'k'};

class CDegreesCalc {

private:

// temperatura w stopniach Celsjusza double m_fStopnieC;

public:

// ustawienie i pobranie temperatury

void UstawTemperature(double fTemperatura, SCALE Skala);

double PobierzTemperature(SCALE Skala);

};

// --- funkcja main()--- void main()

{

// zapytujemy o skalę, w której będzie wprowadzona wartość char chSkala;

std::cout << "Wybierz wejsciowa skale temperatur" << std::endl;

std::cout << "(c - Celsjusza, f - Fahrenheita, k - Kelwina): ";

std::cin >> chSkala;

if (chSkala != 'c' && chSkala != 'f' && chSkala != 'k') return;

// zapytujemy o rzeczoną temperaturę float fTemperatura;

std::cout << "Podaj temperature: ";

std::cin >> fTemperatura;

// deklarujemy obiekt kalkulatora i przekazujemy doń temp.

CDegreesCalc Kalkulator;

Kalkulator.UstawTemperature (fTemperatura,

static_cast<SCALE>(chSkala));

// pokazujemy wynik - czyli temperaturę we wszystkich skalach std::cout << std::endl;

std::cout << "- stopnie Celsjusza: "

<< Kalkulator.PobierzTemperature(SCL_CELSIUS) << std::endl;

std::cout << "- stopnie Fahrenheita: "

<< Kalkulator.PobierzTemperature(SCL_FAHRENHEIT) << std::endl;

std::cout << "- kelwiny: "

<< Kalkulator.PobierzTemperature(SCL_KELVIN) << std::endl;

// czekamy na dowolny klawisz getch();

}

Cała aplikacja jest prostym programem przeliczającym między trzema skalami temperatur:

Screen 31. Kalkulator przeliczający wartości temperatur

Jej pełny kod, z implementacją metod klasy CDegreesCalc, znaleźć można w programach przykładowych. Nas jednak bardziej interesuje forma definicji tejże klasy oraz podział jej składowych na prywatne oraz publiczne.

Widzimy więc wyraźnie, iż klasa posiada jedno prywatne pole - jest nim m_fStopnieC, w którym zapisywana jest temperatura w wewnętrznie używanej, wygodnej skali Celsjusza.

Oprócz niego mamy jeszcze dwie publiczne metody - UstawTemperature() oraz

PobierzTemperature(), dzięki którym uzyskujemy dostęp do naszego prywatnego pola.

Jednocześnie oferują nam jednak dodatkową funkcjonalność, jaką jest dokonywanie przeliczania pomiędzy wartościami wyrażonymi w różnych miarach.

To bardzo częsta sytuacja, gdy prywatne pole klasy „obudowane” jest publicznymi metodami, zapewniającymi doń dostęp. Daje to wiele pożytecznych możliwości, jak choćby kontrola przypisywanej polu wartości czy tworzenie pól tylko do odczytu.

Jednocześnie „prywatność” pola chroni je przed przypadkową, niepożądaną ingerencją z zewnątrz.

Takie zjawisko wyodrębniania pewnych fragmentów kodu nazywamy hermetyzacją.

Jak wiemy, prywatne składowe klasy nie są dostępne poza nią samą. Kiedy więc tworzymy nasz obiekt:

CDegreesCalc Kalkulator;

jesteśmy niejako „skazani” na korzystanie tylko z jego publicznych metod; próba odwołania się do prywatnego pola (poprzez Kalkulator.m_fStopnieC) skończy się bowiem błędem kompilacji.

Fakt ten wcale nas jednak nie ogranicza, lecz zabezpiecza przed niepowołanym dostępem do wewnętrznych informacji klasy, które z zasady powinny być do jej wyłącznej

dyspozycji. Do komunikacji z otoczeniem istnieją za to dwie publiczne metody, i to z nich właśnie będziemy korzystać w funkcji main().

Najpierw więc wywołujemy funkcję składową UstawTemperature(), podając jej wpisaną przez użytkownika wartość oraz wybraną skalę65:

Kalkulator.UstawTemperature (fTemperatura, static_cast<SCALE>(chSkala));

W tym momencie w ogóle nie interesują nas działania, które zostaną na tych danych podjęte - jest to wewnętrzna sprawa klasy CDegreesCalc (podobnie zresztą jak jej pole m_fStopnieC). Ważne jest, że w ich następstwie możemy użyć drugiej metody,

PobierzTemperature(), do uzyskania podanej wcześniej wartości w wybranej przez siebie, nowej skali:

std::cout << "- stopnie Celsjusza: "

<< Kalkulator.PobierzTemperature(SCL_CELSIUS) << std::endl;

// itd.

Wszystkie kwestie dotyczące szczegółowych aspektów przeliczania owych wartości są zatem szczelnie poukrywane. Kod funkcji main() jest klarowny i wolny od niepotrzebnych detali, co nie zmienia faktu, iż w razie potrzeby możliwe jest zajęcie się nimi. Wystarczy przecież rzucić okiem implementacje metod klasy CDegreesCalc.

Zaprowadzanie porządku poprzez ograniczanie dostępu do pewnych elementów klasy to jedna z reguł, a jednocześnie zalet programowania obiektowego. Do jej praktycznej

65 Znowu stosujemy tu technikę odpowiedniego dobrania wartości typu wyliczeniowego, przez co unikamy instrukcji switch.

realizacji służą w C++ poznane specyfikatory private oraz public. W miarę nabywania doświadczenia w pracy z klasami będziesz je coraz efektywniej stosował w swoim

własnym kodzie.

Deklaracje pól

Pola są właściwą treścią każdego obiektu klasy, to one stanowią jego reprezentację w pamięci operacyjnej. Pod tym względem nie różnią się niczym od znanych ci już pól w strukturach i są po prostu zwykłymi zmiennymi, zgrupowanymi w jedną, kompleksową całość.

Jako miejsce na przechowywanie wszelkiego rodzaju danych, pola mają kluczowe

znaczenie dla obiektów i dlatego powinny być chronione przez niepowołanym dostępem z zewnątrz. Przyjęło się więc, że w zasadzie wszystkie pola w klasach deklaruje się jako prywatne; ich nazwy zwykle poprzedza się też przedrostkiem m_, aby odróżnić je od zmiennych lokalnych:

class CFoo66 {

private:

int m_nJakasLiczba;

std::string m_strJakisNapis;

Dostęp do danych zawartych w polach musi się zatem odbywać za pomocą

dedykowanych metod. Rozwiązanie to ma wiele rozlicznych zalet: pozwala chociażby na tworzenie pól, które można jedynie odczytywać, daje sposobność wykrywania

niedozwolonych wartości (np. indeksów przekraczających rozmiary tablic itp.) czy też podejmowania dodatkowych akcji podczas operacji przypisywania.

Rzeczone funkcje mogą wyglądać chociażby tak:

public:

int JakasLiczba() { return m_nJakasLiczba; } void JakasLiczba(int nLiczba) { m_nJakasLiczba = nLiczba; } std::string JakisNapis() { return m_strJakisNapis; } };

Nazwałem je tu identycznie jak odpowiadające im pola, pomijając jedynie przedrostki67. Niektórzy stosują nazwy w rodzaju Pobierz...()/Ustaw...() czy też z angielskiego - Get...()/Set...(). Leży to całkowicie w zakresie upodobań programisty.

Użycie naszych metod „dostępowych” może zaś przedstawiać się na przykład tak:

CFoo Foo;

Foo.JakasLiczba (10); // przypisanie 10 do pola m_nJakasLiczba std::cout << Foo.JakisNapis(); // wyświetlenie pola m_strJakisNapis Zauważmy przy okazji, że pole m_strJakisNapis może być tutaj jedynie odczytane, gdyż nie przewidzieliśmy metody do nadania mu jakiejś wartości. Takie postępowanie jest często pożądane, ale zależy rzecz jasna od konkretnej sytuacji, a tu jest jedynie przykładem.

Wielkim mankamentem C++ jest brak wsparcia dla tzw. właściwości (ang. properties), czyli „nakładek” na pola klas, imitujących zmienne i pozwalających na użycie bardziej

66 foo oraz bar to takie dziwne nazwy, stosowane przez programistów najczęściej w przykładowych kodach, dla bliżej nieokreślonych bytów, nie mających żadnego praktycznego sensu i służących jedynie w celach

prezentacyjnych. Mają one tę zaletę, że nie można ich pomylić tak łatwo, jak np. litery A, B, C, D itp.

67 Sprawia to, że funkcje odpowiadające temu samemu polu, a służące do zapisu i odczytu, są przeciążone.

naturalnej składni (choćby operatora =) niż dedykowane metody.

Wiele kompilatorów udostępnia więc tego rodzaju funkcjonalność we własnym zakresie - w Visual C++ jest to konstrukcja __declspec(property(...)), o której możesz

przeczytać w MSDN. Nie dorównuje ona jednak podobnym mechanizmom znanym z Delphi.