Mnóstwo odpowiedzi, ale tylko @szweszwe zwrócił uwagę na coś, co dla początkującego programisty może być naprawdę przydatne (bo wybaczcie, ale przy tym poziomie zrozumienia jaki został zaprezentowany w pierwszym poście - opowiadanie o funkcjach wirtualnych nie ma najmniejszego sensu).
@R0ut4, BARDZO wielu nauczycieli (niestety - czasem również akademickich) przedstawia swoim uczniom dziedziczenie jako sposób na unikanie powielania kodu.
"Mamy klasę punkt, w tej klasie współrzędne, koło też ma współrzędne to znaczy że warto tu użyć dziedziczenia"
To jest złe podejście - dziedziczenie definiuje nam w programie bardzo ważną zależność między klasami.
Troszkę uproszczę, ale gdy rozpatrujesz związki między różnymi obiektami, to najczęściej spotkasz się z takimi sytuacjami
A USES B (A używa B)
Można powiedzieć, że to jeden z "najluźniejszych" związków. Obiekt B jest potrzebny obiektowi A do tego żeby wykonać jakąś konkretną akcję, ale poza tym nie są ze sobą w żaden sposób związane.
Przykładowo - masz klasę "Map", która oferuje "usługę" wyszukania najbliższego sklepu:
class Map
{
...
public:
... findShop(const Point &a) const; // zastanów się, co ta funkcja może/powinna zwrócić ?
...
};
int main()
{
Map m;
Point p1 {51.4500616,16.217394};
cout << m.findShop( p ) << endl;
}
Możemy powiedzieć, że mapa (obiekt m) UŻYWA punku (obiektu p) jako punktu orientacyjnego do znalezienia najbliższego sklepu.
A HAS_A B (A posiada B)
To już jest mocniejszy związek - mamy z nim do czynienia gdy jeden obiekt jest częścią składową drugiego obiektu.
Prosty przykład - Twój punkt POSIADA współrzędne x i y. Zamodelowałeś to dodając je jako pola klasy.
class Point {
float x;
float y;
...
};
Inne dość często spotykane przypadki - gdy jedna klasa posiada całą kolekcję obiektów jakiegoś typu - np. zebranie w którym uczestniczą pracownicy
class Employee { ... };
class Meeting {
std::vector<Employee> participants; // częściej zamiast wartości przechowywalibyśmy tu jakiegoś rodzaju wskaźniki - ale to temat na przyszłość
...
};
A IS-A B (A jest szczególnym przypadkiem B)
Ten trzeci rodzaj zależności, to sytuacja w której mamy dwie klasy, w której jedna jest pewnym rozszerzeniem drugiej, ALE (co bardzo ważne) - nadal zachowuje jej wszystkie podstawowe właściwości.
Osoba i student.
Wyobraźmy sobie taki uproszczony model:
Osoba posiada imię i nazwisko. Każda osoba potrafi się przedstawić (podać swoje imię i nazwisko)
Student - posiada imię, nazwisko oraz dodatkowo numer indeksu.
Pytanie ?
Czy Osoba jest szczególnym przypadkiem Studenta ? (hmm, mamy tu odpowiednik githubowego spoiler tag-a ?)
Nie, bo nie każda osoba jest studentem. Nie każda osoba może być studentem - np. typowe dziecko w wieku 7 lat
posiada imię i nazwisko, potrafi się przedstawić - ale raczej nie zdało jeszcze matury i w związku z tym nie może być studentem
Czy Student jest szczególnym przypadkiem Osoby ?
Raczej tak...
Możemy powiedzieć, że KAŻDY student jest osobą (ale nie każda osoba jest studentem).
Jeśli mamy taką sytuację, to wtedy jest sens rozważać użycie dziedziczenia - (Student jako klasa pochodna od Osoby).
Pytanie - czy w Twoim przypadku można powiedzieć, że koło jest punktem ? Odpowiedź nie jest łatwa, bo zależy od tego co z tymi obiektami będziesz później robić...
Gdybyś np. w przyszłości chciał policzyć odległość między dwoma punktami
float distance ( const Point & p1, const Point & p2 )
{
...
}
to musiałbyś wziąć pod uwagę to, że ta funkcja powinna działać poprawnie również wtedy, gdy jako argument przekażemy jej Koło - bo dla kompilatora będzie to całkowicie legalne.
Definiując klasę koło jako klasę pochodną od punkt mówisz kompilatorowi, że każde koło jest punktem. Wszędzie tam gdzie wymagane jest dostarczenie punktu programista będzie
mógł użyć obiektu typu koło.
Inny przykład - co zrobisz, jeśli do programu będziesz musiał dodać klasę reprezentującą dowolny czworokąt. Jeśli chcesz w tym celu wykorzystac punkt - to w zasadzie czworokąt
jest kolekcją 4 punktów. Nie da się odziedziczyć kilka razy po tej samej klasie (*)... Można odziedziczyć punkt i dodać do klasy 3 kolejne - tylko jaki to ma sens ?
I teraz zaczynamy ewentualnie rozważac funkcje wirtualne i wgłębiać się w niuanse Liskov Substitution Principle ;)
Jeśli przetrawisz (ze zrozumieniem !) i będziesz miał ochotę na więcej to mogę rozszerzyć temat.
*)
tak, ja wiem ale nawet nie próbujcie mu tego teraz pokazywać ;)