• Nie Znaleziono Wyników

Wykład 2Hierarchia klas - klasy abstrakcyjne i konkretne, dziedziczenie metodNazwa przedmiotu: Inżynieria oprogramowania

N/A
N/A
Protected

Academic year: 2021

Share "Wykład 2Hierarchia klas - klasy abstrakcyjne i konkretne, dziedziczenie metodNazwa przedmiotu: Inżynieria oprogramowania"

Copied!
1
0
0

Pełen tekst

(1)

Wykład 2

Hierarchia klas - klasy abstrakcyjne i konkretne, dziedziczenie metod

Nazwa przedmiotu: Inżynieria oprogramowania Egz.

Liczba godzin: 20 godz. wykładu

Punkt_2D protected: int x, y;

Przesuń

Figura_2D

Narysuj Ukryj

Okrąg int promień;

Skaluj

Trójkąt

Punkt_2D A, B, C;

Skaluj Obróć

Klasa

abstrakcyjna

Klasy konkretne

(2)

Rysunek na stronie poprzedniej przedstawia przykład hierarchii klas z dziedziczeniem prostym w notacji Coada- Yourdona.

Omówienie:

 Klasa abstrakcyjna Figura_2D posiada dwie metody czysto wirtualne (abstrakcyjne) Narysuj i Ukryj, które mogą być dziedziczone do konkretnych klas pochodnych i wykorzystywane przez obiekty tych klas na zasadzie polimorfizmu.

 Klasa Okrąg wykorzystuje odziedziczone z klasy Punkt_2D składowe x i y i metodę Przesuń do zapamiętania współrzędnych środka okręgu i przesuwania okręgu.

 Klasa Trójkąt (poza hierarchią dziedziczenia) wykorzystuje klasę Punkt_2D do wytworzenia obiektów zagnieżdżonych (podobiektów) A, B i C w celu zapamiętania położeń wierzchołków trójkąta.

 Wszystkie klasy konkretne posiadają (oprócz odziedziczonych) własne, niezbędne im metody.

Hierarchia klas - dziedziczenie i przesłanianie funkcji składowych

Base

Derived1Level1 Derived1Level1 DerivedLeveL2

(3)

Rysunek na stronie poprzedniej przestawia dziedziczenie wielopoziomowe z udziałem dziedziczenia wielobazowego (kratę), fragment kodu poniżej – implementację tej hierarchii z uwidocznieniem zasad dziedziczenia i przesłaniania metod class Base

{ void h( );

protected:

void g( );

public:

void f( ) { … h( ); …} // wywołanie metody prywatnej };

class Derived1Level1: public Base { public:

void f( ) { … g( ); h( ); …}

// przesłonięcie odziedziczonej metody Base::f( ), // wewnątrz definicji metody f( ) odwołanie do // odziedziczonej metody g( ) i własnej metody h( ) void h( );

// deklaracja własnej metody h( ) bez przesłaniania // metody Base::h( ), która nie jest dziedziczona };

class Derived2Level1: public Base { public:

void f( ) { g( );

h( ); // błąd kompilacji – Base::h( ) niedostępna }

};

(4)

class DerivedLevel2: public Derived1Level1, public Derived2Level1 { public:

void f( ) { g( );

h( ); // wywołanie odziedziczonej metody // Derived1Level1::h( )

Base::f( ); // wywołanie funkcji z klasy bazowej Base // z użyciem kwalifikatotra dostępu

} };

W rozpatrywanej hierarchii klas:

o Próba wywołania h( ) z metody f( ) w klasie Derived2Level1 powoduje błąd kompilacji z uwagi na rodzaj dziedziczenia,

o Dane i funkcje składowe chronione klasy bazowej dostępne są jedynie w klasach pochodnych. Dlatego też obie klasy pochodne mogą wywoływać metodę chronioną g( ), ale wywołanie tej metody z dowolnej funkcji zewnętrznej byłoby już niedopuszczalne.

o Wszystkie klasy pochodne definiują lokalne wersje metody f( ), przesłaniając wersje klas bazowych. Jednak dostęp do metody zdefiniowanej wyżej w hierarchii klas zawsze można sobie zapewnić za pomocą kwalifikatora dostępu.

(5)

Postać ogólna nagłówka definicji klasy pochodnej:

class oznacznik _klasy: lista_klas_bazowych Element listy klas bazowych ma postać:

< public | protected | private > ozn_klasy_bazowej

Dostępność składowych klas bazowych w klasach pochodnych:

Rodzaj dziedziczenia

Składowe klasy bazowej ...

w klasach

pochodnych są ...

public private niedostępne

protected protected

public public

protected private niedostępne

protected protected

public protected

private private niedostępne

protected prywatnymi klasy pochodnej public prywatnymi klasy

pochodnej Jeśli żadne słowo kluczowe nie wystąpiło przyjmuje się:

 domyślnie private dla klas zdefiniowanych z użyciem słowa class,

 domyślnie public dla klas zdefiniowanych z użyciem słów struct lub union.

(6)

Obiekty klasy pochodnej w roli obiektów klasy bazowej Przykład:

class PRACOWNIK {

protected:

char * Nazwisko, * Dział;

int Uposażenie;

. . . . };

class KIEROWNIK: public PRACOWNIK {

int Uprawnienia[ 8 ];

. . . . };

int main( ) {

KIEROWNIK kk;

PRACOWNIK * p = & kk;

// każdy kierownik jest jednocześnie pracownikiem,

PRACOWNIK pp;

KIEROWNIK * k = & pp;

// . . . ale nie każdy pracownik kierownikiem }

o Obiekty klasy pochodnej są traktowane jak obiekty klasy bazowej, jeśli sięgnie się do nich za pomocą wskaźnika.

Odwrotnie – NIE !

(7)

Polimorfizm składowych. Wiązanie statyczne i dynamiczne class Class1 {

public:

virtual void f( ) { … } void g( );

};

class Class2 { public:

virtual void f( ) { … } void g( );

};

Powyżej zdefiniowano dwie, nie powiązane dziedziczeniem, klasy z identycznymi deklaracjami funkcji składowych.

Jeżeli wymusimy, aby wskaźnik uprawniony do wskazywania obiektu klasy Class1 pokazywał na obiekt klasy Class2, to:

 wywołanie poprzez ten wskaźnik metody g( ) spowoduje wywołanie metody Class1::g( ) - wiązanie statyczne,

 wywołanie poprzez ten wskaźnik metody f( ) spowoduje wywołanie metody Class2::f( ) - wiązanie dynamiczne.

o Wiązanie statyczne wskaźnika z obiektem ma miejsce na etapie kompilacji. Późniejsze wiązanie wskaźnika z obiektem innej klasy nie ma już znaczenia.

o W przypadku wiązania dynamicznego decyzja o wiązaniu jest odkładana do czasu wykonania programu. Wiązanie dynamiczne można wymusić za pomocą słowa kluczowego virtual, poprzedzającego deklarację funkcji składowej. Wiąże ono wszystkie funkcje poprzedzone słowem virtual o tej samej nazwie w różnych klasach.

(8)

Zjawisko wywoływania funkcji, stosownej do klasy obiektu, z którym w danym momencie jest związany wskaźnik, nazywamy polimorfizmem.

Polimorfizm jest potężnym narzędziem programowania obiektowego. Wystarczy wysłać standardowy komunikat do obiektów wielu różnych klas, zawierających wirtualne funkcje o tej samej nazwie, ale różnych treściach. Wykonana zostanie funkcja z klasy, z której obiektem jest w danej chwili związany wskaźnik.

Dziedziczenie i polimorfizm. Abstrakcyjne typy danych.

W poniższym przykładzie zaprezentowane zostanie użycie:

klas abstrakcyjnych, funkcji wirtualnych i czysto wirtualnych (abstrakcyjnych), oraz omówiony mechanizm polimorfizmu.

Klasa FIGURE istnieje tylko po to, aby dostarczać interfejsu dla wyprowadzanych z niej klas – jest abstrakcyjnym typem danych (abstract data type – ADT). ADT jest zwykle typem bazowym dla hierarchii klas konkretnych, nie tworzy obiektów i zazwyczaj nie posiada danych składowych.

FIGURE

RECTANGLE CIRCLE

SQUARE

Klasa abstrakcyjna

(9)

class FIGURE {

public:

// poniżej deklaracje funkcji abstrakcyjnych,

// to jest czysto wirtualnych (nie wymagających definicji) virtual float GetArea( )=0;

virtual void Draw( )=0;

};

class CIRCLE: public FIGURE {

float x, y, radius;

public:

virtual float GetArea( ) { return 3.14*radius*radius;}

virtual void Draw( );

};

class RECTANGLE: public FIGURE {

float width;

float length;

public:

virtual float GetArea( ) { return width*length;}

virtual float GetLength( ) { return length;}

virtual float GetWidth( ) { return width;}

virtual void Draw( );

};

(10)

class SQUARE: public RECTANGLE {

public:

SQUARE::SQUARE( float len): RECTANGLE(len, len){ } // powyżej definicja konstruktora klasy SQUARE -

// faktycznie tworzony jest obiekt klasy RECTANGLE // wszystkie inne jawne metody klasy są dostępne jako // odziedziczone z klasy RECTANGLE

};

Dopuszczalne jest deklarowanie wskaźników do obiektów (nie istniejących przecież obiektów) klas abstrakcyjnych.

Spróbujmy to zrobić

FIGURE *sp;

Jeśli sp wskazywać będzie na dowolny obiekt klasy konkretnej, dziedziczącej na dowolnym poziomie z klasy abstrakcyjnej FIGURE, wtedy wywołanie funkcji składowej zadeklarowanej w klasie FIGURE, spowoduje wykonanie funkcji o definicji odpowiedniej do klasy obiektu, na który akurat wskazuje sp.

Przykłady

sp->Draw( ); // wystąpi polimorfizm !!!

float area=sp->GetArea( ); // wystąpi polimorfizm !!!

Podobnie delete sp; jest równoważne z wywołaniem destruktora odpowiedniej klasy pochodnej, i usunięciem obiektu, odpowiedniego dla klasy, na którą wskazuje w danej chwili sp. Destruktor w klasie FIGURE jest czysto wirtualny, ale jego definicje znajdują się w klasach pochodnych.

(11)

Zasady używania funkcji wirtualnych i czysto wirtualnych w warunkach dziedziczenia:

 Można tworzyć obiekty klas zawierających funkcje wirtualne, ale jeśli chociaż jedną z tych funkcji uczynimy abstrakcyjną, cała klasa staje się abstrakcyjną.

 Funkcje wirtualne dziedziczą się do klas pochodnych tak jak ”zwykłe” funkcje.

 Funkcja wirtualna musi być zdefiniowana w pierwszej klasie konkretnej hierarchii klas.

 Klasy pochodne nie muszą korzystać z funkcji wirtualnych klas bazowych.

 Funkcja czysto wirtualna, która jest dziedziczona do klasy pochodnej i nie zostanie w tej klasie zdefiniowana, pozostaje funkcją czysto wirtualną, czyniąc klasę pochodną również klasą abstrakcyjną.

 Jeśli wywołuje się funkcję wirtualną z kwalifikatorem zakresu, np. SQUARE:: Draw( ), mechanizm wirtualny nie działa.

Modelowanie przypadków użycia systemu

Diagram przypadków użycia systemu pełni pierwszą i podstawową rolę w projektowaniu systemu a także planowaniu jego powstawania.

Podstawowe definicje

Przypadek użycia systemu jest zbiorem scenariuszy, powiązanych ze sobą wspólnym celem użytkownika (aktora).

Scenariusz jest ciągiem kroków opisujących interakcję między aktorem a modelowanym systemem.

(12)

Aktor (rola) jest to funkcja, którą użytkownik pełni w stosunku do systemu.

Przykład scenariusza zakupu towaru w sklepie internetowym

Zakup towaru

Klient przegląda katalog i wybiera towar Klient przechodzi do kasy

Klient podaje adres i termin dostawy System podaje pełną informację cenową Klient podaje informacje o karcie kredytowej System autoryzuje sprzedaż

System wysyła e-mail do klienta z potwierdzeniem transakcji Alternatywa: niepowodzenie autoryzacji w kroku 6.

Umożliwić klientowi powtórzenie kroku 5. i przejść do kroku 6.

Alternatywa: stały klient

Wg. scenariusza głównego do punktu 2

3. System wyświetla: bieżące warunki dostawy, informacje o cenie, cztery ostatnie cyfry numeru karty kredytowej

4. Klient potwierdza, lub zmienia swoje dane domyślne Wg. scenariusza głównego od punktu 6

(13)

Diagram przypadków użycia dla systemu maklerskiego Zalecenie metodologiczne:

 W scenariuszach i diagramie przypadków użycia nie należy dokumentować zbyt wielu szczegółów. Im wyższy stopień ryzyka niesionego przez przypadek użycia, tym dokładniejszy powinien być scenariusz. W dalszym procesie iteracyjnym i przyrostowym tworzenia systemu uszczegóławia się poszczególne scenariusze.

Ustal limity

Przeanalizuj ryzyko

Wyceń kontrakt

Zarejestruj transakcję

Limit

przekroczony

Zaktualizuj rachunki

Kierownik

sali

Makler

System księgowy

Sprzedawca Określ wartość

<<zawiera>>

<<zawiera>>

Przypadek użycia Aktor

Uogólnienie

(14)

Modelowanie związków między aktorami a przypadkami użycia:

1. Aktor to pełniona rola, a nie osoba fizyczna, zajmowane stanowisko, lub konkretny system – ta sama osoba może móc pełnić rolę kierownika sali, maklera i sprzedawcy. Odwrotnie – może być wiele osób pełniących rolę maklera.

2. Jeden aktor może występować w wielu przypadkach użycia i na odwrót – przypadek użycia może być wykonywany przez wielu aktorów.

3. Należy dążyć do wykrycia wszystkich przypadków użycia systemu.

4. Mogą być przypadki użycia nie mające związków z konkretnymi aktorami.

5. Najważniejszym jest zrozumienie przypadku użycia i celów użytkownika, które spełnia.

Modelowanie związków między poszczególnymi przypadkami użycia:

1. Do relacji zawierania dochodzi wtedy, gdy kilka przypadków użycia ma wspólną sekwencje podobnych kroków w scenariuszu.

2. Uogólnienie jest jak gdyby dziedziczeniem na poziomie przypadków użycia. Przypadek użycia podrzędny (zwany niekiedy specjalistycznym przypadkiem użycia) zwykle zawiera jeden ze scenariuszy alternatywnych podstawowego (uogólnionego) przypadku użycia. Chociaż specjalistyczny przypadek użycia przesłania niekiedy część podstawowego przypadku użycia, ale zawsze powinien dotyczyć tego samego celu użytkownika, co podstawowy przypadek użycia.

(15)

3. Relacja rozszerzenia (nie występuje w przykładowym diagramie przypadków użycia) wzbogaca podstawowy przypadek użycia o dodatkowe zachowania, które warto potraktować odrębnie. Jeśli używamy rozszerzeń, podstawowy przypadek użycia musi mieć wyspecyfikowane punkty rozszerzeń, a dodatkowy przypadek użycia może dodawać nowe zachowania tylko w tych punktach. Graficznie relację rozszerzenia w UML dokumentuje się podobnie, jak relację zawierania przy pomocy strzałki <<rozszerza>>

4. Podstawowy przypadek użycia może mieć wiele różnych rozszerzeń, podobnie jak określony specjalistyczny przypadek użycia może rozszerzać podstawowy w wielu różnych punktach.

Zalecenie metodologiczne:

 Gdy trzeba powtórzyć coś w kilku różnych przypadkach użycia a jednocześnie chce się uniknąć powtórzeń, należy używać relacji zawierania.

 Gdy trzeba opisać warianty typowego postępowania przy niezbyt jeszcze sformalizowanym scenariuszu, należy używać relacji uogólnienia.

 Gdy trzeba opisać warianty typowego postępowania, ale jest już potrzebny bardziej precyzyjny scenariusz ze zdefiniowanymi punktami rozszerzeń, należy używać relacji rozszerzenia.

 Rozbicie podstawowego przypadku użycia przy użyciu uogólnień i rozszerzeń powinno mieć rozsądny stopień, adekwatny do przyjętego w

(16)

całym diagramie przypadków użycia, stopnia szczegółowości.

Biznesowe i systemowe przypadki użycia

Systemowy przypadek użycia to interakcja użytkownika (lub innego systemu) z danym systemem, biznesowy przypadek użycia to reakcja przedsiębiorstwa (a nie systemu) na klientów i zdarzenia zewnętrzne. We wczesnych fazach rozwinięcia systemu należy koncentrować się bardziej na biznesowych przypadkach użycia. Pomagają one, na przykład, w wypracowaniu alternatywnych sposobów osiągnięcia celu przez aktora. Później dopiero można wypracowywać, spełniające poszczególne biznesowe przypadki użycia, systemowe przypadki użycia. Te ostatnie są niezbędne dla dalszego rozwijania systemu, począwszy od pierwszego jego prototypu.

Posumowanie:

 Przypadki użycia są podstawowym narzędziem w uchwyceniu wymagań systemu a później w planowaniu i zarządzaniu iteracyjnym projektem tworzenia systemu informatycznego, zwłaszcza w metodologii programowania ekstremalnego.

 Każdy przypadek użycia to potencjalne wymaganie, a dopóki nie określi się wymagań, nie można zaplanować

„co dalej”.

 Większość przypadków użycia powstaje w fazie projektu, ale w miarę postępu prac pojawiają się nowe.

 Przypadki użycia reprezentują spojrzenie z zewnątrz na tworzony system i dlatego nie istnieją korelacje między nimi a klasami wewnątrz systemu.

(17)

 Modelowanie pojęciowe (poprzez biznesowe przypadki użycia) to najlepsza platforma porozumienia z przyszłym użytkownikiem tworzonego systemu informatycznego.

Model statyczny. Diagram klas

Diagram klas w perspektywie pojęciowej obrazuje klasy modelowanego systemu i związki (asocjacje) miedzy nimi. W perspektywie implementacyjnej diagram ten zwykle ulega zmianom (rozszerzeniu o nowe klasy i asocjacje).

Narzędzia implementujące język UML (np. Rational Rose) pozwalają oglądać diagram klas na różnym poziomie szczegółowości. Dlatego mówimy o warstwie klas i powiązań, warstwie atrybutów i usług oraz warstwie specyfikacyjnej.

Rysunek na stronie 33 prezentuje przykładowy fragment diagramu klas modelującego przypadek użycia „Zamawianie towaru” przedstawiony w warstwie atrybutów i usług.

Niektóre klasy na tym rysunku zostały przedstawione skrótowo, tak jak się to robi tworząc warstwę klas i powiązań.

Są to klasy Towar i Pracownik. Pozostałe klasy zostały wyspecyfikowane w sposób typowy dla warstwy atrybutów i usług, kiedy to podaje się tylko najważniejsze dla zrozumienia modelu i ważne z punktu widzenia modelowanego przypadku użycia, atrybuty i usługi. Typ wartości atrybutu, lub wartości zwracanej przez usługę, zwykle nie jest specyfikowany, chyba że jest to istotne dla zrozumienia modelu. Nie podaje się też argumentów usług i ich typów.

Natomiast w warstwie specyfikacyjnej podaje się pełną specyfikację atrybutów, wg. schematu

specyfikator_dostępu nazwa_atrybutu:typ=wartość_domyślna oraz specyfikacje usług wg. schematu

specyfikator_dostępu nazwa_usługi(lista_parametrów):

typ_wyniku {łańcuch_własności}

(18)

* 1

1 { A* }

{wiarygodnośćKredytowa() pozycje * == ”niska”}

*

0..1 przedstawiciel handlowy

* 1

{ A* } – { if( Zamówienie.Klient.wiarygodnośćKredytowa() = = ”niska” ) Zamówienie.przedpłata = = true } Asocjacje

W perspektywie pojęciowej asocjacje reprezentują stałe związki pojęciowe miedzy obiektami klas: Zamówienie musi przyjść od pojedynczego Klienta, Klient może złożyć kilka Zamówień, Klient firmowy może mieć co najwyżej jednego Pracownika w roli Przedstawiciela handlowego.

Należy zwrócić uwagę na dopuszczalne w UML przedstawienie krotności obiektów, tj. 1, *, 0 .. 1, m .. n, np. 1. .12. Gwiazdka * oznacza 0 . . . Ponadto w ostatniej specyfikacji musi zachodzić m  n.

Zamówienie numer

dataOtrzymania przedpłata:Boolean wartość

wyślij() zamknij()

Klient nazwa

adres

wiarygodnośćKredytowa():String zamknij()

Klient firmowy wiarygodnośćKredytowa limitKredytowy

osobaDoKontaktów przypomnij() obciążZaMiesiąc()

Klient indywidualny nrKartyKredytowej

Pozycja zamówienia ilość

cena

zapewniona:Boolean

Pracownik

Towar

nazwa roli ograniczenie

(19)

Przedstawienie ról wygląda ogólnie tak jak na poniższym rysunku

rola B rola A

Nazwy ról można pominąć, jeśli pełnione role są oczywiste i zrozumiałe.

Z punktu widzenia perspektywy implementacyjnej asocjacje oznaczają zobowiązania klas i muszą znaleźć swoje odbicie w usługach klas i strukturze bazy danych, obsługiwanej przez obiekty obu klas.

Zobowiązania, o których powyżej, są czasem przedstawiane za pomocą strzałek

* 1

Mówimy wtedy, że mamy do czynienia z nawigowalnością, lub asocjacją jednokierunkową, czyli o możliwości przejścia z klasy do klasy. Realizowane jest to w ten sposób, że np.

obiekt klasy A ma wskaźnik do obiektu klasy B (mówimy czasem, że posiada obiekt klasy B), ale nie odwrotnie.

Nawigowalność powinna być specyfikowana w perspektywie implementacyjnej, natomiast w perspektywie pojęciowej nie musi.

Stosowanie ograniczeń

Ograniczenia, specyfikowane jako {opis ograniczenia}

dotyczą klas. Są to asercje, które są zdaniami logicznymi zawsze (dla wszystkich obiektów klas) prawdziwymi.

Wpisanie ograniczenia do diagramu klas zobowiązuje implementującego do takiego napisania kodu, aby prawdziwość ograniczenia była zawsze zapewniona.

Klasa_A Klasa_B

(20)

Końcowe uwagi metodologiczne, dotyczące diagramów klas:

1. Na etapie budowy modelu analizy należy stosować tylko perspektywę pojęciową.

2. Na początek używać tylko omawianych powyżej elementów: klas, asocjacji, istotnych dla zrozumienia modelu atrybutów i usług, ról, krotności obiektów, uogólnień i ograniczeń.

3. Później, ale tylko gdy będzie wyraźna potrzeba, można wprowadzać bardziej złożone elementy: nawigowalność, agregację, zawieranie, role uporządkowane, asocjacje kwalifikowane, powiązania w postaci zależności.

4. Lepiej jest mieć diagramy klas dla poszczególnych przypadków użycia systemu (nawet z pewnymi częściami wspólnymi), niż jeden wielki diagram, modelujący cały system, gdyż:

- pozwala to użytkownikowi lepiej zrozumieć system,

- ułatwia modyfikację (dodawanie funkcjonalności) i kontrolę poprawności modelu.

5. Na każdym etapie należy uważać, aby nie ugrzęznąć w szczegółach.

Cytaty

Powiązane dokumenty

Kolejnego dnia, przemierzając tę samą drogę, Piotr tankował dwa razy dłużej, przez co całkowity czas jego podróży wyniósł jedną godzinę3. Ile czasu zajęłaby

Zdrowy krasnoludek zarazi się matemafilią, jeśli co najmniej dwóch jego sąsiadów jest na nią chorych (sąsiadami są krasnoludki, które zajmują pola o sąsiednim boku)..

Na okrągłym stoliku gracze kładą złotówki, przy czym nie mogą one wystawać poza stolik ani nachodzić na siebie oraz nie wolno przesuwać leżących już monet.. W

W podobny sposób jak uzyskaliśmy pochodne drugiego i trzeciego rzędu poprzez dwu- i trzykrotne różniczkowanie funkcji, możemy zdefiniować 1 pochodną dowolnego rzędu 2 naturalnego

Dla wszystkich obiektów klasy Ksiazka powinna zostać wywołana metoda PrzedstawSie(), natomiast dla obiektów klasy Film na ekran powinno zostać wypisane nazwisko reżysera oraz

Następnie korzystając z mechanizmu dziedziczenia zdefiniuj klasy pochodne Pies i Kot, zawierające dodatkową metodę publiczną Mow(), wypisującą na ekran „hau” lub

Klasa Klasa implementuje też interfejs MouseListener, a więc jej obiekty mogą pełnić rolę słuchaczy zdarzeń typu MouseEvent.. Ponieważ implementuje interfejs MouseListener,

Metoda klasowa: Mają dostęp do całej ekstensji, a zatem do wszystkich obiektów należących do danej klasy. Mogą na nich operować, ale nie muszą: może